diff --git a/.drone.yml b/.drone.yml
index b4d18d14d509a..d6938afbf4539 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -19,7 +19,7 @@ steps:
depends_on: [ composer ]
commands:
- echo $(date)
- - ./libraries/vendor/bin/phpcs --extensions=php -p --standard=libraries/vendor/joomla/cms-coding-standards/lib/Joomla-CMS .
+ - ./libraries/vendor/bin/phpcs --extensions=php -p --standard=ruleset.xml .
- echo $(date)
- name: npm
@@ -475,6 +475,6 @@ trigger:
---
kind: signature
-hmac: d5db8148323f0205a8c0cd165da3934f5a77b25f73862d09ead95d3c42f1df01
+hmac: 783273e5140ae315f2403b13eb12f4773b77995a3d44a38551b55c89b9d40350
...
diff --git a/.editorconfig b/.editorconfig
index 6f38e0ffdd5ee..34010d1b7e1dc 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -3,14 +3,14 @@
# top-most EditorConfig file
root = true
-# Unix-style newlines with a newline ending every file
+# Unix-style end of lines and a blank line at the end of the file
[*]
-indent_style = tab
+indent_style = space
+indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,json,scss,css,vue}]
-indent_style = space
indent_size = 2
diff --git a/administrator/components/com_actionlogs/services/provider.php b/administrator/components/com_actionlogs/services/provider.php
index 0cad8c5826d5d..3d81708bd9fe0 100644
--- a/administrator/components/com_actionlogs/services/provider.php
+++ b/administrator/components/com_actionlogs/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Actionlogs'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Actionlogs'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Actionlogs'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Actionlogs'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_actionlogs/src/Controller/ActionlogsController.php b/administrator/components/com_actionlogs/src/Controller/ActionlogsController.php
index 6b429f54278e3..0c28230097e8b 100644
--- a/administrator/components/com_actionlogs/src/Controller/ActionlogsController.php
+++ b/administrator/components/com_actionlogs/src/Controller/ActionlogsController.php
@@ -1,4 +1,5 @@
registerTask('exportSelectedLogs', 'exportLogs');
- }
-
- /**
- * Method to export logs
- *
- * @return void
- *
- * @since 3.9.0
- *
- * @throws Exception
- */
- public function exportLogs()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $task = $this->getTask();
-
- $pks = array();
-
- if ($task == 'exportSelectedLogs')
- {
- // Get selected logs
- $pks = ArrayHelper::toInteger(explode(',', $this->input->post->getString('cids')));
- }
-
- /** @var ActionlogsModel $model */
- $model = $this->getModel();
-
- // Get the logs data
- $data = $model->getLogDataAsIterator($pks);
-
- if (\count($data))
- {
- try
- {
- $rows = ActionlogsHelper::getCsvData($data);
- }
- catch (InvalidArgumentException $exception)
- {
- $this->setMessage(Text::_('COM_ACTIONLOGS_ERROR_COULD_NOT_EXPORT_DATA'), 'error');
- $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false));
-
- return;
- }
-
- // Destroy the iterator now
- unset($data);
-
- $date = new Date('now', new DateTimeZone('UTC'));
- $filename = 'logs_' . $date->format('Y-m-d_His_T');
-
- $csvDelimiter = ComponentHelper::getComponent('com_actionlogs')->getParams()->get('csv_delimiter', ',');
-
- $this->app->setHeader('Content-Type', 'application/csv', true)
- ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '.csv"', true)
- ->setHeader('Cache-Control', 'must-revalidate', true)
- ->sendHeaders();
-
- $output = fopen("php://output", "w");
-
- foreach ($rows as $row)
- {
- fputcsv($output, $row, $csvDelimiter);
- }
-
- fclose($output);
- $this->app->triggerEvent('onAfterLogExport', array());
- $this->app->close();
- }
- else
- {
- $this->setMessage(Text::_('COM_ACTIONLOGS_NO_LOGS_TO_EXPORT'));
- $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false));
- }
- }
-
- /**
- * Method to get a model object, loading it if required.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return object The model.
- *
- * @since 3.9.0
- */
- public function getModel($name = 'Actionlogs', $prefix = 'Administrator', $config = ['ignore_request' => true])
- {
- // Return the model
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Clean out the logs
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function purge()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $model = $this->getModel();
-
- if ($model->purge())
- {
- $message = Text::_('COM_ACTIONLOGS_PURGE_SUCCESS');
- }
- else
- {
- $message = Text::_('COM_ACTIONLOGS_PURGE_FAIL');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false), $message);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ public function __construct($config = [], MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('exportSelectedLogs', 'exportLogs');
+ }
+
+ /**
+ * Method to export logs
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ public function exportLogs()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $task = $this->getTask();
+
+ $pks = array();
+
+ if ($task == 'exportSelectedLogs') {
+ // Get selected logs
+ $pks = ArrayHelper::toInteger(explode(',', $this->input->post->getString('cids')));
+ }
+
+ /** @var ActionlogsModel $model */
+ $model = $this->getModel();
+
+ // Get the logs data
+ $data = $model->getLogDataAsIterator($pks);
+
+ if (\count($data)) {
+ try {
+ $rows = ActionlogsHelper::getCsvData($data);
+ } catch (InvalidArgumentException $exception) {
+ $this->setMessage(Text::_('COM_ACTIONLOGS_ERROR_COULD_NOT_EXPORT_DATA'), 'error');
+ $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false));
+
+ return;
+ }
+
+ // Destroy the iterator now
+ unset($data);
+
+ $date = new Date('now', new DateTimeZone('UTC'));
+ $filename = 'logs_' . $date->format('Y-m-d_His_T');
+
+ $csvDelimiter = ComponentHelper::getComponent('com_actionlogs')->getParams()->get('csv_delimiter', ',');
+
+ $this->app->setHeader('Content-Type', 'application/csv', true)
+ ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '.csv"', true)
+ ->setHeader('Cache-Control', 'must-revalidate', true)
+ ->sendHeaders();
+
+ $output = fopen("php://output", "w");
+
+ foreach ($rows as $row) {
+ fputcsv($output, $row, $csvDelimiter);
+ }
+
+ fclose($output);
+ $this->app->triggerEvent('onAfterLogExport', array());
+ $this->app->close();
+ } else {
+ $this->setMessage(Text::_('COM_ACTIONLOGS_NO_LOGS_TO_EXPORT'));
+ $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false));
+ }
+ }
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 3.9.0
+ */
+ public function getModel($name = 'Actionlogs', $prefix = 'Administrator', $config = ['ignore_request' => true])
+ {
+ // Return the model
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Clean out the logs
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function purge()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $model = $this->getModel();
+
+ if ($model->purge()) {
+ $message = Text::_('COM_ACTIONLOGS_PURGE_SUCCESS');
+ } else {
+ $message = Text::_('COM_ACTIONLOGS_PURGE_FAIL');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_actionlogs&view=actionlogs', false), $message);
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Controller/DisplayController.php b/administrator/components/com_actionlogs/src/Controller/DisplayController.php
index 4c51bfe7ddbe5..d1c05eabe6901 100644
--- a/administrator/components/com_actionlogs/src/Controller/DisplayController.php
+++ b/administrator/components/com_actionlogs/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('extension'))
- ->from($db->quoteName('#__action_logs'))
- ->order($db->quoteName('extension'));
+ /**
+ * Method to get the options to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.9.0
+ */
+ public function getOptions()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('extension'))
+ ->from($db->quoteName('#__action_logs'))
+ ->order($db->quoteName('extension'));
- $db->setQuery($query);
- $context = $db->loadColumn();
+ $db->setQuery($query);
+ $context = $db->loadColumn();
- $options = array();
+ $options = array();
- if (\count($context) > 0)
- {
- foreach ($context as $item)
- {
- $extensions[] = strtok($item, '.');
- }
+ if (\count($context) > 0) {
+ foreach ($context as $item) {
+ $extensions[] = strtok($item, '.');
+ }
- $extensions = array_unique($extensions);
+ $extensions = array_unique($extensions);
- foreach ($extensions as $extension)
- {
- ActionlogsHelper::loadTranslationFiles($extension);
- $options[] = HTMLHelper::_('select.option', $extension, Text::_($extension));
- }
- }
+ foreach ($extensions as $extension) {
+ ActionlogsHelper::loadTranslationFiles($extension);
+ $options[] = HTMLHelper::_('select.option', $extension, Text::_($extension));
+ }
+ }
- return array_merge(parent::getOptions(), $options);
- }
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Field/LogcreatorField.php b/administrator/components/com_actionlogs/src/Field/LogcreatorField.php
index 0db00673751ca..3a85d96270cb1 100644
--- a/administrator/components/com_actionlogs/src/Field/LogcreatorField.php
+++ b/administrator/components/com_actionlogs/src/Field/LogcreatorField.php
@@ -1,4 +1,5 @@
element);
+ /**
+ * Method to get the options to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.9.0
+ */
+ protected function getOptions()
+ {
+ // Accepted modifiers
+ $hash = md5($this->element);
- if (!isset(static::$options[$hash]))
- {
- static::$options[$hash] = parent::getOptions();
+ if (!isset(static::$options[$hash])) {
+ static::$options[$hash] = parent::getOptions();
- $db = $this->getDatabase();
+ $db = $this->getDatabase();
- // Construct the query
- $query = $db->getQuery(true)
- ->select($db->quoteName('u.id', 'value'))
- ->select($db->quoteName('u.username', 'text'))
- ->from($db->quoteName('#__users', 'u'))
- ->join('INNER', $db->quoteName('#__action_logs', 'c') . ' ON ' . $db->quoteName('c.user_id') . ' = ' . $db->quoteName('u.id'))
- ->group($db->quoteName('u.id'))
- ->group($db->quoteName('u.username'))
- ->order($db->quoteName('u.username'));
+ // Construct the query
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('u.id', 'value'))
+ ->select($db->quoteName('u.username', 'text'))
+ ->from($db->quoteName('#__users', 'u'))
+ ->join('INNER', $db->quoteName('#__action_logs', 'c') . ' ON ' . $db->quoteName('c.user_id') . ' = ' . $db->quoteName('u.id'))
+ ->group($db->quoteName('u.id'))
+ ->group($db->quoteName('u.username'))
+ ->order($db->quoteName('u.username'));
- // Setup the query
- $db->setQuery($query);
+ // Setup the query
+ $db->setQuery($query);
- // Return the result
- if ($options = $db->loadObjectList())
- {
- static::$options[$hash] = array_merge(static::$options[$hash], $options);
- }
- }
+ // Return the result
+ if ($options = $db->loadObjectList()) {
+ static::$options[$hash] = array_merge(static::$options[$hash], $options);
+ }
+ }
- return static::$options[$hash];
- }
+ return static::$options[$hash];
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Field/LogsdaterangeField.php b/administrator/components/com_actionlogs/src/Field/LogsdaterangeField.php
index 89f94ac6f2d61..9a4891fbc5d78 100644
--- a/administrator/components/com_actionlogs/src/Field/LogsdaterangeField.php
+++ b/administrator/components/com_actionlogs/src/Field/LogsdaterangeField.php
@@ -1,4 +1,5 @@
'COM_ACTIONLOGS_OPTION_RANGE_TODAY',
- 'past_week' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_WEEK',
- 'past_1month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_1MONTH',
- 'past_3month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_3MONTH',
- 'past_6month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_6MONTH',
- 'past_year' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_YEAR',
- );
+ /**
+ * Available options
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $predefinedOptions = array(
+ 'today' => 'COM_ACTIONLOGS_OPTION_RANGE_TODAY',
+ 'past_week' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_WEEK',
+ 'past_1month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_1MONTH',
+ 'past_3month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_3MONTH',
+ 'past_6month' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_6MONTH',
+ 'past_year' => 'COM_ACTIONLOGS_OPTION_RANGE_PAST_YEAR',
+ );
- /**
- * Method to instantiate the form field object.
- *
- * @param Form $form The form to attach to the form field object.
- *
- * @since 3.9.0
- */
- public function __construct($form = null)
- {
- parent::__construct($form);
+ /**
+ * Method to instantiate the form field object.
+ *
+ * @param Form $form The form to attach to the form field object.
+ *
+ * @since 3.9.0
+ */
+ public function __construct($form = null)
+ {
+ parent::__construct($form);
- // Load the required language
- $lang = Factory::getLanguage();
- $lang->load('com_actionlogs', JPATH_ADMINISTRATOR);
- }
+ // Load the required language
+ $lang = Factory::getLanguage();
+ $lang->load('com_actionlogs', JPATH_ADMINISTRATOR);
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Field/LogtypeField.php b/administrator/components/com_actionlogs/src/Field/LogtypeField.php
index 6bffb3de8267b..e63b6b93d90cc 100644
--- a/administrator/components/com_actionlogs/src/Field/LogtypeField.php
+++ b/administrator/components/com_actionlogs/src/Field/LogtypeField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension'))
- ->from($db->quoteName('#__action_logs_extensions'));
-
- $extensions = $db->setQuery($query)->loadColumn();
-
- $options = [];
-
- foreach ($extensions as $extension)
- {
- ActionlogsHelper::loadTranslationFiles($extension);
- $extensionName = Text::_($extension);
- $options[ApplicationHelper::stringURLSafe($extensionName) . '_' . $extension] = HTMLHelper::_('select.option', $extension, $extensionName);
- }
-
- ksort($options);
-
- return array_merge(parent::getOptions(), array_values($options));
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.9.0
+ */
+ protected $type = 'LogType';
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.9.0
+ */
+ public function getOptions()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension'))
+ ->from($db->quoteName('#__action_logs_extensions'));
+
+ $extensions = $db->setQuery($query)->loadColumn();
+
+ $options = [];
+
+ foreach ($extensions as $extension) {
+ ActionlogsHelper::loadTranslationFiles($extension);
+ $extensionName = Text::_($extension);
+ $options[ApplicationHelper::stringURLSafe($extensionName) . '_' . $extension] = HTMLHelper::_('select.option', $extension, $extensionName);
+ }
+
+ ksort($options);
+
+ return array_merge(parent::getOptions(), array_values($options));
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Field/PlugininfoField.php b/administrator/components/com_actionlogs/src/Field/PlugininfoField.php
index 6b225cee279e7..cd281af35425c 100644
--- a/administrator/components/com_actionlogs/src/Field/PlugininfoField.php
+++ b/administrator/components/com_actionlogs/src/Field/PlugininfoField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('actionlog'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('joomla'));
- $db->setQuery($query);
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.9.2
+ */
+ protected function getInput()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('actionlog'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('joomla'));
+ $db->setQuery($query);
- $result = (int) $db->loadResult();
+ $result = (int) $db->loadResult();
- $link = HTMLHelper::_(
- 'link',
- Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $result),
- Text::_('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED'),
- array('class' => 'alert-link')
- );
+ $link = HTMLHelper::_(
+ 'link',
+ Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . $result),
+ Text::_('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED'),
+ array('class' => 'alert-link')
+ );
- return '
'
- . ''
- . Text::_('INFO')
- . ' '
- . Text::sprintf('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED_REDIRECT', $link)
- . '
';
- }
+ return ''
+ . ''
+ . Text::_('INFO')
+ . ' '
+ . Text::sprintf('PLG_SYSTEM_ACTIONLOGS_JOOMLA_ACTIONLOG_DISABLED_REDIRECT', $link)
+ . '
';
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Helper/ActionlogsHelper.php b/administrator/components/com_actionlogs/src/Helper/ActionlogsHelper.php
index 309ed9d5dcc11..26bb763c612bf 100644
--- a/administrator/components/com_actionlogs/src/Helper/ActionlogsHelper.php
+++ b/administrator/components/com_actionlogs/src/Helper/ActionlogsHelper.php
@@ -1,4 +1,5 @@
extension, '.');
-
- static::loadTranslationFiles($extension);
-
- yield array(
- 'id' => $log->id,
- 'message' => self::escapeCsvFormula(strip_tags(static::getHumanReadableLogMessage($log, false))),
- 'extension' => self::escapeCsvFormula(Text::_($extension)),
- 'date' => (new Date($log->log_date, new \DateTimeZone('UTC')))->format('Y-m-d H:i:s T'),
- 'name' => self::escapeCsvFormula($log->name),
- 'ip_address' => self::escapeCsvFormula($log->ip_address === 'COM_ACTIONLOGS_DISABLED' ? $disabledText : $log->ip_address)
- );
- }
- }
-
- /**
- * Load the translation files for an extension
- *
- * @param string $extension Extension name
- *
- * @return void
- *
- * @since 3.9.0
- */
- public static function loadTranslationFiles($extension)
- {
- static $cache = array();
- $extension = strtolower($extension);
-
- if (isset($cache[$extension]))
- {
- return;
- }
-
- $lang = Factory::getLanguage();
- $source = '';
-
- switch (substr($extension, 0, 3))
- {
- case 'com':
- default:
- $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
- break;
-
- case 'lib':
- $source = JPATH_LIBRARIES . '/' . substr($extension, 4);
- break;
-
- case 'mod':
- $source = JPATH_SITE . '/modules/' . $extension;
- break;
-
- case 'plg':
- $parts = explode('_', $extension, 3);
-
- if (\count($parts) > 2)
- {
- $source = JPATH_PLUGINS . '/' . $parts[1] . '/' . $parts[2];
- }
- break;
-
- case 'pkg':
- $source = JPATH_SITE;
- break;
-
- case 'tpl':
- $source = JPATH_BASE . '/templates/' . substr($extension, 4);
- break;
-
- }
-
- $lang->load($extension, JPATH_ADMINISTRATOR)
- || $lang->load($extension, $source);
-
- if (!$lang->hasKey(strtoupper($extension)))
- {
- $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($extension . '.sys', $source);
- }
-
- $cache[$extension] = true;
- }
-
- /**
- * Get parameters to be
- *
- * @param string $context The context of the content
- *
- * @return mixed An object contains content type parameters, or null if not found
- *
- * @since 3.9.0
- *
- * @deprecated 5.0 Use the action log config model instead
- */
- public static function getLogContentTypeParams($context)
- {
- return Factory::getApplication()->bootComponent('actionlogs')->getMVCFactory()
- ->createModel('ActionlogConfig', 'Administrator')->getLogContentTypeParams($context);
- }
-
- /**
- * Get human readable log message for a User Action Log
- *
- * @param \stdClass $log A User Action log message record
- * @param boolean $generateLinks Flag to disable link generation when creating a message
- *
- * @return string
- *
- * @since 3.9.0
- */
- public static function getHumanReadableLogMessage($log, $generateLinks = true)
- {
- static $links = array();
-
- $message = Text::_($log->message_language_key);
- $messageData = json_decode($log->message, true);
-
- // Special handling for translation extension name
- if (isset($messageData['extension_name']))
- {
- static::loadTranslationFiles($messageData['extension_name']);
- $messageData['extension_name'] = Text::_($messageData['extension_name']);
- }
-
- // Translating application
- if (isset($messageData['app']))
- {
- $messageData['app'] = Text::_($messageData['app']);
- }
-
- // Translating type
- if (isset($messageData['type']))
- {
- $messageData['type'] = Text::_($messageData['type']);
- }
-
- $linkMode = Factory::getApplication()->get('force_ssl', 0) >= 1 ? Route::TLS_FORCE : Route::TLS_IGNORE;
-
- foreach ($messageData as $key => $value)
- {
- // Escape any markup in the values to prevent XSS attacks
- $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
-
- // Convert relative url to absolute url so that it is clickable in action logs notification email
- if ($generateLinks && StringHelper::strpos($value, 'index.php?') === 0)
- {
- if (!isset($links[$value]))
- {
- $links[$value] = Route::link('administrator', $value, false, $linkMode, true);
- }
-
- $value = $links[$value];
- }
-
- $message = str_replace('{' . $key . '}', $value, $message);
- }
-
- return $message;
- }
-
- /**
- * Get link to an item of given content type
- *
- * @param string $component
- * @param string $contentType
- * @param integer $id
- * @param string $urlVar
- * @param CMSObject $object
- *
- * @return string Link to the content item
- *
- * @since 3.9.0
- */
- public static function getContentTypeLink($component, $contentType, $id, $urlVar = 'id', $object = null)
- {
- // Try to find the component helper.
- $eName = str_replace('com_', '', $component);
- $file = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/helpers/' . $eName . '.php');
-
- if (file_exists($file))
- {
- $prefix = ucfirst(str_replace('com_', '', $component));
- $cName = $prefix . 'Helper';
-
- \JLoader::register($cName, $file);
-
- if (class_exists($cName) && \is_callable(array($cName, 'getContentTypeLink')))
- {
- return $cName::getContentTypeLink($contentType, $id, $object);
- }
- }
-
- if (empty($urlVar))
- {
- $urlVar = 'id';
- }
-
- // Return default link to avoid having to implement getContentTypeLink in most of our components
- return 'index.php?option=' . $component . '&task=' . $contentType . '.edit&' . $urlVar . '=' . $id;
- }
-
- /**
- * Load both enabled and disabled actionlog plugins language file.
- *
- * It is used to make sure actions log is displayed properly instead of only language items displayed when a plugin is disabled.
- *
- * @return void
- *
- * @since 3.9.0
- */
- public static function loadActionLogPluginsLanguage()
- {
- $lang = Factory::getLanguage();
- $db = Factory::getDbo();
-
- // Get all (both enabled and disabled) actionlog plugins
- $query = $db->getQuery(true)
- ->select(
- $db->quoteName(
- array(
- 'folder',
- 'element',
- 'params',
- 'extension_id'
- ),
- array(
- 'type',
- 'name',
- 'params',
- 'id'
- )
- )
- )
- ->from('#__extensions')
- ->where('type = ' . $db->quote('plugin'))
- ->where('folder = ' . $db->quote('actionlog'))
- ->where('state IN (0,1)')
- ->order('ordering');
- $db->setQuery($query);
-
- try
- {
- $rows = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $rows = array();
- }
-
- if (empty($rows))
- {
- return;
- }
-
- foreach ($rows as $row)
- {
- $name = $row->name;
- $type = $row->type;
- $extension = 'Plg_' . $type . '_' . $name;
- $extension = strtolower($extension);
-
- // If language already loaded, don't load it again.
- if ($lang->getPaths($extension))
- {
- continue;
- }
-
- $lang->load($extension, JPATH_ADMINISTRATOR)
- || $lang->load($extension, JPATH_PLUGINS . '/' . $type . '/' . $name);
- }
-
- // Load plg_system_actionlogs too
- $lang->load('plg_system_actionlogs', JPATH_ADMINISTRATOR);
-
- // Load com_privacy too.
- $lang->load('com_privacy', JPATH_ADMINISTRATOR);
- }
-
- /**
- * Escapes potential characters that start a formula in a CSV value to prevent injection attacks
- *
- * @param mixed $value csv field value
- *
- * @return mixed
- *
- * @since 3.9.7
- */
- protected static function escapeCsvFormula($value)
- {
- if ($value == '')
- {
- return $value;
- }
-
- if (\in_array($value[0], self::$characters, true))
- {
- $value = ' ' . $value;
- }
-
- return $value;
- }
+ /**
+ * Array of characters starting a formula
+ *
+ * @var array
+ *
+ * @since 3.9.7
+ */
+ private static $characters = array('=', '+', '-', '@');
+
+ /**
+ * Method to convert logs objects array to an iterable type for use with a CSV export
+ *
+ * @param array|\Traversable $data The logs data objects to be exported
+ *
+ * @return Generator
+ *
+ * @since 3.9.0
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function getCsvData($data): Generator
+ {
+ if (!is_iterable($data)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ '%s() requires an array or object implementing the Traversable interface, a %s was given.',
+ __METHOD__,
+ \gettype($data) === 'object' ? \get_class($data) : \gettype($data)
+ )
+ );
+ }
+
+ $disabledText = Text::_('COM_ACTIONLOGS_DISABLED');
+
+ // Header row
+ yield ['Id', 'Action', 'Extension', 'Date', 'Name', 'IP Address'];
+
+ foreach ($data as $log) {
+ $extension = strtok($log->extension, '.');
+
+ static::loadTranslationFiles($extension);
+
+ yield array(
+ 'id' => $log->id,
+ 'message' => self::escapeCsvFormula(strip_tags(static::getHumanReadableLogMessage($log, false))),
+ 'extension' => self::escapeCsvFormula(Text::_($extension)),
+ 'date' => (new Date($log->log_date, new \DateTimeZone('UTC')))->format('Y-m-d H:i:s T'),
+ 'name' => self::escapeCsvFormula($log->name),
+ 'ip_address' => self::escapeCsvFormula($log->ip_address === 'COM_ACTIONLOGS_DISABLED' ? $disabledText : $log->ip_address)
+ );
+ }
+ }
+
+ /**
+ * Load the translation files for an extension
+ *
+ * @param string $extension Extension name
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public static function loadTranslationFiles($extension)
+ {
+ static $cache = array();
+ $extension = strtolower($extension);
+
+ if (isset($cache[$extension])) {
+ return;
+ }
+
+ $lang = Factory::getLanguage();
+ $source = '';
+
+ switch (substr($extension, 0, 3)) {
+ case 'com':
+ default:
+ $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
+ break;
+
+ case 'lib':
+ $source = JPATH_LIBRARIES . '/' . substr($extension, 4);
+ break;
+
+ case 'mod':
+ $source = JPATH_SITE . '/modules/' . $extension;
+ break;
+
+ case 'plg':
+ $parts = explode('_', $extension, 3);
+
+ if (\count($parts) > 2) {
+ $source = JPATH_PLUGINS . '/' . $parts[1] . '/' . $parts[2];
+ }
+ break;
+
+ case 'pkg':
+ $source = JPATH_SITE;
+ break;
+
+ case 'tpl':
+ $source = JPATH_BASE . '/templates/' . substr($extension, 4);
+ break;
+ }
+
+ $lang->load($extension, JPATH_ADMINISTRATOR)
+ || $lang->load($extension, $source);
+
+ if (!$lang->hasKey(strtoupper($extension))) {
+ $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($extension . '.sys', $source);
+ }
+
+ $cache[$extension] = true;
+ }
+
+ /**
+ * Get parameters to be
+ *
+ * @param string $context The context of the content
+ *
+ * @return mixed An object contains content type parameters, or null if not found
+ *
+ * @since 3.9.0
+ *
+ * @deprecated 5.0 Use the action log config model instead
+ */
+ public static function getLogContentTypeParams($context)
+ {
+ return Factory::getApplication()->bootComponent('actionlogs')->getMVCFactory()
+ ->createModel('ActionlogConfig', 'Administrator')->getLogContentTypeParams($context);
+ }
+
+ /**
+ * Get human readable log message for a User Action Log
+ *
+ * @param \stdClass $log A User Action log message record
+ * @param boolean $generateLinks Flag to disable link generation when creating a message
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ public static function getHumanReadableLogMessage($log, $generateLinks = true)
+ {
+ static $links = array();
+
+ $message = Text::_($log->message_language_key);
+ $messageData = json_decode($log->message, true);
+
+ // Special handling for translation extension name
+ if (isset($messageData['extension_name'])) {
+ static::loadTranslationFiles($messageData['extension_name']);
+ $messageData['extension_name'] = Text::_($messageData['extension_name']);
+ }
+
+ // Translating application
+ if (isset($messageData['app'])) {
+ $messageData['app'] = Text::_($messageData['app']);
+ }
+
+ // Translating type
+ if (isset($messageData['type'])) {
+ $messageData['type'] = Text::_($messageData['type']);
+ }
+
+ $linkMode = Factory::getApplication()->get('force_ssl', 0) >= 1 ? Route::TLS_FORCE : Route::TLS_IGNORE;
+
+ foreach ($messageData as $key => $value) {
+ // Escape any markup in the values to prevent XSS attacks
+ $value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
+
+ // Convert relative url to absolute url so that it is clickable in action logs notification email
+ if ($generateLinks && StringHelper::strpos($value, 'index.php?') === 0) {
+ if (!isset($links[$value])) {
+ $links[$value] = Route::link('administrator', $value, false, $linkMode, true);
+ }
+
+ $value = $links[$value];
+ }
+
+ $message = str_replace('{' . $key . '}', $value, $message);
+ }
+
+ return $message;
+ }
+
+ /**
+ * Get link to an item of given content type
+ *
+ * @param string $component
+ * @param string $contentType
+ * @param integer $id
+ * @param string $urlVar
+ * @param CMSObject $object
+ *
+ * @return string Link to the content item
+ *
+ * @since 3.9.0
+ */
+ public static function getContentTypeLink($component, $contentType, $id, $urlVar = 'id', $object = null)
+ {
+ // Try to find the component helper.
+ $eName = str_replace('com_', '', $component);
+ $file = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/helpers/' . $eName . '.php');
+
+ if (file_exists($file)) {
+ $prefix = ucfirst(str_replace('com_', '', $component));
+ $cName = $prefix . 'Helper';
+
+ \JLoader::register($cName, $file);
+
+ if (class_exists($cName) && \is_callable(array($cName, 'getContentTypeLink'))) {
+ return $cName::getContentTypeLink($contentType, $id, $object);
+ }
+ }
+
+ if (empty($urlVar)) {
+ $urlVar = 'id';
+ }
+
+ // Return default link to avoid having to implement getContentTypeLink in most of our components
+ return 'index.php?option=' . $component . '&task=' . $contentType . '.edit&' . $urlVar . '=' . $id;
+ }
+
+ /**
+ * Load both enabled and disabled actionlog plugins language file.
+ *
+ * It is used to make sure actions log is displayed properly instead of only language items displayed when a plugin is disabled.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public static function loadActionLogPluginsLanguage()
+ {
+ $lang = Factory::getLanguage();
+ $db = Factory::getDbo();
+
+ // Get all (both enabled and disabled) actionlog plugins
+ $query = $db->getQuery(true)
+ ->select(
+ $db->quoteName(
+ array(
+ 'folder',
+ 'element',
+ 'params',
+ 'extension_id'
+ ),
+ array(
+ 'type',
+ 'name',
+ 'params',
+ 'id'
+ )
+ )
+ )
+ ->from('#__extensions')
+ ->where('type = ' . $db->quote('plugin'))
+ ->where('folder = ' . $db->quote('actionlog'))
+ ->where('state IN (0,1)')
+ ->order('ordering');
+ $db->setQuery($query);
+
+ try {
+ $rows = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $rows = array();
+ }
+
+ if (empty($rows)) {
+ return;
+ }
+
+ foreach ($rows as $row) {
+ $name = $row->name;
+ $type = $row->type;
+ $extension = 'Plg_' . $type . '_' . $name;
+ $extension = strtolower($extension);
+
+ // If language already loaded, don't load it again.
+ if ($lang->getPaths($extension)) {
+ continue;
+ }
+
+ $lang->load($extension, JPATH_ADMINISTRATOR)
+ || $lang->load($extension, JPATH_PLUGINS . '/' . $type . '/' . $name);
+ }
+
+ // Load plg_system_actionlogs too
+ $lang->load('plg_system_actionlogs', JPATH_ADMINISTRATOR);
+
+ // Load com_privacy too.
+ $lang->load('com_privacy', JPATH_ADMINISTRATOR);
+ }
+
+ /**
+ * Escapes potential characters that start a formula in a CSV value to prevent injection attacks
+ *
+ * @param mixed $value csv field value
+ *
+ * @return mixed
+ *
+ * @since 3.9.7
+ */
+ protected static function escapeCsvFormula($value)
+ {
+ if ($value == '') {
+ return $value;
+ }
+
+ if (\in_array($value[0], self::$characters, true)) {
+ $value = ' ' . $value;
+ }
+
+ return $value;
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Model/ActionlogConfigModel.php b/administrator/components/com_actionlogs/src/Model/ActionlogConfigModel.php
index ec6149c81d666..95b76768fc792 100644
--- a/administrator/components/com_actionlogs/src/Model/ActionlogConfigModel.php
+++ b/administrator/components/com_actionlogs/src/Model/ActionlogConfigModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('a.*')
- ->from($db->quoteName('#__action_log_config', 'a'))
- ->where($db->quoteName('a.type_alias') . ' = :context')
- ->bind(':context', $context);
+ /**
+ * Returns the action logs config for the given context.
+ *
+ * @param string $context The context of the content
+ *
+ * @return stdClass|null An object contains content type parameters, or null if not found
+ *
+ * @since 4.2.0
+ */
+ public function getLogContentTypeParams(string $context): ?stdClass
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('a.*')
+ ->from($db->quoteName('#__action_log_config', 'a'))
+ ->where($db->quoteName('a.type_alias') . ' = :context')
+ ->bind(':context', $context);
- $db->setQuery($query);
+ $db->setQuery($query);
- return $db->loadObject();
- }
+ return $db->loadObject();
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Model/ActionlogModel.php b/administrator/components/com_actionlogs/src/Model/ActionlogModel.php
index 97fc399b36ca8..7bf5524ef8161 100644
--- a/administrator/components/com_actionlogs/src/Model/ActionlogModel.php
+++ b/administrator/components/com_actionlogs/src/Model/ActionlogModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $date = Factory::getDate();
- $params = ComponentHelper::getComponent('com_actionlogs')->getParams();
-
- if ($params->get('ip_logging', 0))
- {
- $ip = IpHelper::getIp();
-
- if (!filter_var($ip, FILTER_VALIDATE_IP))
- {
- $ip = 'COM_ACTIONLOGS_IP_INVALID';
- }
- }
- else
- {
- $ip = 'COM_ACTIONLOGS_DISABLED';
- }
-
- $loggedMessages = array();
-
- foreach ($messages as $message)
- {
- $logMessage = new \stdClass;
- $logMessage->message_language_key = $messageLanguageKey;
- $logMessage->message = json_encode($message);
- $logMessage->log_date = (string) $date;
- $logMessage->extension = $context;
- $logMessage->user_id = $user->id;
- $logMessage->ip_address = $ip;
- $logMessage->item_id = isset($message['id']) ? (int) $message['id'] : 0;
-
- try
- {
- $db->insertObject('#__action_logs', $logMessage);
- $loggedMessages[] = $logMessage;
- }
- catch (\RuntimeException $e)
- {
- // Ignore it
- }
- }
-
- try
- {
- // Send notification email to users who choose to be notified about the action logs
- $this->sendNotificationEmails($loggedMessages, $user->name, $context);
- }
- catch (MailDisabledException | phpMailerException $e)
- {
- // Ignore it
- }
- }
-
- /**
- * Send notification emails about the action log
- *
- * @param array $messages The logged messages
- * @param string $username The username
- * @param string $context The Context
- *
- * @return void
- *
- * @since 3.9.0
- *
- * @throws MailDisabledException if mail is disabled
- * @throws phpmailerException if sending mail failed
- */
- protected function sendNotificationEmails($messages, $username, $context)
- {
- $app = Factory::getApplication();
- $lang = $app->getLanguage();
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query
- ->select($db->quoteName(array('u.email', 'l.extensions')))
- ->from($db->quoteName('#__users', 'u'))
- ->where($db->quoteName('u.block') . ' = 0')
- ->join(
- 'INNER',
- $db->quoteName('#__action_logs_users', 'l') . ' ON ( ' . $db->quoteName('l.notify') . ' = 1 AND '
- . $db->quoteName('l.user_id') . ' = ' . $db->quoteName('u.id') . ')'
- );
-
- $db->setQuery($query);
-
- $users = $db->loadObjectList();
-
- $recipients = array();
-
- foreach ($users as $user)
- {
- $extensions = json_decode($user->extensions, true);
-
- if ($extensions && \in_array(strtok($context, '.'), $extensions))
- {
- $recipients[] = $user->email;
- }
- }
-
- if (empty($recipients))
- {
- return;
- }
-
- $extension = strtok($context, '.');
- $lang->load('com_actionlogs', JPATH_ADMINISTRATOR);
- ActionlogsHelper::loadTranslationFiles($extension);
- $temp = [];
-
- foreach ($messages as $message)
- {
- $m = [];
- $m['extension'] = Text::_($extension);
- $m['message'] = ActionlogsHelper::getHumanReadableLogMessage($message);
- $m['date'] = HTMLHelper::_('date', $message->log_date, 'Y-m-d H:i:s T', 'UTC');
- $m['username'] = $username;
- $temp[] = $m;
- }
-
- $templateData = [
- 'messages' => $temp
- ];
-
- $mailer = new MailTemplate('com_actionlogs.notification', $app->getLanguage()->getTag());
- $mailer->addTemplateData($templateData);
-
- foreach ($recipients as $recipient)
- {
- $mailer->addRecipient($recipient);
- }
-
- $mailer->send();
- }
+ /**
+ * Function to add logs to the database
+ * This method adds a record to #__action_logs contains (message_language_key, message, date, context, user)
+ *
+ * @param array $messages The contents of the messages to be logged
+ * @param string $messageLanguageKey The language key of the message
+ * @param string $context The context of the content passed to the plugin
+ * @param integer $userId ID of user perform the action, usually ID of current logged in user
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function addLog($messages, $messageLanguageKey, $context, $userId = null)
+ {
+ $user = Factory::getUser($userId);
+ $db = $this->getDatabase();
+ $date = Factory::getDate();
+ $params = ComponentHelper::getComponent('com_actionlogs')->getParams();
+
+ if ($params->get('ip_logging', 0)) {
+ $ip = IpHelper::getIp();
+
+ if (!filter_var($ip, FILTER_VALIDATE_IP)) {
+ $ip = 'COM_ACTIONLOGS_IP_INVALID';
+ }
+ } else {
+ $ip = 'COM_ACTIONLOGS_DISABLED';
+ }
+
+ $loggedMessages = array();
+
+ foreach ($messages as $message) {
+ $logMessage = new \stdClass();
+ $logMessage->message_language_key = $messageLanguageKey;
+ $logMessage->message = json_encode($message);
+ $logMessage->log_date = (string) $date;
+ $logMessage->extension = $context;
+ $logMessage->user_id = $user->id;
+ $logMessage->ip_address = $ip;
+ $logMessage->item_id = isset($message['id']) ? (int) $message['id'] : 0;
+
+ try {
+ $db->insertObject('#__action_logs', $logMessage);
+ $loggedMessages[] = $logMessage;
+ } catch (\RuntimeException $e) {
+ // Ignore it
+ }
+ }
+
+ try {
+ // Send notification email to users who choose to be notified about the action logs
+ $this->sendNotificationEmails($loggedMessages, $user->name, $context);
+ } catch (MailDisabledException | phpMailerException $e) {
+ // Ignore it
+ }
+ }
+
+ /**
+ * Send notification emails about the action log
+ *
+ * @param array $messages The logged messages
+ * @param string $username The username
+ * @param string $context The Context
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ *
+ * @throws MailDisabledException if mail is disabled
+ * @throws phpmailerException if sending mail failed
+ */
+ protected function sendNotificationEmails($messages, $username, $context)
+ {
+ $app = Factory::getApplication();
+ $lang = $app->getLanguage();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select($db->quoteName(array('u.email', 'l.extensions')))
+ ->from($db->quoteName('#__users', 'u'))
+ ->where($db->quoteName('u.block') . ' = 0')
+ ->join(
+ 'INNER',
+ $db->quoteName('#__action_logs_users', 'l') . ' ON ( ' . $db->quoteName('l.notify') . ' = 1 AND '
+ . $db->quoteName('l.user_id') . ' = ' . $db->quoteName('u.id') . ')'
+ );
+
+ $db->setQuery($query);
+
+ $users = $db->loadObjectList();
+
+ $recipients = array();
+
+ foreach ($users as $user) {
+ $extensions = json_decode($user->extensions, true);
+
+ if ($extensions && \in_array(strtok($context, '.'), $extensions)) {
+ $recipients[] = $user->email;
+ }
+ }
+
+ if (empty($recipients)) {
+ return;
+ }
+
+ $extension = strtok($context, '.');
+ $lang->load('com_actionlogs', JPATH_ADMINISTRATOR);
+ ActionlogsHelper::loadTranslationFiles($extension);
+ $temp = [];
+
+ foreach ($messages as $message) {
+ $m = [];
+ $m['extension'] = Text::_($extension);
+ $m['message'] = ActionlogsHelper::getHumanReadableLogMessage($message);
+ $m['date'] = HTMLHelper::_('date', $message->log_date, 'Y-m-d H:i:s T', 'UTC');
+ $m['username'] = $username;
+ $temp[] = $m;
+ }
+
+ $templateData = [
+ 'messages' => $temp
+ ];
+
+ $mailer = new MailTemplate('com_actionlogs.notification', $app->getLanguage()->getTag());
+ $mailer->addTemplateData($templateData);
+
+ foreach ($recipients as $recipient) {
+ $mailer->addRecipient($recipient);
+ }
+
+ $mailer->send();
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Model/ActionlogsModel.php b/administrator/components/com_actionlogs/src/Model/ActionlogsModel.php
index aff8e7da27922..d108c1cc0e72c 100644
--- a/administrator/components/com_actionlogs/src/Model/ActionlogsModel.php
+++ b/administrator/components/com_actionlogs/src/Model/ActionlogsModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('a.*')
- ->select($db->quoteName('u.name'))
- ->from($db->quoteName('#__action_logs', 'a'))
- ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'));
-
- // Get ordering
- $fullorderCol = $this->state->get('list.fullordering', 'a.id DESC');
-
- // Apply ordering
- if (!empty($fullorderCol))
- {
- $query->order($db->escape($fullorderCol));
- }
-
- // Get filter by user
- $user = $this->getState('filter.user');
-
- // Apply filter by user
- if (!empty($user))
- {
- $user = (int) $user;
- $query->where($db->quoteName('a.user_id') . ' = :userid')
- ->bind(':userid', $user, ParameterType::INTEGER);
- }
-
- // Get filter by extension
- $extension = $this->getState('filter.extension');
-
- // Apply filter by extension
- if (!empty($extension))
- {
- $extension = $extension . '%';
- $query->where($db->quoteName('a.extension') . ' LIKE :extension')
- ->bind(':extension', $extension);
- }
-
- // Get filter by date range
- $dateRange = $this->getState('filter.dateRange');
-
- // Apply filter by date range
- if (!empty($dateRange))
- {
- $date = $this->buildDateRange($dateRange);
-
- // If the chosen range is not more than a year ago
- if ($date['dNow'] !== false && $date['dStart'] !== false)
- {
- $dStart = $date['dStart']->format('Y-m-d H:i:s');
- $dNow = $date['dNow']->format('Y-m-d H:i:s');
- $query->where(
- $db->quoteName('a.log_date') . ' BETWEEN :dstart AND :dnow'
- );
- $query->bind(':dstart', $dStart);
- $query->bind(':dnow', $dNow);
- }
- }
-
- // Filter the items over the search string if set.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $ids, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'item_id:') === 0)
- {
- $ids = (int) substr($search, 8);
- $query->where($db->quoteName('a.item_id') . ' = :itemid')
- ->bind(':itemid', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . $search . '%';
- $query->where($db->quoteName('a.message') . ' LIKE :message')
- ->bind(':message', $search);
- }
- }
-
- return $query;
- }
-
- /**
- * Construct the date range to filter on.
- *
- * @param string $range The textual range to construct the filter for.
- *
- * @return array The date range to filter on.
- *
- * @since 3.9.0
- *
- * @throws Exception
- */
- private function buildDateRange($range)
- {
- // Get UTC for now.
- $dNow = new Date;
- $dStart = clone $dNow;
-
- switch ($range)
- {
- case 'past_week':
- $dStart->modify('-7 day');
- break;
-
- case 'past_1month':
- $dStart->modify('-1 month');
- break;
-
- case 'past_3month':
- $dStart->modify('-3 month');
- break;
-
- case 'past_6month':
- $dStart->modify('-6 month');
- break;
-
- case 'past_year':
- $dStart->modify('-1 year');
- break;
-
- case 'today':
- // Ranges that need to align with local 'days' need special treatment.
- $offset = Factory::getApplication()->get('offset');
-
- // Reset the start time to be the beginning of today, local time.
- $dStart = new Date('now', $offset);
- $dStart->setTime(0, 0, 0);
-
- // Now change the timezone back to UTC.
- $tz = new DateTimeZone('GMT');
- $dStart->setTimezone($tz);
- break;
- }
-
- return array('dNow' => $dNow, 'dStart' => $dStart);
- }
-
- /**
- * Get all log entries for an item
- *
- * @param string $extension The extension the item belongs to
- * @param integer $itemId The item ID
- *
- * @return array
- *
- * @since 3.9.0
- */
- public function getLogsForItem($extension, $itemId)
- {
- $itemId = (int) $itemId;
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('a.*')
- ->select($db->quoteName('u.name'))
- ->from($db->quoteName('#__action_logs', 'a'))
- ->join('INNER', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'))
- ->where($db->quoteName('a.extension') . ' = :extension')
- ->where($db->quoteName('a.item_id') . ' = :itemid')
- ->bind(':extension', $extension)
- ->bind(':itemid', $itemId, ParameterType::INTEGER);
-
- // Get ordering
- $fullorderCol = $this->getState('list.fullordering', 'a.id DESC');
-
- // Apply ordering
- if (!empty($fullorderCol))
- {
- $query->order($db->escape($fullorderCol));
- }
-
- $db->setQuery($query);
-
- return $db->loadObjectList();
- }
-
- /**
- * Get logs data into Table object
- *
- * @param integer[]|null $pks An optional array of log record IDs to load
- *
- * @return array All logs in the table
- *
- * @since 3.9.0
- */
- public function getLogsData($pks = null)
- {
- $db = $this->getDatabase();
- $query = $this->getLogDataQuery($pks);
-
- $db->setQuery($query);
-
- return $db->loadObjectList();
- }
-
- /**
- * Get logs data as a database iterator
- *
- * @param integer[]|null $pks An optional array of log record IDs to load
- *
- * @return DatabaseIterator
- *
- * @since 3.9.0
- */
- public function getLogDataAsIterator($pks = null)
- {
- $db = $this->getDatabase();
- $query = $this->getLogDataQuery($pks);
-
- $db->setQuery($query);
-
- return $db->getIterator();
- }
-
- /**
- * Get the query for loading logs data
- *
- * @param integer[]|null $pks An optional array of log record IDs to load
- *
- * @return DatabaseQuery
- *
- * @since 3.9.0
- */
- private function getLogDataQuery($pks = null)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('a.*')
- ->select($db->quoteName('u.name'))
- ->from($db->quoteName('#__action_logs', 'a'))
- ->join('INNER', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'));
-
- if (\is_array($pks) && \count($pks) > 0)
- {
- $pks = ArrayHelper::toInteger($pks);
- $query->whereIn($db->quoteName('a.id'), $pks);
- }
-
- return $query;
- }
-
- /**
- * Delete logs
- *
- * @param array $pks Primary keys of logs
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function delete(&$pks)
- {
- $keys = ArrayHelper::toInteger($pks);
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__action_logs'))
- ->whereIn($db->quoteName('id'), $keys);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- Factory::getApplication()->triggerEvent('onAfterLogPurge', array());
-
- return true;
- }
-
- /**
- * Removes all of logs from the table.
- *
- * @return boolean result of operation
- *
- * @since 3.9.0
- */
- public function purge()
- {
- try
- {
- $this->getDatabase()->truncateTable('#__action_logs');
- }
- catch (Exception $e)
- {
- return false;
- }
-
- Factory::getApplication()->triggerEvent('onAfterLogPurge', array());
-
- return true;
- }
-
- /**
- * Get the filter form
- *
- * @param array $data data
- * @param boolean $loadData load current data
- *
- * @return Form|boolean The Form object or false on error
- *
- * @since 3.9.0
- */
- public function getFilterForm($data = array(), $loadData = true)
- {
- $form = parent::getFilterForm($data, $loadData);
- $params = ComponentHelper::getParams('com_actionlogs');
- $ipLogging = (bool) $params->get('ip_logging', 0);
-
- // Add ip sort options to sort dropdown
- if ($form && $ipLogging)
- {
- /* @var \Joomla\CMS\Form\Field\ListField $field */
- $field = $form->getField('fullordering', 'list');
- $field->addOption(Text::_('COM_ACTIONLOGS_IP_ADDRESS_ASC'), array('value' => 'a.ip_address ASC'));
- $field->addOption(Text::_('COM_ACTIONLOGS_IP_ADDRESS_DESC'), array('value' => 'a.ip_address DESC'));
- }
-
- return $form;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'a.id', 'id',
+ 'a.extension', 'extension',
+ 'a.user_id', 'user',
+ 'a.message', 'message',
+ 'a.log_date', 'log_date',
+ 'a.ip_address', 'ip_address',
+ 'dateRange',
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ protected function populateState($ordering = 'a.id', $direction = 'desc')
+ {
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ protected function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('a.*')
+ ->select($db->quoteName('u.name'))
+ ->from($db->quoteName('#__action_logs', 'a'))
+ ->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'));
+
+ // Get ordering
+ $fullorderCol = $this->state->get('list.fullordering', 'a.id DESC');
+
+ // Apply ordering
+ if (!empty($fullorderCol)) {
+ $query->order($db->escape($fullorderCol));
+ }
+
+ // Get filter by user
+ $user = $this->getState('filter.user');
+
+ // Apply filter by user
+ if (!empty($user)) {
+ $user = (int) $user;
+ $query->where($db->quoteName('a.user_id') . ' = :userid')
+ ->bind(':userid', $user, ParameterType::INTEGER);
+ }
+
+ // Get filter by extension
+ $extension = $this->getState('filter.extension');
+
+ // Apply filter by extension
+ if (!empty($extension)) {
+ $extension = $extension . '%';
+ $query->where($db->quoteName('a.extension') . ' LIKE :extension')
+ ->bind(':extension', $extension);
+ }
+
+ // Get filter by date range
+ $dateRange = $this->getState('filter.dateRange');
+
+ // Apply filter by date range
+ if (!empty($dateRange)) {
+ $date = $this->buildDateRange($dateRange);
+
+ // If the chosen range is not more than a year ago
+ if ($date['dNow'] !== false && $date['dStart'] !== false) {
+ $dStart = $date['dStart']->format('Y-m-d H:i:s');
+ $dNow = $date['dNow']->format('Y-m-d H:i:s');
+ $query->where(
+ $db->quoteName('a.log_date') . ' BETWEEN :dstart AND :dnow'
+ );
+ $query->bind(':dstart', $dStart);
+ $query->bind(':dnow', $dNow);
+ }
+ }
+
+ // Filter the items over the search string if set.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $ids, ParameterType::INTEGER);
+ } elseif (stripos($search, 'item_id:') === 0) {
+ $ids = (int) substr($search, 8);
+ $query->where($db->quoteName('a.item_id') . ' = :itemid')
+ ->bind(':itemid', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . $search . '%';
+ $query->where($db->quoteName('a.message') . ' LIKE :message')
+ ->bind(':message', $search);
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Construct the date range to filter on.
+ *
+ * @param string $range The textual range to construct the filter for.
+ *
+ * @return array The date range to filter on.
+ *
+ * @since 3.9.0
+ *
+ * @throws Exception
+ */
+ private function buildDateRange($range)
+ {
+ // Get UTC for now.
+ $dNow = new Date();
+ $dStart = clone $dNow;
+
+ switch ($range) {
+ case 'past_week':
+ $dStart->modify('-7 day');
+ break;
+
+ case 'past_1month':
+ $dStart->modify('-1 month');
+ break;
+
+ case 'past_3month':
+ $dStart->modify('-3 month');
+ break;
+
+ case 'past_6month':
+ $dStart->modify('-6 month');
+ break;
+
+ case 'past_year':
+ $dStart->modify('-1 year');
+ break;
+
+ case 'today':
+ // Ranges that need to align with local 'days' need special treatment.
+ $offset = Factory::getApplication()->get('offset');
+
+ // Reset the start time to be the beginning of today, local time.
+ $dStart = new Date('now', $offset);
+ $dStart->setTime(0, 0, 0);
+
+ // Now change the timezone back to UTC.
+ $tz = new DateTimeZone('GMT');
+ $dStart->setTimezone($tz);
+ break;
+ }
+
+ return array('dNow' => $dNow, 'dStart' => $dStart);
+ }
+
+ /**
+ * Get all log entries for an item
+ *
+ * @param string $extension The extension the item belongs to
+ * @param integer $itemId The item ID
+ *
+ * @return array
+ *
+ * @since 3.9.0
+ */
+ public function getLogsForItem($extension, $itemId)
+ {
+ $itemId = (int) $itemId;
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('a.*')
+ ->select($db->quoteName('u.name'))
+ ->from($db->quoteName('#__action_logs', 'a'))
+ ->join('INNER', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'))
+ ->where($db->quoteName('a.extension') . ' = :extension')
+ ->where($db->quoteName('a.item_id') . ' = :itemid')
+ ->bind(':extension', $extension)
+ ->bind(':itemid', $itemId, ParameterType::INTEGER);
+
+ // Get ordering
+ $fullorderCol = $this->getState('list.fullordering', 'a.id DESC');
+
+ // Apply ordering
+ if (!empty($fullorderCol)) {
+ $query->order($db->escape($fullorderCol));
+ }
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList();
+ }
+
+ /**
+ * Get logs data into Table object
+ *
+ * @param integer[]|null $pks An optional array of log record IDs to load
+ *
+ * @return array All logs in the table
+ *
+ * @since 3.9.0
+ */
+ public function getLogsData($pks = null)
+ {
+ $db = $this->getDatabase();
+ $query = $this->getLogDataQuery($pks);
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList();
+ }
+
+ /**
+ * Get logs data as a database iterator
+ *
+ * @param integer[]|null $pks An optional array of log record IDs to load
+ *
+ * @return DatabaseIterator
+ *
+ * @since 3.9.0
+ */
+ public function getLogDataAsIterator($pks = null)
+ {
+ $db = $this->getDatabase();
+ $query = $this->getLogDataQuery($pks);
+
+ $db->setQuery($query);
+
+ return $db->getIterator();
+ }
+
+ /**
+ * Get the query for loading logs data
+ *
+ * @param integer[]|null $pks An optional array of log record IDs to load
+ *
+ * @return DatabaseQuery
+ *
+ * @since 3.9.0
+ */
+ private function getLogDataQuery($pks = null)
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('a.*')
+ ->select($db->quoteName('u.name'))
+ ->from($db->quoteName('#__action_logs', 'a'))
+ ->join('INNER', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('a.user_id') . ' = ' . $db->quoteName('u.id'));
+
+ if (\is_array($pks) && \count($pks) > 0) {
+ $pks = ArrayHelper::toInteger($pks);
+ $query->whereIn($db->quoteName('a.id'), $pks);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Delete logs
+ *
+ * @param array $pks Primary keys of logs
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function delete(&$pks)
+ {
+ $keys = ArrayHelper::toInteger($pks);
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__action_logs'))
+ ->whereIn($db->quoteName('id'), $keys);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ Factory::getApplication()->triggerEvent('onAfterLogPurge', array());
+
+ return true;
+ }
+
+ /**
+ * Removes all of logs from the table.
+ *
+ * @return boolean result of operation
+ *
+ * @since 3.9.0
+ */
+ public function purge()
+ {
+ try {
+ $this->getDatabase()->truncateTable('#__action_logs');
+ } catch (Exception $e) {
+ return false;
+ }
+
+ Factory::getApplication()->triggerEvent('onAfterLogPurge', array());
+
+ return true;
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return Form|boolean The Form object or false on error
+ *
+ * @since 3.9.0
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+ $params = ComponentHelper::getParams('com_actionlogs');
+ $ipLogging = (bool) $params->get('ip_logging', 0);
+
+ // Add ip sort options to sort dropdown
+ if ($form && $ipLogging) {
+ /* @var \Joomla\CMS\Form\Field\ListField $field */
+ $field = $form->getField('fullordering', 'list');
+ $field->addOption(Text::_('COM_ACTIONLOGS_IP_ADDRESS_ASC'), array('value' => 'a.ip_address ASC'));
+ $field->addOption(Text::_('COM_ACTIONLOGS_IP_ADDRESS_DESC'), array('value' => 'a.ip_address DESC'));
+ }
+
+ return $form;
+ }
}
diff --git a/administrator/components/com_actionlogs/src/Plugin/ActionLogPlugin.php b/administrator/components/com_actionlogs/src/Plugin/ActionLogPlugin.php
index c6520f1360810..ca2b4cc6e984d 100644
--- a/administrator/components/com_actionlogs/src/Plugin/ActionLogPlugin.php
+++ b/administrator/components/com_actionlogs/src/Plugin/ActionLogPlugin.php
@@ -1,4 +1,5 @@
$message)
- {
- if (!\array_key_exists('userid', $message))
- {
- $message['userid'] = $user->id;
- }
+ foreach ($messages as $index => $message) {
+ if (!\array_key_exists('userid', $message)) {
+ $message['userid'] = $user->id;
+ }
- if (!\array_key_exists('username', $message))
- {
- $message['username'] = $user->username;
- }
+ if (!\array_key_exists('username', $message)) {
+ $message['username'] = $user->username;
+ }
- if (!\array_key_exists('accountlink', $message))
- {
- $message['accountlink'] = 'index.php?option=com_users&task=user.edit&id=' . $user->id;
- }
+ if (!\array_key_exists('accountlink', $message)) {
+ $message['accountlink'] = 'index.php?option=com_users&task=user.edit&id=' . $user->id;
+ }
- if (\array_key_exists('type', $message))
- {
- $message['type'] = strtoupper($message['type']);
- }
+ if (\array_key_exists('type', $message)) {
+ $message['type'] = strtoupper($message['type']);
+ }
- if (\array_key_exists('app', $message))
- {
- $message['app'] = strtoupper($message['app']);
- }
+ if (\array_key_exists('app', $message)) {
+ $message['app'] = strtoupper($message['app']);
+ }
- $messages[$index] = $message;
- }
+ $messages[$index] = $message;
+ }
- /** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel $model */
- $model = $this->app->bootComponent('com_actionlogs')
- ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
+ /** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogModel $model */
+ $model = $this->app->bootComponent('com_actionlogs')
+ ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
- $model->addLog($messages, strtoupper($messageLanguageKey), $context, $userId);
- }
+ $model->addLog($messages, strtoupper($messageLanguageKey), $context, $userId);
+ }
}
diff --git a/administrator/components/com_admin/postinstall/addnosniff.php b/administrator/components/com_admin/postinstall/addnosniff.php
index b8dcab690900b..620d898cedcf1 100644
--- a/administrator/components/com_admin/postinstall/addnosniff.php
+++ b/administrator/components/com_admin/postinstall/addnosniff.php
@@ -1,4 +1,5 @@
get('behind_loadbalancer', '0'))
- {
- return false;
- }
+ if ($app->get('behind_loadbalancer', '0')) {
+ return false;
+ }
- if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) && !empty($_SERVER['HTTP_X_FORWARDED_FOR']))
- {
- return true;
- }
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ return true;
+ }
- if (array_key_exists('HTTP_CLIENT_IP', $_SERVER) && !empty($_SERVER['HTTP_CLIENT_IP']))
- {
- return true;
- }
+ if (array_key_exists('HTTP_CLIENT_IP', $_SERVER) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
+ return true;
+ }
- return false;
+ return false;
}
@@ -55,35 +51,32 @@ function admin_postinstall_behindproxy_condition()
*/
function behindproxy_postinstall_action()
{
- $prev = ArrayHelper::fromObject(new JConfig);
- $data = array_merge($prev, array('behind_loadbalancer' => '1'));
+ $prev = ArrayHelper::fromObject(new JConfig());
+ $data = array_merge($prev, array('behind_loadbalancer' => '1'));
- $config = new Registry($data);
+ $config = new Registry($data);
- // Set the configuration file path.
- $file = JPATH_CONFIGURATION . '/configuration.php';
+ // Set the configuration file path.
+ $file = JPATH_CONFIGURATION . '/configuration.php';
- // Attempt to make the file writeable
- if (Path::isOwner($file) && !Path::setPermissions($file, '0644'))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'error');
+ // Attempt to make the file writeable
+ if (Path::isOwner($file) && !Path::setPermissions($file, '0644')) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'error');
- return;
- }
+ return;
+ }
- // Attempt to write the configuration file as a PHP class named JConfig.
- $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false));
+ // Attempt to write the configuration file as a PHP class named JConfig.
+ $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false));
- if (!File::write($file, $configuration))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_WRITE_FAILED'), 'error');
+ if (!File::write($file, $configuration)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_WRITE_FAILED'), 'error');
- return;
- }
+ return;
+ }
- // Attempt to make the file unwriteable
- if (Path::isOwner($file) && !Path::setPermissions($file, '0444'))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'error');
- }
+ // Attempt to make the file unwriteable
+ if (Path::isOwner($file) && !Path::setPermissions($file, '0444')) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'error');
+ }
}
diff --git a/administrator/components/com_admin/postinstall/htaccesssvg.php b/administrator/components/com_admin/postinstall/htaccesssvg.php
index 91645751f1d39..bfe292da5ab4b 100644
--- a/administrator/components/com_admin/postinstall/htaccesssvg.php
+++ b/administrator/components/com_admin/postinstall/htaccesssvg.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select($db->quoteName('access'))
- ->from($db->quoteName('#__languages'))
- ->where($db->quoteName('access') . ' = ' . $db->quote('0'));
- $db->setQuery($query);
- $db->execute();
- $numRows = $db->getNumRows();
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('access'))
+ ->from($db->quoteName('#__languages'))
+ ->where($db->quoteName('access') . ' = ' . $db->quote('0'));
+ $db->setQuery($query);
+ $db->execute();
+ $numRows = $db->getNumRows();
- if (isset($numRows) && $numRows != 0)
- {
- // We have rows here so we have at minimum one row with access set to 0
- return true;
- }
+ if (isset($numRows) && $numRows != 0) {
+ // We have rows here so we have at minimum one row with access set to 0
+ return true;
+ }
- // All good the query return nothing.
- return false;
+ // All good the query return nothing.
+ return false;
}
diff --git a/administrator/components/com_admin/postinstall/statscollection.php b/administrator/components/com_admin/postinstall/statscollection.php
index c1cf1a3a9886a..ead154f4548cd 100644
--- a/administrator/components/com_admin/postinstall/statscollection.php
+++ b/administrator/components/com_admin/postinstall/statscollection.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
- */
-defined('_JEXEC') or die;
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
+ */
use Joomla\CMS\Extension\ExtensionHelper;
use Joomla\CMS\Factory;
@@ -27,8795 +28,8650 @@
*/
class JoomlaInstallerScript
{
- /**
- * The Joomla Version we are updating from
- *
- * @var string
- * @since 3.7
- */
- protected $fromVersion = null;
-
- /**
- * Function to act prior to installation process begins
- *
- * @param string $action Which action is happening (install|uninstall|discover_install|update)
- * @param Installer $installer The class calling this method
- *
- * @return boolean True on success
- *
- * @since 3.7.0
- */
- public function preflight($action, $installer)
- {
- if ($action === 'update')
- {
- // Get the version we are updating from
- if (!empty($installer->extension->manifest_cache))
- {
- $manifestValues = json_decode($installer->extension->manifest_cache, true);
-
- if (array_key_exists('version', $manifestValues))
- {
- $this->fromVersion = $manifestValues['version'];
-
- // Ensure templates are moved to the correct mode
- $this->fixTemplateMode();
-
- return true;
- }
- }
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Method to update Joomla!
- *
- * @param Installer $installer The class calling this method
- *
- * @return void
- */
- public function update($installer)
- {
- $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
- $options['text_file'] = 'joomla_update.php';
-
- Log::addLogger($options, Log::INFO, array('Update', 'databasequery', 'jerror'));
-
- try
- {
- Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_DELETE_FILES'), Log::INFO, 'Update');
- }
- catch (RuntimeException $exception)
- {
- // Informational log only
- }
-
- // Uninstall plugins before removing their files and folders
- $this->uninstallRepeatableFieldsPlugin();
- $this->uninstallEosPlugin();
-
- // This needs to stay for 2.5 update compatibility
- $this->deleteUnexistingFiles();
- $this->updateManifestCaches();
- $this->updateDatabase();
- $this->updateAssets($installer);
- $this->clearStatsCache();
- $this->convertTablesToUtf8mb4(true);
- $this->addUserAuthProviderColumn();
- $this->cleanJoomlaCache();
- }
-
- /**
- * Method to clear our stats plugin cache to ensure we get fresh data on Joomla Update
- *
- * @return void
- *
- * @since 3.5
- */
- protected function clearStatsCache()
- {
- $db = Factory::getDbo();
-
- try
- {
- // Get the params for the stats plugin
- $params = $db->setQuery(
- $db->getQuery(true)
- ->select($db->quoteName('params'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('stats'))
- )->loadResult();
- }
- catch (Exception $e)
- {
- echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '');
- $html = preg_replace('/ ]*>([^<]+)<\/th>/', '\1 ', $html);
- $html = preg_replace('/ ]*>([^<]+)<\/td>/', '\1 ', $html);
- $t = preg_split('/(]*>[^<]+<\/h2>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
- $r = [];
- $count = \count($t);
- $p1 = '([^<]+)<\/info>';
- $p2 = '/' . $p1 . '\s*' . $p1 . '\s*' . $p1 . '/';
- $p3 = '/' . $p1 . '\s*' . $p1 . '/';
-
- for ($i = 1; $i < $count; $i++)
- {
- if (preg_match('/]*>([^<]+)<\/h2>/', $t[$i], $matches))
- {
- $name = trim($matches[1]);
- $vals = explode("\n", $t[$i + 1]);
-
- foreach ($vals AS $val)
- {
- // 3cols
- if (preg_match($p2, $val, $matches))
- {
- $r[$name][trim($matches[1])] = [trim($matches[2]), trim($matches[3]),];
- }
- // 2cols
- elseif (preg_match($p3, $val, $matches))
- {
- $r[$name][trim($matches[1])] = trim($matches[2]);
- }
- }
- }
- }
-
- return $r;
- }
+ /**
+ * Some PHP settings
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $php_settings = [];
+
+ /**
+ * Config values
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $config = [];
+
+ /**
+ * Some system values
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $info = [];
+
+ /**
+ * PHP info
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $php_info = null;
+
+ /**
+ * Array containing the phpinfo() data.
+ *
+ * @var array
+ *
+ * @since 3.5
+ */
+ protected $phpInfoArray;
+
+ /**
+ * Private/critical data that we don't want to share
+ *
+ * @var array
+ *
+ * @since 3.5
+ */
+ protected $privateSettings = [
+ 'phpInfoArray' => [
+ 'CONTEXT_DOCUMENT_ROOT',
+ 'Cookie',
+ 'DOCUMENT_ROOT',
+ 'extension_dir',
+ 'error_log',
+ 'Host',
+ 'HTTP_COOKIE',
+ 'HTTP_HOST',
+ 'HTTP_ORIGIN',
+ 'HTTP_REFERER',
+ 'HTTP Request',
+ 'include_path',
+ 'mysql.default_socket',
+ 'MYSQL_SOCKET',
+ 'MYSQL_INCLUDE',
+ 'MYSQL_LIBS',
+ 'mysqli.default_socket',
+ 'MYSQLI_SOCKET',
+ 'PATH',
+ 'Path to sendmail',
+ 'pdo_mysql.default_socket',
+ 'Referer',
+ 'REMOTE_ADDR',
+ 'SCRIPT_FILENAME',
+ 'sendmail_path',
+ 'SERVER_ADDR',
+ 'SERVER_ADMIN',
+ 'Server Administrator',
+ 'SERVER_NAME',
+ 'Server Root',
+ 'session.name',
+ 'session.save_path',
+ 'upload_tmp_dir',
+ 'User/Group',
+ 'open_basedir',
+ ],
+ 'other' => [
+ 'db',
+ 'dbprefix',
+ 'fromname',
+ 'live_site',
+ 'log_path',
+ 'mailfrom',
+ 'memcached_server_host',
+ 'open_basedir',
+ 'Origin',
+ 'proxy_host',
+ 'proxy_user',
+ 'proxy_pass',
+ 'redis_server_host',
+ 'redis_server_auth',
+ 'secret',
+ 'sendmail',
+ 'session.save_path',
+ 'session_memcached_server_host',
+ 'session_redis_server_host',
+ 'session_redis_server_auth',
+ 'sitename',
+ 'smtphost',
+ 'tmp_path',
+ 'open_basedir',
+ ]
+ ];
+
+ /**
+ * System values that can be "safely" shared
+ *
+ * @var array
+ *
+ * @since 3.5
+ */
+ protected $safeData;
+
+ /**
+ * Information about writable state of directories
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $directories = [];
+
+ /**
+ * The current editor.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $editor = null;
+
+ /**
+ * Remove sections of data marked as private in the privateSettings
+ *
+ * @param array $dataArray Array with data that may contain private information
+ * @param string $dataType Type of data to search for a specific section in the privateSettings array
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function cleanPrivateData(array $dataArray, string $dataType = 'other'): array
+ {
+ $dataType = isset($this->privateSettings[$dataType]) ? $dataType : 'other';
+
+ $privateSettings = $this->privateSettings[$dataType];
+
+ if (!$privateSettings) {
+ return $dataArray;
+ }
+
+ foreach ($dataArray as $section => $values) {
+ if (\is_array($values)) {
+ $dataArray[$section] = $this->cleanPrivateData($values, $dataType);
+ }
+
+ if (\in_array($section, $privateSettings, true)) {
+ $dataArray[$section] = $this->cleanSectionPrivateData($values);
+ }
+ }
+
+ return $dataArray;
+ }
+
+ /**
+ * Obfuscate section values
+ *
+ * @param mixed $sectionValues Section data
+ *
+ * @return string|array
+ *
+ * @since 3.5
+ */
+ protected function cleanSectionPrivateData($sectionValues)
+ {
+ if (!\is_array($sectionValues)) {
+ if (strstr($sectionValues, JPATH_ROOT)) {
+ $sectionValues = 'xxxxxx';
+ }
+
+ return \strlen($sectionValues) ? 'xxxxxx' : '';
+ }
+
+ foreach ($sectionValues as $setting => $value) {
+ $sectionValues[$setting] = \strlen($value) ? 'xxxxxx' : '';
+ }
+
+ return $sectionValues;
+ }
+
+ /**
+ * Method to get the PHP settings
+ *
+ * @return array Some PHP settings
+ *
+ * @since 1.6
+ */
+ public function &getPhpSettings(): array
+ {
+ if (!empty($this->php_settings)) {
+ return $this->php_settings;
+ }
+
+ $this->php_settings = [
+ 'memory_limit' => ini_get('memory_limit'),
+ 'upload_max_filesize' => ini_get('upload_max_filesize'),
+ 'post_max_size' => ini_get('post_max_size'),
+ 'display_errors' => ini_get('display_errors') == '1',
+ 'short_open_tag' => ini_get('short_open_tag') == '1',
+ 'file_uploads' => ini_get('file_uploads') == '1',
+ 'output_buffering' => (int) ini_get('output_buffering') !== 0,
+ 'open_basedir' => ini_get('open_basedir'),
+ 'session.save_path' => ini_get('session.save_path'),
+ 'session.auto_start' => ini_get('session.auto_start'),
+ 'disable_functions' => ini_get('disable_functions'),
+ 'xml' => \extension_loaded('xml'),
+ 'zlib' => \extension_loaded('zlib'),
+ 'zip' => \function_exists('zip_open') && \function_exists('zip_read'),
+ 'mbstring' => \extension_loaded('mbstring'),
+ 'fileinfo' => \extension_loaded('fileinfo'),
+ 'gd' => \extension_loaded('gd'),
+ 'iconv' => \function_exists('iconv'),
+ 'intl' => \function_exists('transliterator_transliterate'),
+ 'max_input_vars' => ini_get('max_input_vars'),
+ ];
+
+ return $this->php_settings;
+ }
+
+ /**
+ * Method to get the config
+ *
+ * @return array config values
+ *
+ * @since 1.6
+ */
+ public function &getConfig(): array
+ {
+ if (!empty($this->config)) {
+ return $this->config;
+ }
+
+ $registry = new Registry(new \JConfig());
+ $this->config = $registry->toArray();
+ $hidden = [
+ 'host', 'user', 'password', 'ftp_user', 'ftp_pass',
+ 'smtpuser', 'smtppass', 'redis_server_auth', 'session_redis_server_auth',
+ 'proxy_user', 'proxy_pass', 'secret'
+ ];
+
+ foreach ($hidden as $key) {
+ $this->config[$key] = 'xxxxxx';
+ }
+
+ return $this->config;
+ }
+
+ /**
+ * Method to get the system information
+ *
+ * @return array System information values
+ *
+ * @since 1.6
+ */
+ public function &getInfo(): array
+ {
+ if (!empty($this->info)) {
+ return $this->info;
+ }
+
+ $db = $this->getDatabase();
+
+ $this->info = [
+ 'php' => php_uname(),
+ 'dbserver' => $db->getServerType(),
+ 'dbversion' => $db->getVersion(),
+ 'dbcollation' => $db->getCollation(),
+ 'dbconnectioncollation' => $db->getConnectionCollation(),
+ 'dbconnectionencryption' => $db->getConnectionEncryption(),
+ 'dbconnencryptsupported' => $db->isConnectionEncryptionSupported(),
+ 'phpversion' => PHP_VERSION,
+ 'server' => $_SERVER['SERVER_SOFTWARE'] ?? getenv('SERVER_SOFTWARE'),
+ 'sapi_name' => PHP_SAPI,
+ 'version' => (new Version())->getLongVersion(),
+ 'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
+ ];
+
+ return $this->info;
+ }
+
+ /**
+ * Check if the phpinfo function is enabled
+ *
+ * @return boolean True if enabled
+ *
+ * @since 3.4.1
+ */
+ public function phpinfoEnabled(): bool
+ {
+ return !\in_array('phpinfo', explode(',', ini_get('disable_functions')));
+ }
+
+ /**
+ * Method to get filter data from the model
+ *
+ * @param string $dataType Type of data to get safely
+ * @param bool $public If true no sensitive information will be removed
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ public function getSafeData(string $dataType, bool $public = true): array
+ {
+ if (isset($this->safeData[$dataType])) {
+ return $this->safeData[$dataType];
+ }
+
+ $methodName = 'get' . ucfirst($dataType);
+
+ if (!method_exists($this, $methodName)) {
+ return [];
+ }
+
+ $data = $this->$methodName($public);
+
+ $this->safeData[$dataType] = $this->cleanPrivateData($data, $dataType);
+
+ return $this->safeData[$dataType];
+ }
+
+ /**
+ * Method to get the PHP info
+ *
+ * @return string PHP info
+ *
+ * @since 1.6
+ */
+ public function &getPHPInfo(): string
+ {
+ if (!$this->phpinfoEnabled()) {
+ $this->php_info = Text::_('COM_ADMIN_PHPINFO_DISABLED');
+
+ return $this->php_info;
+ }
+
+ if (!\is_null($this->php_info)) {
+ return $this->php_info;
+ }
+
+ ob_start();
+ date_default_timezone_set('UTC');
+ phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES);
+ $phpInfo = ob_get_contents();
+ ob_end_clean();
+ preg_match_all('#]*>(.*)#siU', $phpInfo, $output);
+ $output = preg_replace('# ]*>#', '', $output[1][0]);
+ $output = preg_replace('#(\w),(\w)#', '\1, \2', $output);
+ $output = preg_replace('# #', '', $output);
+ $output = str_replace('', '', $output);
+ $output = preg_replace('#
(.*) #', '$1 ', $output);
+ $output = str_replace('
', '
', $output);
+ $output = str_replace('', '', $output);
+ $this->php_info = $output;
+
+ return $this->php_info;
+ }
+
+ /**
+ * Get phpinfo() output as array
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ public function getPhpInfoArray(): array
+ {
+ // Already cached
+ if (null !== $this->phpInfoArray) {
+ return $this->phpInfoArray;
+ }
+
+ $phpInfo = $this->getPHPInfo();
+
+ $this->phpInfoArray = $this->parsePhpInfo($phpInfo);
+
+ return $this->phpInfoArray;
+ }
+
+ /**
+ * Method to get a list of installed extensions
+ *
+ * @return array installed extensions
+ *
+ * @since 3.5
+ */
+ public function getExtensions(): array
+ {
+ $installed = [];
+ $db = Factory::getContainer()->get('DatabaseDriver');
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__extensions'));
+ $db->setQuery($query);
+
+ try {
+ $extensions = $db->loadObjectList();
+ } catch (\Exception $e) {
+ try {
+ Log::add(Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()),
+ 'warning'
+ );
+ }
+
+ return $installed;
+ }
+
+ if (empty($extensions)) {
+ return $installed;
+ }
+
+ foreach ($extensions as $extension) {
+ if (\strlen($extension->name) == 0) {
+ continue;
+ }
+
+ $installed[$extension->name] = [
+ 'name' => $extension->name,
+ 'type' => $extension->type,
+ 'state' => $extension->enabled ? Text::_('JENABLED') : Text::_('JDISABLED'),
+ 'author' => 'unknown',
+ 'version' => 'unknown',
+ 'creationDate' => 'unknown',
+ 'authorUrl' => 'unknown',
+ ];
+
+ $manifest = new Registry($extension->manifest_cache);
+
+ $extraData = [
+ 'author' => $manifest->get('author', ''),
+ 'version' => $manifest->get('version', ''),
+ 'creationDate' => $manifest->get('creationDate', ''),
+ 'authorUrl' => $manifest->get('authorUrl', '')
+ ];
+
+ $installed[$extension->name] = array_merge($installed[$extension->name], $extraData);
+ }
+
+ return $installed;
+ }
+
+ /**
+ * Method to get the directory states
+ *
+ * @param bool $public If true no information is going to be removed
+ *
+ * @return array States of directories
+ *
+ * @throws \Exception
+ * @since 1.6
+ */
+ public function getDirectory(bool $public = false): array
+ {
+ if (!empty($this->directories)) {
+ return $this->directories;
+ }
+
+ $this->directories = [];
+
+ $registry = Factory::getApplication()->getConfig();
+ $cparams = ComponentHelper::getParams('com_media');
+
+ $this->addDirectory('administrator/components', JPATH_ADMINISTRATOR . '/components');
+ $this->addDirectory('administrator/components/com_joomlaupdate', JPATH_ADMINISTRATOR . '/components/com_joomlaupdate');
+ $this->addDirectory('administrator/language', JPATH_ADMINISTRATOR . '/language');
+
+ // List all admin languages
+ $admin_langs = new \DirectoryIterator(JPATH_ADMINISTRATOR . '/language');
+
+ foreach ($admin_langs as $folder) {
+ if ($folder->isDot() || !$folder->isDir()) {
+ continue;
+ }
+
+ $this->addDirectory(
+ 'administrator/language/' . $folder->getFilename(),
+ JPATH_ADMINISTRATOR . '/language/' . $folder->getFilename()
+ );
+ }
+
+ // List all manifests folders
+ $manifests = new \DirectoryIterator(JPATH_ADMINISTRATOR . '/manifests');
+
+ foreach ($manifests as $folder) {
+ if ($folder->isDot() || !$folder->isDir()) {
+ continue;
+ }
+
+ $this->addDirectory(
+ 'administrator/manifests/' . $folder->getFilename(),
+ JPATH_ADMINISTRATOR . '/manifests/' . $folder->getFilename()
+ );
+ }
+
+ $this->addDirectory('administrator/modules', JPATH_ADMINISTRATOR . '/modules');
+ $this->addDirectory('administrator/templates', JPATH_THEMES);
+
+ $this->addDirectory('components', JPATH_SITE . '/components');
+
+ $this->addDirectory($cparams->get('image_path'), JPATH_SITE . '/' . $cparams->get('image_path'));
+
+ // List all images folders
+ $image_folders = new \DirectoryIterator(JPATH_SITE . '/' . $cparams->get('image_path'));
+
+ foreach ($image_folders as $folder) {
+ if ($folder->isDot() || !$folder->isDir()) {
+ continue;
+ }
+
+ $this->addDirectory(
+ 'images/' . $folder->getFilename(),
+ JPATH_SITE . '/' . $cparams->get('image_path') . '/' . $folder->getFilename()
+ );
+ }
+
+ $this->addDirectory('language', JPATH_SITE . '/language');
+
+ // List all site languages
+ $site_langs = new \DirectoryIterator(JPATH_SITE . '/language');
+
+ foreach ($site_langs as $folder) {
+ if ($folder->isDot() || !$folder->isDir()) {
+ continue;
+ }
+
+ $this->addDirectory('language/' . $folder->getFilename(), JPATH_SITE . '/language/' . $folder->getFilename());
+ }
+
+ $this->addDirectory('libraries', JPATH_LIBRARIES);
+
+ $this->addDirectory('media', JPATH_SITE . '/media');
+ $this->addDirectory('modules', JPATH_SITE . '/modules');
+ $this->addDirectory('plugins', JPATH_PLUGINS);
+
+ $plugin_groups = new \DirectoryIterator(JPATH_SITE . '/plugins');
+
+ foreach ($plugin_groups as $folder) {
+ if ($folder->isDot() || !$folder->isDir()) {
+ continue;
+ }
+
+ $this->addDirectory('plugins/' . $folder->getFilename(), JPATH_PLUGINS . '/' . $folder->getFilename());
+ }
+
+ $this->addDirectory('templates', JPATH_SITE . '/templates');
+ $this->addDirectory('configuration.php', JPATH_CONFIGURATION . '/configuration.php');
+
+ // Is there a cache path in configuration.php?
+ if ($cache_path = trim($registry->get('cache_path', ''))) {
+ // Frontend and backend use same directory for caching.
+ $this->addDirectory($cache_path, $cache_path, 'COM_ADMIN_CACHE_DIRECTORY');
+ } else {
+ $this->addDirectory('administrator/cache', JPATH_CACHE, 'COM_ADMIN_CACHE_DIRECTORY');
+ }
+
+ $this->addDirectory('media/cache', JPATH_ROOT . '/media/cache', 'COM_ADMIN_MEDIA_CACHE_DIRECTORY');
+
+ if ($public) {
+ $this->addDirectory(
+ 'log',
+ $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'),
+ 'COM_ADMIN_LOG_DIRECTORY'
+ );
+ $this->addDirectory(
+ 'tmp',
+ $registry->get('tmp_path', JPATH_ROOT . '/tmp'),
+ 'COM_ADMIN_TEMP_DIRECTORY'
+ );
+ } else {
+ $this->addDirectory(
+ $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'),
+ $registry->get('log_path', JPATH_ADMINISTRATOR . '/logs'),
+ 'COM_ADMIN_LOG_DIRECTORY'
+ );
+ $this->addDirectory(
+ $registry->get('tmp_path', JPATH_ROOT . '/tmp'),
+ $registry->get('tmp_path', JPATH_ROOT . '/tmp'),
+ 'COM_ADMIN_TEMP_DIRECTORY'
+ );
+ }
+
+ return $this->directories;
+ }
+
+ /**
+ * Method to add a directory
+ *
+ * @param string $name Directory Name
+ * @param string $path Directory path
+ * @param string $message Message
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ private function addDirectory(string $name, string $path, string $message = ''): void
+ {
+ $this->directories[$name] = ['writable' => is_writable($path), 'message' => $message,];
+ }
+
+ /**
+ * Method to get the editor
+ *
+ * @return string The default editor
+ *
+ * @note Has to be removed (it is present in the config...)
+ * @since 1.6
+ */
+ public function &getEditor(): string
+ {
+ if (!is_null($this->editor)) {
+ return $this->editor;
+ }
+
+ $this->editor = Factory::getApplication()->get('editor');
+
+ return $this->editor;
+ }
+
+ /**
+ * Parse phpinfo output into an array
+ * Source https://gist.github.com/sbmzhcn/6255314
+ *
+ * @param string $html Output of phpinfo()
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function parsePhpInfo(string $html): array
+ {
+ $html = strip_tags($html, '');
+ $html = preg_replace('/ ]*>([^<]+)<\/th>/', '\1 ', $html);
+ $html = preg_replace('/ ]*>([^<]+)<\/td>/', '\1 ', $html);
+ $t = preg_split('/(]*>[^<]+<\/h2>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $r = [];
+ $count = \count($t);
+ $p1 = '([^<]+)<\/info>';
+ $p2 = '/' . $p1 . '\s*' . $p1 . '\s*' . $p1 . '/';
+ $p3 = '/' . $p1 . '\s*' . $p1 . '/';
+
+ for ($i = 1; $i < $count; $i++) {
+ if (preg_match('/]*>([^<]+)<\/h2>/', $t[$i], $matches)) {
+ $name = trim($matches[1]);
+ $vals = explode("\n", $t[$i + 1]);
+
+ foreach ($vals as $val) {
+ // 3cols
+ if (preg_match($p2, $val, $matches)) {
+ $r[$name][trim($matches[1])] = [trim($matches[2]), trim($matches[3]),];
+ } elseif (preg_match($p3, $val, $matches)) {
+ // 2cols
+ $r[$name][trim($matches[1])] = trim($matches[2]);
+ }
+ }
+ }
+ }
+
+ return $r;
+ }
}
diff --git a/administrator/components/com_admin/src/Service/HTML/Directory.php b/administrator/components/com_admin/src/Service/HTML/Directory.php
index f5de97a6ca568..0bed0f675e2a9 100644
--- a/administrator/components/com_admin/src/Service/HTML/Directory.php
+++ b/administrator/components/com_admin/src/Service/HTML/Directory.php
@@ -1,4 +1,5 @@
' . Text::_('COM_ADMIN_WRITABLE') . '';
- }
-
- return '' . Text::_('COM_ADMIN_UNWRITABLE') . ' ';
- }
-
- /**
- * Method to generate a message for a directory
- *
- * @param string $dir the directory
- * @param boolean $message the message
- * @param boolean $visible is the $dir visible?
- *
- * @return string html code
- */
- public function message($dir, $message, $visible = true)
- {
- $output = $visible ? $dir : '';
-
- if (empty($message))
- {
- return $output;
- }
-
- return $output . ' ' . Text::_($message) . ' ';
- }
+ /**
+ * Method to generate a (un)writable message for directory
+ *
+ * @param boolean $writable is the directory writable?
+ *
+ * @return string html code
+ */
+ public function writable($writable)
+ {
+ if ($writable) {
+ return '' . Text::_('COM_ADMIN_WRITABLE') . ' ';
+ }
+
+ return '' . Text::_('COM_ADMIN_UNWRITABLE') . ' ';
+ }
+
+ /**
+ * Method to generate a message for a directory
+ *
+ * @param string $dir the directory
+ * @param boolean $message the message
+ * @param boolean $visible is the $dir visible?
+ *
+ * @return string html code
+ */
+ public function message($dir, $message, $visible = true)
+ {
+ $output = $visible ? $dir : '';
+
+ if (empty($message)) {
+ return $output;
+ }
+
+ return $output . ' ' . Text::_($message) . ' ';
+ }
}
diff --git a/administrator/components/com_admin/src/Service/HTML/PhpSetting.php b/administrator/components/com_admin/src/Service/HTML/PhpSetting.php
index 8d4a1cd602dcb..903c781d498ea 100644
--- a/administrator/components/com_admin/src/Service/HTML/PhpSetting.php
+++ b/administrator/components/com_admin/src/Service/HTML/PhpSetting.php
@@ -1,4 +1,5 @@
getModel();
- $this->helpSearch = $model->getHelpSearch();
- $this->page = $model->getPage();
- $this->toc = $model->getToc();
- $this->languageTag = $model->getLangTag();
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var HelpModel $model */
+ $model = $this->getModel();
+ $this->helpSearch = $model->getHelpSearch();
+ $this->page = $model->getPage();
+ $this->toc = $model->getToc();
+ $this->languageTag = $model->getLangTag();
- $this->addToolbar();
+ $this->addToolbar();
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * Setup the Toolbar
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar(): void
- {
- ToolbarHelper::title(Text::_('COM_ADMIN_HELP'), 'support help_header');
- }
+ /**
+ * Setup the Toolbar
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar(): void
+ {
+ ToolbarHelper::title(Text::_('COM_ADMIN_HELP'), 'support help_header');
+ }
}
diff --git a/administrator/components/com_admin/src/View/Sysinfo/HtmlView.php b/administrator/components/com_admin/src/View/Sysinfo/HtmlView.php
index 4e7dca1227313..0fadf0891c4d0 100644
--- a/administrator/components/com_admin/src/View/Sysinfo/HtmlView.php
+++ b/administrator/components/com_admin/src/View/Sysinfo/HtmlView.php
@@ -1,4 +1,5 @@
getCurrentUser()->authorise('core.admin'))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ // Access check.
+ if (!$this->getCurrentUser()->authorise('core.admin')) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- /** @var SysinfoModel $model */
- $model = $this->getModel();
- $this->phpSettings = $model->getPhpSettings();
- $this->config = $model->getConfig();
- $this->info = $model->getInfo();
- $this->phpInfo = $model->getPHPInfo();
- $this->directory = $model->getDirectory();
+ /** @var SysinfoModel $model */
+ $model = $this->getModel();
+ $this->phpSettings = $model->getPhpSettings();
+ $this->config = $model->getConfig();
+ $this->info = $model->getInfo();
+ $this->phpInfo = $model->getPHPInfo();
+ $this->directory = $model->getDirectory();
- $this->addToolbar();
+ $this->addToolbar();
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * Setup the Toolbar
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar(): void
- {
- ToolbarHelper::title(Text::_('COM_ADMIN_SYSTEM_INFORMATION'), 'info-circle systeminfo');
- ToolbarHelper::link(
- Route::_('index.php?option=com_admin&view=sysinfo&format=text&' . Session::getFormToken() . '=1'),
- 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_TEXT',
- 'download'
- );
- ToolbarHelper::link(
- Route::_('index.php?option=com_admin&view=sysinfo&format=json&' . Session::getFormToken() . '=1'),
- 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_JSON',
- 'download'
- );
- ToolbarHelper::help('Site_System_Information');
- }
+ /**
+ * Setup the Toolbar
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar(): void
+ {
+ ToolbarHelper::title(Text::_('COM_ADMIN_SYSTEM_INFORMATION'), 'info-circle systeminfo');
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_admin&view=sysinfo&format=text&' . Session::getFormToken() . '=1'),
+ 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_TEXT',
+ 'download'
+ );
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_admin&view=sysinfo&format=json&' . Session::getFormToken() . '=1'),
+ 'COM_ADMIN_DOWNLOAD_SYSTEM_INFORMATION_JSON',
+ 'download'
+ );
+ ToolbarHelper::help('Site_System_Information');
+ }
}
diff --git a/administrator/components/com_admin/src/View/Sysinfo/JsonView.php b/administrator/components/com_admin/src/View/Sysinfo/JsonView.php
index 116a0875c9bcb..b3d921509b363 100644
--- a/administrator/components/com_admin/src/View/Sysinfo/JsonView.php
+++ b/administrator/components/com_admin/src/View/Sysinfo/JsonView.php
@@ -1,4 +1,5 @@
authorise('core.admin'))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.5
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ // Access check.
+ if (!Factory::getUser()->authorise('core.admin')) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- header('MIME-Version: 1.0');
- header('Content-Disposition: attachment; filename="systeminfo-' . date('c') . '.json"');
- header('Content-Transfer-Encoding: binary');
+ header('MIME-Version: 1.0');
+ header('Content-Disposition: attachment; filename="systeminfo-' . date('c') . '.json"');
+ header('Content-Transfer-Encoding: binary');
- $data = $this->getLayoutData();
+ $data = $this->getLayoutData();
- echo json_encode($data, JSON_PRETTY_PRINT);
+ echo json_encode($data, JSON_PRETTY_PRINT);
- Factory::getApplication()->close();
- }
+ Factory::getApplication()->close();
+ }
- /**
- * Get the data for the view
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData(): array
- {
- /** @var SysinfoModel $model */
- $model = $this->getModel();
+ /**
+ * Get the data for the view
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData(): array
+ {
+ /** @var SysinfoModel $model */
+ $model = $this->getModel();
- return [
- 'info' => $model->getSafeData('info'),
- 'phpSettings' => $model->getSafeData('phpSettings'),
- 'config' => $model->getSafeData('config'),
- 'directories' => $model->getSafeData('directory', true),
- 'phpInfo' => $model->getSafeData('phpInfoArray'),
- 'extensions' => $model->getSafeData('extensions')
- ];
- }
+ return [
+ 'info' => $model->getSafeData('info'),
+ 'phpSettings' => $model->getSafeData('phpSettings'),
+ 'config' => $model->getSafeData('config'),
+ 'directories' => $model->getSafeData('directory', true),
+ 'phpInfo' => $model->getSafeData('phpInfoArray'),
+ 'extensions' => $model->getSafeData('extensions')
+ ];
+ }
}
diff --git a/administrator/components/com_admin/src/View/Sysinfo/TextView.php b/administrator/components/com_admin/src/View/Sysinfo/TextView.php
index 6da1078e7c417..ec4d1d579d68d 100644
--- a/administrator/components/com_admin/src/View/Sysinfo/TextView.php
+++ b/administrator/components/com_admin/src/View/Sysinfo/TextView.php
@@ -1,4 +1,5 @@
authorise('core.admin'))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- header('Content-Type: text/plain; charset=utf-8');
- header('Content-Description: File Transfer');
- header('Content-Disposition: attachment; filename="systeminfo-' . date('c') . '.txt"');
- header('Cache-Control: must-revalidate');
-
- $data = $this->getLayoutData();
-
- $lines = [];
-
- foreach ($data as $sectionName => $section)
- {
- $customRenderingMethod = 'render' . ucfirst($sectionName);
-
- if (method_exists($this, $customRenderingMethod))
- {
- $lines[] = $this->$customRenderingMethod($section['title'], $section['data']);
- }
- else
- {
- $lines[] = $this->renderSection($section['title'], $section['data']);
- }
- }
-
- echo str_replace(JPATH_ROOT, 'xxxxxx', implode("\n\n", $lines));
-
- Factory::getApplication()->close();
- }
-
- /**
- * Get the data for the view
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData(): array
- {
- /** @var SysinfoModel $model */
- $model = $this->getModel();
-
- return [
- 'info' => [
- 'title' => Text::_('COM_ADMIN_SYSTEM_INFORMATION', true),
- 'data' => $model->getSafeData('info')
- ],
- 'phpSettings' => [
- 'title' => Text::_('COM_ADMIN_PHP_SETTINGS', true),
- 'data' => $model->getSafeData('phpSettings')
- ],
- 'config' => [
- 'title' => Text::_('COM_ADMIN_CONFIGURATION_FILE', true),
- 'data' => $model->getSafeData('config')
- ],
- 'directories' => [
- 'title' => Text::_('COM_ADMIN_DIRECTORY_PERMISSIONS', true),
- 'data' => $model->getSafeData('directory', true)
- ],
- 'phpInfo' => [
- 'title' => Text::_('COM_ADMIN_PHP_INFORMATION', true),
- 'data' => $model->getSafeData('phpInfoArray')
- ],
- 'extensions' => [
- 'title' => Text::_('COM_ADMIN_EXTENSIONS', true),
- 'data' => $model->getSafeData('extensions')
- ]
- ];
- }
-
- /**
- * Render a section
- *
- * @param string $sectionName Name of the section to render
- * @param array $sectionData Data of the section to render
- * @param integer $level Depth level for indentation
- *
- * @return string
- *
- * @since 3.5
- */
- protected function renderSection(string $sectionName, array $sectionData, int $level = 0): string
- {
- $lines = [];
-
- $margin = ($level > 0) ? str_repeat("\t", $level) : null;
-
- $lines[] = $margin . '=============';
- $lines[] = $margin . $sectionName;
- $lines[] = $margin . '=============';
- $level++;
-
- foreach ($sectionData as $name => $value)
- {
- if (\is_array($value))
- {
- if ($name == 'Directive')
- {
- continue;
- }
-
- $lines[] = '';
- $lines[] = $this->renderSection($name, $value, $level);
- }
- else
- {
- if (\is_bool($value))
- {
- $value = $value ? 'true' : 'false';
- }
-
- if (\is_int($name) && ($name == 0 || $name == 1))
- {
- $name = ($name == 0 ? 'Local Value' : 'Master Value');
- }
-
- $lines[] = $margin . $name . ': ' . $value;
- }
- }
-
- return implode("\n", $lines);
- }
-
- /**
- * Specific rendering for directories
- *
- * @param string $sectionName Name of the section
- * @param array $sectionData Directories information
- * @param integer $level Starting level
- *
- * @return string
- *
- * @since 3.5
- */
- protected function renderDirectories(string $sectionName, array $sectionData, int $level = -1): string
- {
- foreach ($sectionData as $directory => $data)
- {
- $sectionData[$directory] = $data['writable'] ? ' writable' : ' NOT writable';
- }
-
- return $this->renderSection($sectionName, $sectionData, $level);
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return mixed A string if successful, otherwise an Error object.
+ *
+ * @since 3.5
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ // Access check.
+ if (!Factory::getUser()->authorise('core.admin')) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ header('Content-Type: text/plain; charset=utf-8');
+ header('Content-Description: File Transfer');
+ header('Content-Disposition: attachment; filename="systeminfo-' . date('c') . '.txt"');
+ header('Cache-Control: must-revalidate');
+
+ $data = $this->getLayoutData();
+
+ $lines = [];
+
+ foreach ($data as $sectionName => $section) {
+ $customRenderingMethod = 'render' . ucfirst($sectionName);
+
+ if (method_exists($this, $customRenderingMethod)) {
+ $lines[] = $this->$customRenderingMethod($section['title'], $section['data']);
+ } else {
+ $lines[] = $this->renderSection($section['title'], $section['data']);
+ }
+ }
+
+ echo str_replace(JPATH_ROOT, 'xxxxxx', implode("\n\n", $lines));
+
+ Factory::getApplication()->close();
+ }
+
+ /**
+ * Get the data for the view
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData(): array
+ {
+ /** @var SysinfoModel $model */
+ $model = $this->getModel();
+
+ return [
+ 'info' => [
+ 'title' => Text::_('COM_ADMIN_SYSTEM_INFORMATION', true),
+ 'data' => $model->getSafeData('info')
+ ],
+ 'phpSettings' => [
+ 'title' => Text::_('COM_ADMIN_PHP_SETTINGS', true),
+ 'data' => $model->getSafeData('phpSettings')
+ ],
+ 'config' => [
+ 'title' => Text::_('COM_ADMIN_CONFIGURATION_FILE', true),
+ 'data' => $model->getSafeData('config')
+ ],
+ 'directories' => [
+ 'title' => Text::_('COM_ADMIN_DIRECTORY_PERMISSIONS', true),
+ 'data' => $model->getSafeData('directory', true)
+ ],
+ 'phpInfo' => [
+ 'title' => Text::_('COM_ADMIN_PHP_INFORMATION', true),
+ 'data' => $model->getSafeData('phpInfoArray')
+ ],
+ 'extensions' => [
+ 'title' => Text::_('COM_ADMIN_EXTENSIONS', true),
+ 'data' => $model->getSafeData('extensions')
+ ]
+ ];
+ }
+
+ /**
+ * Render a section
+ *
+ * @param string $sectionName Name of the section to render
+ * @param array $sectionData Data of the section to render
+ * @param integer $level Depth level for indentation
+ *
+ * @return string
+ *
+ * @since 3.5
+ */
+ protected function renderSection(string $sectionName, array $sectionData, int $level = 0): string
+ {
+ $lines = [];
+
+ $margin = ($level > 0) ? str_repeat("\t", $level) : null;
+
+ $lines[] = $margin . '=============';
+ $lines[] = $margin . $sectionName;
+ $lines[] = $margin . '=============';
+ $level++;
+
+ foreach ($sectionData as $name => $value) {
+ if (\is_array($value)) {
+ if ($name == 'Directive') {
+ continue;
+ }
+
+ $lines[] = '';
+ $lines[] = $this->renderSection($name, $value, $level);
+ } else {
+ if (\is_bool($value)) {
+ $value = $value ? 'true' : 'false';
+ }
+
+ if (\is_int($name) && ($name == 0 || $name == 1)) {
+ $name = ($name == 0 ? 'Local Value' : 'Master Value');
+ }
+
+ $lines[] = $margin . $name . ': ' . $value;
+ }
+ }
+
+ return implode("\n", $lines);
+ }
+
+ /**
+ * Specific rendering for directories
+ *
+ * @param string $sectionName Name of the section
+ * @param array $sectionData Directories information
+ * @param integer $level Starting level
+ *
+ * @return string
+ *
+ * @since 3.5
+ */
+ protected function renderDirectories(string $sectionName, array $sectionData, int $level = -1): string
+ {
+ foreach ($sectionData as $directory => $data) {
+ $sectionData[$directory] = $data['writable'] ? ' writable' : ' NOT writable';
+ }
+
+ return $this->renderSection($sectionName, $sectionData, $level);
+ }
}
diff --git a/administrator/components/com_admin/tmpl/help/default.php b/administrator/components/com_admin/tmpl/help/default.php
index 1cdac79740241..1cfe5e816f6b3 100644
--- a/administrator/components/com_admin/tmpl/help/default.php
+++ b/administrator/components/com_admin/tmpl/help/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_admin/tmpl/help/langforum.php b/administrator/components/com_admin/tmpl/help/langforum.php
index 3e4b0782978ba..979c726533351 100644
--- a/administrator/components/com_admin/tmpl/help/langforum.php
+++ b/administrator/components/com_admin/tmpl/help/langforum.php
@@ -1,4 +1,5 @@
- 'site', 'recall' => true, 'breakpoint' => 768]); ?>
+ 'site', 'recall' => true, 'breakpoint' => 768]); ?>
-
- loadTemplate('system'); ?>
-
+
+ loadTemplate('system'); ?>
+
-
- loadTemplate('phpsettings'); ?>
-
+
+ loadTemplate('phpsettings'); ?>
+
-
- loadTemplate('config'); ?>
-
+
+ loadTemplate('config'); ?>
+
-
- loadTemplate('directory'); ?>
-
+
+ loadTemplate('directory'); ?>
+
-
- loadTemplate('phpinfo'); ?>
-
+
+ loadTemplate('phpinfo'); ?>
+
-
+
diff --git a/administrator/components/com_admin/tmpl/sysinfo/default_config.php b/administrator/components/com_admin/tmpl/sysinfo/default_config.php
index 49e3f14b38fec..9cc7f0d5dcba4 100644
--- a/administrator/components/com_admin/tmpl/sysinfo/default_config.php
+++ b/administrator/components/com_admin/tmpl/sysinfo/default_config.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- config as $key => $value) : ?>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ config as $key => $value) : ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_admin/tmpl/sysinfo/default_directory.php b/administrator/components/com_admin/tmpl/sysinfo/default_directory.php
index a18fd59e19916..135c16eabc6e3 100644
--- a/administrator/components/com_admin/tmpl/sysinfo/default_directory.php
+++ b/administrator/components/com_admin/tmpl/sysinfo/default_directory.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- directory as $dir => $info) : ?>
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ directory as $dir => $info) : ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_admin/tmpl/sysinfo/default_phpinfo.php b/administrator/components/com_admin/tmpl/sysinfo/default_phpinfo.php
index b4c8a68faca16..18e0fb1f60155 100644
--- a/administrator/components/com_admin/tmpl/sysinfo/default_phpinfo.php
+++ b/administrator/components/com_admin/tmpl/sysinfo/default_phpinfo.php
@@ -1,4 +1,5 @@
- phpInfo; ?>
+ phpInfo; ?>
diff --git a/administrator/components/com_admin/tmpl/sysinfo/default_phpsettings.php b/administrator/components/com_admin/tmpl/sysinfo/default_phpsettings.php
index 3ea85c31b4024..078555d28d371 100644
--- a/administrator/components/com_admin/tmpl/sysinfo/default_phpsettings.php
+++ b/administrator/components/com_admin/tmpl/sysinfo/default_phpsettings.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- phpSettings['upload_max_filesize']); ?>
-
-
-
-
-
-
-
- phpSettings['post_max_size']); ?>
-
-
-
-
-
-
-
- phpSettings['memory_limit']); ?>
-
-
-
-
-
-
-
- phpSettings['open_basedir']); ?>
-
-
-
-
-
-
-
- phpSettings['display_errors']); ?>
-
-
-
-
-
-
-
- phpSettings['short_open_tag']); ?>
-
-
-
-
-
-
-
- phpSettings['file_uploads']); ?>
-
-
-
-
-
-
-
- phpSettings['output_buffering']); ?>
-
-
-
-
-
-
-
- phpSettings['session.save_path']); ?>
-
-
-
-
-
-
-
- phpSettings['session.auto_start']; ?>
-
-
-
-
-
-
-
- phpSettings['xml']); ?>
-
-
-
-
-
-
-
- phpSettings['zlib']); ?>
-
-
-
-
-
-
-
- phpSettings['zip']); ?>
-
-
-
-
-
-
-
- phpSettings['disable_functions']); ?>
-
-
-
-
-
-
-
- phpSettings['fileinfo']); ?>
-
-
-
-
-
-
-
- phpSettings['mbstring']); ?>
-
-
-
-
-
-
-
- phpSettings['gd']); ?>
-
-
-
-
-
-
-
- phpSettings['iconv']); ?>
-
-
-
-
-
-
-
- phpSettings['intl']); ?>
-
-
-
-
-
-
-
- phpSettings['max_input_vars']; ?>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ phpSettings['upload_max_filesize']); ?>
+
+
+
+
+
+
+
+ phpSettings['post_max_size']); ?>
+
+
+
+
+
+
+
+ phpSettings['memory_limit']); ?>
+
+
+
+
+
+
+
+ phpSettings['open_basedir']); ?>
+
+
+
+
+
+
+
+ phpSettings['display_errors']); ?>
+
+
+
+
+
+
+
+ phpSettings['short_open_tag']); ?>
+
+
+
+
+
+
+
+ phpSettings['file_uploads']); ?>
+
+
+
+
+
+
+
+ phpSettings['output_buffering']); ?>
+
+
+
+
+
+
+
+ phpSettings['session.save_path']); ?>
+
+
+
+
+
+
+
+ phpSettings['session.auto_start']; ?>
+
+
+
+
+
+
+
+ phpSettings['xml']); ?>
+
+
+
+
+
+
+
+ phpSettings['zlib']); ?>
+
+
+
+
+
+
+
+ phpSettings['zip']); ?>
+
+
+
+
+
+
+
+ phpSettings['disable_functions']); ?>
+
+
+
+
+
+
+
+ phpSettings['fileinfo']); ?>
+
+
+
+
+
+
+
+ phpSettings['mbstring']); ?>
+
+
+
+
+
+
+
+ phpSettings['gd']); ?>
+
+
+
+
+
+
+
+ phpSettings['iconv']); ?>
+
+
+
+
+
+
+
+ phpSettings['intl']); ?>
+
+
+
+
+
+
+
+ phpSettings['max_input_vars']; ?>
+
+
+
+
diff --git a/administrator/components/com_admin/tmpl/sysinfo/default_system.php b/administrator/components/com_admin/tmpl/sysinfo/default_system.php
index 97ddce3adf5b6..0891b5c2ef82b 100644
--- a/administrator/components/com_admin/tmpl/sysinfo/default_system.php
+++ b/administrator/components/com_admin/tmpl/sysinfo/default_system.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- info['php']; ?>
-
-
-
-
-
-
-
- info['dbserver']; ?>
-
-
-
-
-
-
-
- info['dbversion']; ?>
-
-
-
-
-
-
-
- info['dbcollation']; ?>
-
-
-
-
-
-
-
- info['dbconnectioncollation']; ?>
-
-
-
-
-
-
-
- info['dbconnectionencryption'] ?: Text::_('JNONE'); ?>
-
-
-
-
-
-
-
- info['dbconnencryptsupported'] ? Text::_('JYES') : Text::_('JNO'); ?>
-
-
-
-
-
-
-
- info['phpversion']; ?>
-
-
-
-
-
-
-
- info['server']); ?>
-
-
-
-
-
-
-
- info['sapi_name']; ?>
-
-
-
-
-
-
-
- info['version']; ?>
-
-
-
-
-
-
-
- info['useragent'], ENT_COMPAT, 'UTF-8'); ?>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ info['php']; ?>
+
+
+
+
+
+
+
+ info['dbserver']; ?>
+
+
+
+
+
+
+
+ info['dbversion']; ?>
+
+
+
+
+
+
+
+ info['dbcollation']; ?>
+
+
+
+
+
+
+
+ info['dbconnectioncollation']; ?>
+
+
+
+
+
+
+
+ info['dbconnectionencryption'] ?: Text::_('JNONE'); ?>
+
+
+
+
+
+
+
+ info['dbconnencryptsupported'] ? Text::_('JYES') : Text::_('JNO'); ?>
+
+
+
+
+
+
+
+ info['phpversion']; ?>
+
+
+
+
+
+
+
+ info['server']); ?>
+
+
+
+
+
+
+
+ info['sapi_name']; ?>
+
+
+
+
+
+
+
+ info['version']; ?>
+
+
+
+
+
+
+
+ info['useragent'], ENT_COMPAT, 'UTF-8'); ?>
+
+
+
+
diff --git a/administrator/components/com_ajax/ajax.php b/administrator/components/com_ajax/ajax.php
index 40e35cd18f9d4..37e61ad93a326 100644
--- a/administrator/components/com_ajax/ajax.php
+++ b/administrator/components/com_ajax/ajax.php
@@ -1,4 +1,5 @@
filterForm) && !empty($data['view']->filterForm))
-{
- // Checks if a selector (e.g. client_id) exists.
- if ($selectorField = $data['view']->filterForm->getField($selectorFieldName))
- {
- $showSelector = $selectorField->getAttribute('filtermode', '') === 'selector' ? true : $showSelector;
-
- // Checks if a selector should be shown in the current layout.
- if (isset($data['view']->layout))
- {
- $showSelector = $selectorField->getAttribute('layout', 'default') != $data['view']->layout ? false : $showSelector;
- }
-
- // Unset the selector field from active filters group.
- unset($data['view']->activeFilters[$selectorFieldName]);
- }
-
- // Checks if the filters button should exist.
- $filters = $data['view']->filterForm->getGroup('filter');
- $showFilterButton = isset($filters['filter_search']) && count($filters) === 1 ? false : true;
-
- // Checks if it should show the be hidden.
- $hideActiveFilters = empty($data['view']->activeFilters);
-
- // Check if the no results message should appear.
- if (isset($data['view']->total) && (int) $data['view']->total === 0)
- {
- $noResults = $data['view']->filterForm->getFieldAttribute('search', 'noresults', '', 'filter');
- if (!empty($noResults))
- {
- $noResultsText = Text::_($noResults);
- }
- }
+if (isset($data['view']->filterForm) && !empty($data['view']->filterForm)) {
+ // Checks if a selector (e.g. client_id) exists.
+ if ($selectorField = $data['view']->filterForm->getField($selectorFieldName)) {
+ $showSelector = $selectorField->getAttribute('filtermode', '') === 'selector' ? true : $showSelector;
+
+ // Checks if a selector should be shown in the current layout.
+ if (isset($data['view']->layout)) {
+ $showSelector = $selectorField->getAttribute('layout', 'default') != $data['view']->layout ? false : $showSelector;
+ }
+
+ // Unset the selector field from active filters group.
+ unset($data['view']->activeFilters[$selectorFieldName]);
+ }
+
+ // Checks if the filters button should exist.
+ $filters = $data['view']->filterForm->getGroup('filter');
+ $showFilterButton = isset($filters['filter_search']) && count($filters) === 1 ? false : true;
+
+ // Checks if it should show the be hidden.
+ $hideActiveFilters = empty($data['view']->activeFilters);
+
+ // Check if the no results message should appear.
+ if (isset($data['view']->total) && (int) $data['view']->total === 0) {
+ $noResults = $data['view']->filterForm->getFieldAttribute('search', 'noresults', '', 'filter');
+ if (!empty($noResults)) {
+ $noResultsText = Text::_($noResults);
+ }
+ }
}
// Set some basic options.
$customOptions = array(
- 'filtersHidden' => isset($data['options']['filtersHidden']) && $data['options']['filtersHidden'] ? $data['options']['filtersHidden'] : $hideActiveFilters,
- 'filterButton' => isset($data['options']['filterButton']) && $data['options']['filterButton'] ? $data['options']['filterButton'] : $showFilterButton,
- 'defaultLimit' => $data['options']['defaultLimit'] ?? Factory::getApplication()->get('list_limit', 20),
- 'searchFieldSelector' => '#filter_search',
- 'selectorFieldName' => $selectorFieldName,
- 'showSelector' => $showSelector,
- 'orderFieldSelector' => '#list_fullordering',
- 'showNoResults' => !empty($noResultsText),
- 'noResultsText' => !empty($noResultsText) ? $noResultsText : '',
- 'formSelector' => !empty($data['options']['formSelector']) ? $data['options']['formSelector'] : '#adminForm',
+ 'filtersHidden' => isset($data['options']['filtersHidden']) && $data['options']['filtersHidden'] ? $data['options']['filtersHidden'] : $hideActiveFilters,
+ 'filterButton' => isset($data['options']['filterButton']) && $data['options']['filterButton'] ? $data['options']['filterButton'] : $showFilterButton,
+ 'defaultLimit' => $data['options']['defaultLimit'] ?? Factory::getApplication()->get('list_limit', 20),
+ 'searchFieldSelector' => '#filter_search',
+ 'selectorFieldName' => $selectorFieldName,
+ 'showSelector' => $showSelector,
+ 'orderFieldSelector' => '#list_fullordering',
+ 'showNoResults' => !empty($noResultsText),
+ 'noResultsText' => !empty($noResultsText) ? $noResultsText : '',
+ 'formSelector' => !empty($data['options']['formSelector']) ? $data['options']['formSelector'] : '#adminForm',
);
// Merge custom options in the options array.
@@ -85,44 +81,44 @@
HTMLHelper::_('searchtools.form', $data['options']['formSelector'], $data['options']);
?>
- sublayout('noitems', $data); ?>
+ sublayout('noitems', $data); ?>
diff --git a/administrator/components/com_associations/services/provider.php b/administrator/components/com_associations/services/provider.php
index 4ad47fec2648b..071fec0053bf2 100644
--- a/administrator/components/com_associations/services/provider.php
+++ b/administrator/components/com_associations/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Associations'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Associations'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Associations'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Associations'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_associations/src/Controller/AssociationController.php b/administrator/components/com_associations/src/Controller/AssociationController.php
index e3e7f12860480..2590760b931de 100644
--- a/administrator/components/com_associations/src/Controller/AssociationController.php
+++ b/administrator/components/com_associations/src/Controller/AssociationController.php
@@ -1,4 +1,5 @@
input->get('itemtype', '', 'string'), 2);
+ /**
+ * Method to edit an existing record.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key
+ * (sometimes required to avoid router collisions).
+ *
+ * @return FormController|boolean True if access level check and checkout passes, false otherwise.
+ *
+ * @since 3.7.0
+ */
+ public function edit($key = null, $urlVar = null)
+ {
+ list($extensionName, $typeName) = explode('.', $this->input->get('itemtype', '', 'string'), 2);
- $id = $this->input->get('id', 0, 'int');
+ $id = $this->input->get('id', 0, 'int');
- // Check if reference item can be edited.
- if (!AssociationsHelper::allowEdit($extensionName, $typeName, $id))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
- $this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
+ // Check if reference item can be edited.
+ if (!AssociationsHelper::allowEdit($extensionName, $typeName, $id)) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
+ $this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
- return false;
- }
+ return false;
+ }
- return parent::display();
- }
+ return parent::display();
+ }
- /**
- * Method for canceling the edit action
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function cancel($key = null)
- {
- $this->checkToken();
+ /**
+ * Method for canceling the edit action
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function cancel($key = null)
+ {
+ $this->checkToken();
- list($extensionName, $typeName) = explode('.', $this->input->get('itemtype', '', 'string'), 2);
+ list($extensionName, $typeName) = explode('.', $this->input->get('itemtype', '', 'string'), 2);
- // Only check in, if component item type allows to check out.
- if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName))
- {
- $ids = array();
- $targetId = $this->input->get('target-id', '', 'string');
+ // Only check in, if component item type allows to check out.
+ if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName)) {
+ $ids = array();
+ $targetId = $this->input->get('target-id', '', 'string');
- if ($targetId !== '')
- {
- $ids = array_unique(explode(',', $targetId));
- }
+ if ($targetId !== '') {
+ $ids = array_unique(explode(',', $targetId));
+ }
- $ids[] = $this->input->get('id', 0, 'int');
+ $ids[] = $this->input->get('id', 0, 'int');
- foreach ($ids as $key => $id)
- {
- AssociationsHelper::getItem($extensionName, $typeName, $id)->checkIn();
- }
- }
+ foreach ($ids as $key => $id) {
+ AssociationsHelper::getItem($extensionName, $typeName, $id)->checkIn();
+ }
+ }
- $this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
- }
+ $this->setRedirect(Route::_('index.php?option=com_associations&view=associations', false));
+ }
}
diff --git a/administrator/components/com_associations/src/Controller/AssociationsController.php b/administrator/components/com_associations/src/Controller/AssociationsController.php
index 22bf8c7f8bae3..f5b3916ffd1aa 100644
--- a/administrator/components/com_associations/src/Controller/AssociationsController.php
+++ b/administrator/components/com_associations/src/Controller/AssociationsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to purge the associations table.
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function purge()
- {
- $this->checkToken();
-
- $this->getModel('associations')->purge();
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
- }
-
- /**
- * Method to delete the orphans from the associations table.
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function clean()
- {
- $this->checkToken();
-
- $this->getModel('associations')->clean();
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
- }
-
- /**
- * Method to check in an item from the association item overview.
- *
- * @return void
- *
- * @since 3.7.1
- */
- public function checkin()
- {
- // Set the redirect so we can just stop processing when we find a condition we can't process
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
-
- // Figure out if the item supports checking and check it in
- list($extensionName, $typeName) = explode('.', $this->input->get('itemtype'));
-
- $extension = AssociationsHelper::getSupportedExtension($extensionName);
- $types = $extension->get('types');
-
- if (!\array_key_exists($typeName, $types))
- {
- return;
- }
-
- if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName) === false)
- {
- // How on earth we came to that point, eject internet
- return;
- }
-
- $cid = (array) $this->input->get('cid', array(), 'int');
-
- if (empty($cid))
- {
- // Seems we don't have an id to work with.
- return;
- }
-
- // We know the first element is the one we need because we don't allow multi selection of rows
- $id = $cid[0];
-
- if ($id === 0)
- {
- // Seems we don't have an id to work with.
- return;
- }
-
- if (AssociationsHelper::canCheckinItem($extensionName, $typeName, $id) === true)
- {
- $item = AssociationsHelper::getItem($extensionName, $typeName, $id);
-
- $item->checkIn($id);
-
- return;
- }
-
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list),
- Text::_('COM_ASSOCIATIONS_YOU_ARE_NOT_ALLOWED_TO_CHECKIN_THIS_ITEM')
- );
- }
+ /**
+ * The URL view list variable.
+ *
+ * @var string
+ *
+ * @since 3.7.0
+ */
+ protected $view_list = 'associations';
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel|boolean
+ *
+ * @since 3.7.0
+ */
+ public function getModel($name = 'Associations', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to purge the associations table.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function purge()
+ {
+ $this->checkToken();
+
+ $this->getModel('associations')->purge();
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
+ }
+
+ /**
+ * Method to delete the orphans from the associations table.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function clean()
+ {
+ $this->checkToken();
+
+ $this->getModel('associations')->clean();
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
+ }
+
+ /**
+ * Method to check in an item from the association item overview.
+ *
+ * @return void
+ *
+ * @since 3.7.1
+ */
+ public function checkin()
+ {
+ // Set the redirect so we can just stop processing when we find a condition we can't process
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
+
+ // Figure out if the item supports checking and check it in
+ list($extensionName, $typeName) = explode('.', $this->input->get('itemtype'));
+
+ $extension = AssociationsHelper::getSupportedExtension($extensionName);
+ $types = $extension->get('types');
+
+ if (!\array_key_exists($typeName, $types)) {
+ return;
+ }
+
+ if (AssociationsHelper::typeSupportsCheckout($extensionName, $typeName) === false) {
+ // How on earth we came to that point, eject internet
+ return;
+ }
+
+ $cid = (array) $this->input->get('cid', array(), 'int');
+
+ if (empty($cid)) {
+ // Seems we don't have an id to work with.
+ return;
+ }
+
+ // We know the first element is the one we need because we don't allow multi selection of rows
+ $id = $cid[0];
+
+ if ($id === 0) {
+ // Seems we don't have an id to work with.
+ return;
+ }
+
+ if (AssociationsHelper::canCheckinItem($extensionName, $typeName, $id) === true) {
+ $item = AssociationsHelper::getItem($extensionName, $typeName, $id);
+
+ $item->checkIn($id);
+
+ return;
+ }
+
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list),
+ Text::_('COM_ASSOCIATIONS_YOU_ARE_NOT_ALLOWED_TO_CHECKIN_THIS_ITEM')
+ );
+ }
}
diff --git a/administrator/components/com_associations/src/Controller/DisplayController.php b/administrator/components/com_associations/src/Controller/DisplayController.php
index e2a48320e1d91..d02944ad6d582 100644
--- a/administrator/components/com_associations/src/Controller/DisplayController.php
+++ b/administrator/components/com_associations/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('itemtype', '', 'string');
-
- if ($itemType !== '')
- {
- list($extensionName, $typeName) = explode('.', $itemType);
-
- if (!AssociationsHelper::hasSupport($extensionName))
- {
- throw new \Exception(
- Text::sprintf('COM_ASSOCIATIONS_COMPONENT_NOT_SUPPORTED', $this->app->getLanguage()->_($extensionName)),
- 404
- );
- }
-
- if (!$this->app->getIdentity()->authorise('core.manage', $extensionName))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
- }
+ /**
+ * Method to check component access permission
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ *
+ * @throws \Exception|NotAllowed
+ */
+ protected function checkAccess()
+ {
+ parent::checkAccess();
+
+ // Check if user has permission to access the component item type.
+ $itemType = $this->input->get('itemtype', '', 'string');
+
+ if ($itemType !== '') {
+ list($extensionName, $typeName) = explode('.', $itemType);
+
+ if (!AssociationsHelper::hasSupport($extensionName)) {
+ throw new \Exception(
+ Text::sprintf('COM_ASSOCIATIONS_COMPONENT_NOT_SUPPORTED', $this->app->getLanguage()->_($extensionName)),
+ 404
+ );
+ }
+
+ if (!$this->app->getIdentity()->authorise('core.manage', $extensionName)) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+ }
}
diff --git a/administrator/components/com_associations/src/Field/ItemlanguageField.php b/administrator/components/com_associations/src/Field/ItemlanguageField.php
index 0d63fe5951d4d..cd081551b606b 100644
--- a/administrator/components/com_associations/src/Field/ItemlanguageField.php
+++ b/administrator/components/com_associations/src/Field/ItemlanguageField.php
@@ -1,4 +1,5 @@
input;
-
- list($extensionName, $typeName) = explode('.', $input->get('itemtype', '', 'string'), 2);
-
- // Get the extension specific helper method
- $helper = AssociationsHelper::getExtensionHelper($extensionName);
-
- $languageField = $helper->getTypeFieldName($typeName, 'language');
- $referenceId = $input->get('id', 0, 'int');
- $reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
- $referenceLang = $reference[$languageField];
-
- // Get item associations given ID and item type
- $associations = AssociationsHelper::getAssociationList($extensionName, $typeName, $referenceId);
-
- // Check if user can create items in this component item type.
- $canCreate = AssociationsHelper::allowAdd($extensionName, $typeName);
-
- // Gets existing languages.
- $existingLanguages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- $options = array();
-
- // Each option has the format "|", example: "en-GB|1"
- foreach ($existingLanguages as $langCode => $language)
- {
- // If language code is equal to reference language we don't need it.
- if ($language->lang_code == $referenceLang)
- {
- continue;
- }
-
- $options[$langCode] = new \stdClass;
- $options[$langCode]->text = $language->title;
-
- // If association exists in this language.
- if (isset($associations[$language->lang_code]))
- {
- $itemId = (int) $associations[$language->lang_code]['id'];
- $options[$langCode]->value = $language->lang_code . ':' . $itemId . ':edit';
-
- // Check if user does have permission to edit the associated item.
- $canEdit = AssociationsHelper::allowEdit($extensionName, $typeName, $itemId);
-
- // Check if item can be checked out
- $canCheckout = AssociationsHelper::canCheckinItem($extensionName, $typeName, $itemId);
-
- // Disable language if user is not allowed to edit the item associated to it.
- $options[$langCode]->disable = !($canEdit && $canCheckout);
- }
- else
- {
- // New item, id = 0 and disabled if user is not allowed to create new items.
- $options[$langCode]->value = $language->lang_code . ':0:add';
-
- // Disable language if user is not allowed to create items.
- $options[$langCode]->disable = !$canCreate;
- }
- }
-
- return array_merge(parent::getOptions(), $options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'Itemlanguage';
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $input = Factory::getApplication()->input;
+
+ list($extensionName, $typeName) = explode('.', $input->get('itemtype', '', 'string'), 2);
+
+ // Get the extension specific helper method
+ $helper = AssociationsHelper::getExtensionHelper($extensionName);
+
+ $languageField = $helper->getTypeFieldName($typeName, 'language');
+ $referenceId = $input->get('id', 0, 'int');
+ $reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
+ $referenceLang = $reference[$languageField];
+
+ // Get item associations given ID and item type
+ $associations = AssociationsHelper::getAssociationList($extensionName, $typeName, $referenceId);
+
+ // Check if user can create items in this component item type.
+ $canCreate = AssociationsHelper::allowAdd($extensionName, $typeName);
+
+ // Gets existing languages.
+ $existingLanguages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ $options = array();
+
+ // Each option has the format "|", example: "en-GB|1"
+ foreach ($existingLanguages as $langCode => $language) {
+ // If language code is equal to reference language we don't need it.
+ if ($language->lang_code == $referenceLang) {
+ continue;
+ }
+
+ $options[$langCode] = new \stdClass();
+ $options[$langCode]->text = $language->title;
+
+ // If association exists in this language.
+ if (isset($associations[$language->lang_code])) {
+ $itemId = (int) $associations[$language->lang_code]['id'];
+ $options[$langCode]->value = $language->lang_code . ':' . $itemId . ':edit';
+
+ // Check if user does have permission to edit the associated item.
+ $canEdit = AssociationsHelper::allowEdit($extensionName, $typeName, $itemId);
+
+ // Check if item can be checked out
+ $canCheckout = AssociationsHelper::canCheckinItem($extensionName, $typeName, $itemId);
+
+ // Disable language if user is not allowed to edit the item associated to it.
+ $options[$langCode]->disable = !($canEdit && $canCheckout);
+ } else {
+ // New item, id = 0 and disabled if user is not allowed to create new items.
+ $options[$langCode]->value = $language->lang_code . ':0:add';
+
+ // Disable language if user is not allowed to create items.
+ $options[$langCode]->disable = !$canCreate;
+ }
+ }
+
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_associations/src/Field/ItemtypeField.php b/administrator/components/com_associations/src/Field/ItemtypeField.php
index c31c081ead930..7c6dd51dcfc37 100644
--- a/administrator/components/com_associations/src/Field/ItemtypeField.php
+++ b/administrator/components/com_associations/src/Field/ItemtypeField.php
@@ -1,4 +1,5 @@
get('associationssupport') === true)
- {
- foreach ($extension->get('types') as $type)
- {
- $context = $extension->get('component') . '.' . $type->get('name');
- $options[$extension->get('title')][] = HTMLHelper::_('select.option', $context, $type->get('title'));
- }
- }
- }
-
- // Sort by alpha order.
- uksort($options, 'strnatcmp');
-
- // Add options to parent array.
- return array_merge(parent::getGroups(), $options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ *
+ * @since 3.7.0
+ */
+ protected $type = 'Itemtype';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 3.7.0
+ *
+ * @throws \UnexpectedValueException
+ */
+ protected function getGroups()
+ {
+ $options = array();
+ $extensions = AssociationsHelper::getSupportedExtensions();
+
+ foreach ($extensions as $extension) {
+ if ($extension->get('associationssupport') === true) {
+ foreach ($extension->get('types') as $type) {
+ $context = $extension->get('component') . '.' . $type->get('name');
+ $options[$extension->get('title')][] = HTMLHelper::_('select.option', $context, $type->get('title'));
+ }
+ }
+ }
+
+ // Sort by alpha order.
+ uksort($options, 'strnatcmp');
+
+ // Add options to parent array.
+ return array_merge(parent::getGroups(), $options);
+ }
}
diff --git a/administrator/components/com_associations/src/Field/Modal/AssociationField.php b/administrator/components/com_associations/src/Field/Modal/AssociationField.php
index f05d89046b30d..3a173aabd8c4a 100644
--- a/administrator/components/com_associations/src/Field/Modal/AssociationField.php
+++ b/administrator/components/com_associations/src/Field/Modal/AssociationField.php
@@ -1,4 +1,5 @@
value ?: '';
-
- $doc = Factory::getApplication()->getDocument();
- $wa = $doc->getWebAssetManager();
-
- $doc->addScriptOptions('admin_associations_modal', ['itemId' => $value]);
- $wa->useScript('com_associations.admin-associations-modal');
-
- // Setup variables for display.
- $html = array();
-
- $linkAssociations = 'index.php?option=com_associations&view=associations&layout=modal&tmpl=component'
- . '&forcedItemType=' . Factory::getApplication()->input->get('itemtype', '', 'string') . '&function=jSelectAssociation_' . $this->id;
-
- $linkAssociations .= "&forcedLanguage=' + document.getElementById('target-association').getAttribute('data-language') + '";
-
- $urlSelect = $linkAssociations . '&' . Session::getFormToken() . '=1';
-
- // Select custom association button
- $html[] = ''
- . ' '
- . ' '
- . ' ';
-
- // Clear association button
- $html[] = ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
-
- $html[] = ' ';
-
- // Select custom association modal
- $html[] = HTMLHelper::_(
- 'bootstrap.renderModal',
- 'associationSelect' . $this->id . 'Modal',
- array(
- 'title' => Text::_('COM_ASSOCIATIONS_SELECT_TARGET'),
- 'backdrop' => 'static',
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
-
- return implode("\n", $html);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'Modal_Association';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getInput()
+ {
+ // @todo USE Layouts here!!!
+ // The active item id field.
+ $value = (int) $this->value ?: '';
+
+ $doc = Factory::getApplication()->getDocument();
+ $wa = $doc->getWebAssetManager();
+
+ $doc->addScriptOptions('admin_associations_modal', ['itemId' => $value]);
+ $wa->useScript('com_associations.admin-associations-modal');
+
+ // Setup variables for display.
+ $html = array();
+
+ $linkAssociations = 'index.php?option=com_associations&view=associations&layout=modal&tmpl=component'
+ . '&forcedItemType=' . Factory::getApplication()->input->get('itemtype', '', 'string') . '&function=jSelectAssociation_' . $this->id;
+
+ $linkAssociations .= "&forcedLanguage=' + document.getElementById('target-association').getAttribute('data-language') + '";
+
+ $urlSelect = $linkAssociations . '&' . Session::getFormToken() . '=1';
+
+ // Select custom association button
+ $html[] = ''
+ . ' '
+ . ' '
+ . ' ';
+
+ // Clear association button
+ $html[] = ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+
+ $html[] = ' ';
+
+ // Select custom association modal
+ $html[] = HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'associationSelect' . $this->id . 'Modal',
+ array(
+ 'title' => Text::_('COM_ASSOCIATIONS_SELECT_TARGET'),
+ 'backdrop' => 'static',
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+
+ return implode("\n", $html);
+ }
}
diff --git a/administrator/components/com_associations/src/Helper/AssociationsHelper.php b/administrator/components/com_associations/src/Helper/AssociationsHelper.php
index 5bf5b1df72b5d..9a8804a743871 100644
--- a/administrator/components/com_associations/src/Helper/AssociationsHelper.php
+++ b/administrator/components/com_associations/src/Helper/AssociationsHelper.php
@@ -1,4 +1,5 @@
getAssociationList($typeName, $itemId);
-
- }
-
- /**
- * Get the the instance of the extension helper class
- *
- * @param string $extensionName The extension name with com_
- *
- * @return \Joomla\CMS\Association\AssociationExtensionHelper|null
- *
- * @since 3.7.0
- */
- public static function getExtensionHelper($extensionName)
- {
- if (!self::hasSupport($extensionName))
- {
- return null;
- }
-
- $support = self::$extensionsSupport[$extensionName];
-
- return $support->get('helper');
- }
-
- /**
- * Get item information
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- * @param int $itemId The id of item for which we need the associated items
- *
- * @return \Joomla\CMS\Table\Table|null
- *
- * @since 3.7.0
- */
- public static function getItem($extensionName, $typeName, $itemId)
- {
- if (!self::hasSupport($extensionName))
- {
- return null;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- return $helper->getItem($typeName, $itemId);
- }
-
- /**
- * Check if extension supports associations
- *
- * @param string $extensionName The extension name with com_
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public static function hasSupport($extensionName)
- {
- if (\is_null(self::$extensionsSupport))
- {
- self::getSupportedExtensions();
- }
-
- return \in_array($extensionName, self::$supportedExtensionsList);
- }
-
- /**
- * Loads the helper for the given class.
- *
- * @param string $extensionName The extension name with com_
- *
- * @return AssociationExtensionInterface|null
- *
- * @since 4.0.0
- */
- private static function loadHelper($extensionName)
- {
- $component = Factory::getApplication()->bootComponent($extensionName);
-
- if ($component instanceof AssociationServiceInterface)
- {
- return $component->getAssociationsExtension();
- }
-
- // Check if associations helper exists
- if (!file_exists(JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php'))
- {
- return null;
- }
-
- require_once JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php';
-
- $componentAssociationsHelperClassName = self::getExtensionHelperClassName($extensionName);
-
- if (!class_exists($componentAssociationsHelperClassName, false))
- {
- return null;
- }
-
- // Create an instance of the helper class
- return new $componentAssociationsHelperClassName;
- }
-
- /**
- * Get the extension specific helper class name
- *
- * @param string $extensionName The extension name with com_
- *
- * @return string
- *
- * @since 3.7.0
- */
- private static function getExtensionHelperClassName($extensionName)
- {
- $realName = self::getExtensionRealName($extensionName);
-
- return ucfirst($realName) . 'AssociationsHelper';
- }
-
- /**
- * Get the real extension name. This means without com_
- *
- * @param string $extensionName The extension name with com_
- *
- * @return string
- *
- * @since 3.7.0
- */
- private static function getExtensionRealName($extensionName)
- {
- return strpos($extensionName, 'com_') === false ? $extensionName : substr($extensionName, 4);
- }
-
- /**
- * Get the associated language edit links Html.
- *
- * @param string $extensionName Extension Name
- * @param string $typeName ItemType
- * @param integer $itemId Item id.
- * @param string $itemLanguage Item language code.
- * @param boolean $addLink True for adding edit links. False for just text.
- * @param boolean $assocLanguages True for showing non associated content languages. False only languages with associations.
- *
- * @return string The language HTML
- *
- * @since 3.7.0
- */
- public static function getAssociationHtmlList($extensionName, $typeName, $itemId, $itemLanguage, $addLink = true, $assocLanguages = true)
- {
- // Get the associations list for this item.
- $items = self::getAssociationList($extensionName, $typeName, $itemId);
-
- $titleFieldName = self::getTypeFieldName($extensionName, $typeName, 'title');
-
- // Get all content languages.
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
- $content_languages = array_column($languages, 'lang_code');
-
- // Display warning if Content Language is trashed or deleted
- foreach ($items as $item)
- {
- if (!\in_array($item['language'], $content_languages))
- {
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item['language']), 'warning');
- }
- }
-
- $canEditReference = self::allowEdit($extensionName, $typeName, $itemId);
- $canCreate = self::allowAdd($extensionName, $typeName);
-
- // Create associated items list.
- foreach ($languages as $langCode => $language)
- {
- // Don't do for the reference language.
- if ($langCode == $itemLanguage)
- {
- continue;
- }
-
- // Don't show languages with associations, if we don't want to show them.
- if ($assocLanguages && isset($items[$langCode]))
- {
- unset($items[$langCode]);
- continue;
- }
-
- // Don't show languages without associations, if we don't want to show them.
- if (!$assocLanguages && !isset($items[$langCode]))
- {
- continue;
- }
-
- // Get html parameters.
- if (isset($items[$langCode]))
- {
- $title = $items[$langCode][$titleFieldName];
- $additional = '';
-
- if (isset($items[$langCode]['catid']))
- {
- $db = Factory::getDbo();
-
- // Get the category name
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $items[$langCode]['catid'], ParameterType::INTEGER);
-
- $db->setQuery($query);
- $categoryTitle = $db->loadResult();
-
- $additional = '' . Text::sprintf('JCATEGORY_SPRINTF', $categoryTitle) . ' ';
- }
- elseif (isset($items[$langCode]['menutype']))
- {
- $db = Factory::getDbo();
-
- // Get the menutype name
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__menu_types'))
- ->where($db->quoteName('menutype') . ' = :menutype')
- ->bind(':menutype', $items[$langCode]['menutype']);
-
- $db->setQuery($query);
- $menutypeTitle = $db->loadResult();
-
- $additional = '' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $menutypeTitle) . ' ';
- }
-
- $labelClass = 'bg-secondary';
- $target = $langCode . ':' . $items[$langCode]['id'] . ':edit';
- $allow = $canEditReference
- && self::allowEdit($extensionName, $typeName, $items[$langCode]['id'])
- && self::canCheckinItem($extensionName, $typeName, $items[$langCode]['id']);
-
- $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : '';
- }
- else
- {
- $items[$langCode] = array();
-
- $title = Text::_('COM_ASSOCIATIONS_NO_ASSOCIATION');
- $additional = $addLink ? Text::_('COM_ASSOCIATIONS_ADD_NEW_ASSOCIATION') : '';
- $labelClass = 'bg-warning text-dark';
- $target = $langCode . ':0:add';
- $allow = $canCreate;
- }
-
- // Generate item Html.
- $options = array(
- 'option' => 'com_associations',
- 'view' => 'association',
- 'layout' => 'edit',
- 'itemtype' => $extensionName . '.' . $typeName,
- 'task' => 'association.edit',
- 'id' => $itemId,
- 'target' => $target,
- );
-
- $url = Route::_('index.php?' . http_build_query($options));
- $url = $allow && $addLink ? $url : '';
- $text = $language->lang_code;
-
- $tooltip = '' . htmlspecialchars($language->title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . ' ' . $additional;
- $classes = 'badge ' . $labelClass;
-
- $items[$langCode]['link'] = '' . $text . ' '
- . '' . $tooltip . '
';
- }
-
- return LayoutHelper::render('joomla.content.associations', $items);
- }
-
- /**
- * Get all extensions with associations support.
- *
- * @return array The extensions.
- *
- * @since 3.7.0
- */
- public static function getSupportedExtensions()
- {
- if (!\is_null(self::$extensionsSupport))
- {
- return self::$extensionsSupport;
- }
-
- self::$extensionsSupport = array();
-
- $extensions = self::getEnabledExtensions();
-
- foreach ($extensions as $extension)
- {
- $support = self::getSupportedExtension($extension->element);
-
- if ($support->get('associationssupport') === true)
- {
- self::$supportedExtensionsList[] = $extension->element;
- }
-
- self::$extensionsSupport[$extension->element] = $support;
- }
-
- return self::$extensionsSupport;
- }
-
- /**
- * Get item context based on the item key.
- *
- * @param string $extensionName The extension identifier.
- *
- * @return \Joomla\Registry\Registry The item properties.
- *
- * @since 3.7.0
- */
- public static function getSupportedExtension($extensionName)
- {
- $result = new Registry;
-
- $result->def('component', $extensionName);
- $result->def('associationssupport', false);
- $result->def('helper', null);
-
- $helper = self::loadHelper($extensionName);
-
- if (!$helper)
- {
- return $result;
- }
-
- $result->set('helper', $helper);
-
- if ($helper->hasAssociationsSupport() === false)
- {
- return $result;
- }
-
- $result->set('associationssupport', true);
-
- // Get the translated titles.
- $languagePath = JPATH_ADMINISTRATOR . '/components/' . $extensionName;
- $lang = Factory::getLanguage();
-
- $lang->load($extensionName . '.sys', JPATH_ADMINISTRATOR);
- $lang->load($extensionName . '.sys', $languagePath);
- $lang->load($extensionName, JPATH_ADMINISTRATOR);
- $lang->load($extensionName, $languagePath);
-
- $result->def('title', Text::_(strtoupper($extensionName)));
-
- // Get the supported types
- $types = $helper->getItemTypes();
- $rTypes = array();
-
- foreach ($types as $typeName)
- {
- $details = $helper->getType($typeName);
- $context = 'component';
- $title = $helper->getTypeTitle($typeName);
- $languageKey = $typeName;
-
- $typeNameExploded = explode('.', $typeName);
-
- if (array_pop($typeNameExploded) === 'category')
- {
- $languageKey = strtoupper($extensionName) . '_CATEGORIES';
- $context = 'category';
- }
-
- if ($lang->hasKey(strtoupper($extensionName . '_' . $title . 'S')))
- {
- $languageKey = strtoupper($extensionName . '_' . $title . 'S');
- }
-
- $title = $lang->hasKey($languageKey) ? Text::_($languageKey) : Text::_('COM_ASSOCIATIONS_ITEMS');
-
- $rType = new Registry;
-
- $rType->def('name', $typeName);
- $rType->def('details', $details);
- $rType->def('title', $title);
- $rType->def('context', $context);
-
- $rTypes[$typeName] = $rType;
- }
-
- $result->def('types', $rTypes);
-
- return $result;
- }
-
- /**
- * Get all installed and enabled extensions
- *
- * @return mixed
- *
- * @since 3.7.0
- */
- private static function getEnabledExtensions()
- {
- $db = Factory::getDbo();
-
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
- ->where($db->quoteName('enabled') . ' = 1');
-
- $db->setQuery($query);
-
- return $db->loadObjectList();
- }
-
- /**
- * Get all the content languages.
- *
- * @return array Array of objects all content languages by language code.
- *
- * @since 3.7.0
- */
- public static function getContentLanguages()
- {
- return LanguageHelper::getContentLanguages(array(0, 1));
- }
-
- /**
- * Get the associated items for an item
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- * @param int $itemId The id of item for which we need the associated items
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public static function allowEdit($extensionName, $typeName, $itemId)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- if (method_exists($helper, 'allowEdit'))
- {
- return $helper->allowEdit($typeName, $itemId);
- }
-
- return Factory::getUser()->authorise('core.edit', $extensionName);
- }
-
- /**
- * Check if user is allowed to create items.
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- *
- * @return boolean True on allowed.
- *
- * @since 3.7.0
- */
- public static function allowAdd($extensionName, $typeName)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- if (method_exists($helper, 'allowAdd'))
- {
- return $helper->allowAdd($typeName);
- }
-
- return Factory::getUser()->authorise('core.create', $extensionName);
- }
-
- /**
- * Check if an item is checked out
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- * @param int $itemId The id of item for which we need the associated items
- *
- * @return boolean True if item is checked out.
- *
- * @since 3.7.0
- */
- public static function isCheckoutItem($extensionName, $typeName, $itemId)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- if (!self::typeSupportsCheckout($extensionName, $typeName))
- {
- return false;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- if (method_exists($helper, 'isCheckoutItem'))
- {
- return $helper->isCheckoutItem($typeName, $itemId);
- }
-
- $item = self::getItem($extensionName, $typeName, $itemId);
-
- $checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
-
- return $item->{$checkedOutFieldName} != 0;
- }
-
- /**
- * Check if user can checkin an item.
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- * @param int $itemId The id of item for which we need the associated items
- *
- * @return boolean True on allowed.
- *
- * @since 3.7.0
- */
- public static function canCheckinItem($extensionName, $typeName, $itemId)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- if (!self::typeSupportsCheckout($extensionName, $typeName))
- {
- return true;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- if (method_exists($helper, 'canCheckinItem'))
- {
- return $helper->canCheckinItem($typeName, $itemId);
- }
-
- $item = self::getItem($extensionName, $typeName, $itemId);
-
- $checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
-
- $userId = Factory::getUser()->id;
-
- return ($item->{$checkedOutFieldName} == $userId || $item->{$checkedOutFieldName} == 0);
- }
-
- /**
- * Check if the type supports checkout
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- *
- * @return boolean True on allowed.
- *
- * @since 3.7.0
- */
- public static function typeSupportsCheckout($extensionName, $typeName)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- $support = $helper->getTypeSupport($typeName);
-
- return !empty($support['checkout']);
- }
-
- /**
- * Get a table field name for a type
- *
- * @param string $extensionName The extension name with com_
- * @param string $typeName The item type
- * @param string $fieldName The item type
- *
- * @return boolean True on allowed.
- *
- * @since 3.7.0
- */
- public static function getTypeFieldName($extensionName, $typeName, $fieldName)
- {
- if (!self::hasSupport($extensionName))
- {
- return false;
- }
-
- // Get the extension specific helper method
- $helper = self::getExtensionHelper($extensionName);
-
- return $helper->getTypeFieldName($typeName, $fieldName);
- }
-
- /**
- * Gets the language filter system plugin extension id.
- *
- * @return integer The language filter system plugin extension id.
- *
- * @since 3.7.2
- */
- public static function getLanguagefilterPluginId()
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('languagefilter'));
- $db->setQuery($query);
-
- try
- {
- $result = (int) $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- return $result;
- }
+ /**
+ * Array of Registry objects of extensions
+ *
+ * @var array
+ * @since 3.7.0
+ */
+ public static $extensionsSupport = null;
+
+ /**
+ * List of extensions name with support
+ *
+ * @var array
+ * @since 3.7.0
+ */
+ public static $supportedExtensionsList = array();
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param int $itemId The id of item for which we need the associated items
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public static function getAssociationList($extensionName, $typeName, $itemId)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return array();
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ return $helper->getAssociationList($typeName, $itemId);
+ }
+
+ /**
+ * Get the the instance of the extension helper class
+ *
+ * @param string $extensionName The extension name with com_
+ *
+ * @return \Joomla\CMS\Association\AssociationExtensionHelper|null
+ *
+ * @since 3.7.0
+ */
+ public static function getExtensionHelper($extensionName)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return null;
+ }
+
+ $support = self::$extensionsSupport[$extensionName];
+
+ return $support->get('helper');
+ }
+
+ /**
+ * Get item information
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param int $itemId The id of item for which we need the associated items
+ *
+ * @return \Joomla\CMS\Table\Table|null
+ *
+ * @since 3.7.0
+ */
+ public static function getItem($extensionName, $typeName, $itemId)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return null;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ return $helper->getItem($typeName, $itemId);
+ }
+
+ /**
+ * Check if extension supports associations
+ *
+ * @param string $extensionName The extension name with com_
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public static function hasSupport($extensionName)
+ {
+ if (\is_null(self::$extensionsSupport)) {
+ self::getSupportedExtensions();
+ }
+
+ return \in_array($extensionName, self::$supportedExtensionsList);
+ }
+
+ /**
+ * Loads the helper for the given class.
+ *
+ * @param string $extensionName The extension name with com_
+ *
+ * @return AssociationExtensionInterface|null
+ *
+ * @since 4.0.0
+ */
+ private static function loadHelper($extensionName)
+ {
+ $component = Factory::getApplication()->bootComponent($extensionName);
+
+ if ($component instanceof AssociationServiceInterface) {
+ return $component->getAssociationsExtension();
+ }
+
+ // Check if associations helper exists
+ if (!file_exists(JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php')) {
+ return null;
+ }
+
+ require_once JPATH_ADMINISTRATOR . '/components/' . $extensionName . '/helpers/associations.php';
+
+ $componentAssociationsHelperClassName = self::getExtensionHelperClassName($extensionName);
+
+ if (!class_exists($componentAssociationsHelperClassName, false)) {
+ return null;
+ }
+
+ // Create an instance of the helper class
+ return new $componentAssociationsHelperClassName();
+ }
+
+ /**
+ * Get the extension specific helper class name
+ *
+ * @param string $extensionName The extension name with com_
+ *
+ * @return string
+ *
+ * @since 3.7.0
+ */
+ private static function getExtensionHelperClassName($extensionName)
+ {
+ $realName = self::getExtensionRealName($extensionName);
+
+ return ucfirst($realName) . 'AssociationsHelper';
+ }
+
+ /**
+ * Get the real extension name. This means without com_
+ *
+ * @param string $extensionName The extension name with com_
+ *
+ * @return string
+ *
+ * @since 3.7.0
+ */
+ private static function getExtensionRealName($extensionName)
+ {
+ return strpos($extensionName, 'com_') === false ? $extensionName : substr($extensionName, 4);
+ }
+
+ /**
+ * Get the associated language edit links Html.
+ *
+ * @param string $extensionName Extension Name
+ * @param string $typeName ItemType
+ * @param integer $itemId Item id.
+ * @param string $itemLanguage Item language code.
+ * @param boolean $addLink True for adding edit links. False for just text.
+ * @param boolean $assocLanguages True for showing non associated content languages. False only languages with associations.
+ *
+ * @return string The language HTML
+ *
+ * @since 3.7.0
+ */
+ public static function getAssociationHtmlList($extensionName, $typeName, $itemId, $itemLanguage, $addLink = true, $assocLanguages = true)
+ {
+ // Get the associations list for this item.
+ $items = self::getAssociationList($extensionName, $typeName, $itemId);
+
+ $titleFieldName = self::getTypeFieldName($extensionName, $typeName, 'title');
+
+ // Get all content languages.
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+ $content_languages = array_column($languages, 'lang_code');
+
+ // Display warning if Content Language is trashed or deleted
+ foreach ($items as $item) {
+ if (!\in_array($item['language'], $content_languages)) {
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item['language']), 'warning');
+ }
+ }
+
+ $canEditReference = self::allowEdit($extensionName, $typeName, $itemId);
+ $canCreate = self::allowAdd($extensionName, $typeName);
+
+ // Create associated items list.
+ foreach ($languages as $langCode => $language) {
+ // Don't do for the reference language.
+ if ($langCode == $itemLanguage) {
+ continue;
+ }
+
+ // Don't show languages with associations, if we don't want to show them.
+ if ($assocLanguages && isset($items[$langCode])) {
+ unset($items[$langCode]);
+ continue;
+ }
+
+ // Don't show languages without associations, if we don't want to show them.
+ if (!$assocLanguages && !isset($items[$langCode])) {
+ continue;
+ }
+
+ // Get html parameters.
+ if (isset($items[$langCode])) {
+ $title = $items[$langCode][$titleFieldName];
+ $additional = '';
+
+ if (isset($items[$langCode]['catid'])) {
+ $db = Factory::getDbo();
+
+ // Get the category name
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $items[$langCode]['catid'], ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $categoryTitle = $db->loadResult();
+
+ $additional = '' . Text::sprintf('JCATEGORY_SPRINTF', $categoryTitle) . ' ';
+ } elseif (isset($items[$langCode]['menutype'])) {
+ $db = Factory::getDbo();
+
+ // Get the menutype name
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__menu_types'))
+ ->where($db->quoteName('menutype') . ' = :menutype')
+ ->bind(':menutype', $items[$langCode]['menutype']);
+
+ $db->setQuery($query);
+ $menutypeTitle = $db->loadResult();
+
+ $additional = '' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $menutypeTitle) . ' ';
+ }
+
+ $labelClass = 'bg-secondary';
+ $target = $langCode . ':' . $items[$langCode]['id'] . ':edit';
+ $allow = $canEditReference
+ && self::allowEdit($extensionName, $typeName, $items[$langCode]['id'])
+ && self::canCheckinItem($extensionName, $typeName, $items[$langCode]['id']);
+
+ $additional .= $addLink && $allow ? Text::_('COM_ASSOCIATIONS_EDIT_ASSOCIATION') : '';
+ } else {
+ $items[$langCode] = array();
+
+ $title = Text::_('COM_ASSOCIATIONS_NO_ASSOCIATION');
+ $additional = $addLink ? Text::_('COM_ASSOCIATIONS_ADD_NEW_ASSOCIATION') : '';
+ $labelClass = 'bg-warning text-dark';
+ $target = $langCode . ':0:add';
+ $allow = $canCreate;
+ }
+
+ // Generate item Html.
+ $options = array(
+ 'option' => 'com_associations',
+ 'view' => 'association',
+ 'layout' => 'edit',
+ 'itemtype' => $extensionName . '.' . $typeName,
+ 'task' => 'association.edit',
+ 'id' => $itemId,
+ 'target' => $target,
+ );
+
+ $url = Route::_('index.php?' . http_build_query($options));
+ $url = $allow && $addLink ? $url : '';
+ $text = $language->lang_code;
+
+ $tooltip = '' . htmlspecialchars($language->title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . ' ' . $additional;
+ $classes = 'badge ' . $labelClass;
+
+ $items[$langCode]['link'] = '' . $text . ' '
+ . '' . $tooltip . '
';
+ }
+
+ return LayoutHelper::render('joomla.content.associations', $items);
+ }
+
+ /**
+ * Get all extensions with associations support.
+ *
+ * @return array The extensions.
+ *
+ * @since 3.7.0
+ */
+ public static function getSupportedExtensions()
+ {
+ if (!\is_null(self::$extensionsSupport)) {
+ return self::$extensionsSupport;
+ }
+
+ self::$extensionsSupport = array();
+
+ $extensions = self::getEnabledExtensions();
+
+ foreach ($extensions as $extension) {
+ $support = self::getSupportedExtension($extension->element);
+
+ if ($support->get('associationssupport') === true) {
+ self::$supportedExtensionsList[] = $extension->element;
+ }
+
+ self::$extensionsSupport[$extension->element] = $support;
+ }
+
+ return self::$extensionsSupport;
+ }
+
+ /**
+ * Get item context based on the item key.
+ *
+ * @param string $extensionName The extension identifier.
+ *
+ * @return \Joomla\Registry\Registry The item properties.
+ *
+ * @since 3.7.0
+ */
+ public static function getSupportedExtension($extensionName)
+ {
+ $result = new Registry();
+
+ $result->def('component', $extensionName);
+ $result->def('associationssupport', false);
+ $result->def('helper', null);
+
+ $helper = self::loadHelper($extensionName);
+
+ if (!$helper) {
+ return $result;
+ }
+
+ $result->set('helper', $helper);
+
+ if ($helper->hasAssociationsSupport() === false) {
+ return $result;
+ }
+
+ $result->set('associationssupport', true);
+
+ // Get the translated titles.
+ $languagePath = JPATH_ADMINISTRATOR . '/components/' . $extensionName;
+ $lang = Factory::getLanguage();
+
+ $lang->load($extensionName . '.sys', JPATH_ADMINISTRATOR);
+ $lang->load($extensionName . '.sys', $languagePath);
+ $lang->load($extensionName, JPATH_ADMINISTRATOR);
+ $lang->load($extensionName, $languagePath);
+
+ $result->def('title', Text::_(strtoupper($extensionName)));
+
+ // Get the supported types
+ $types = $helper->getItemTypes();
+ $rTypes = array();
+
+ foreach ($types as $typeName) {
+ $details = $helper->getType($typeName);
+ $context = 'component';
+ $title = $helper->getTypeTitle($typeName);
+ $languageKey = $typeName;
+
+ $typeNameExploded = explode('.', $typeName);
+
+ if (array_pop($typeNameExploded) === 'category') {
+ $languageKey = strtoupper($extensionName) . '_CATEGORIES';
+ $context = 'category';
+ }
+
+ if ($lang->hasKey(strtoupper($extensionName . '_' . $title . 'S'))) {
+ $languageKey = strtoupper($extensionName . '_' . $title . 'S');
+ }
+
+ $title = $lang->hasKey($languageKey) ? Text::_($languageKey) : Text::_('COM_ASSOCIATIONS_ITEMS');
+
+ $rType = new Registry();
+
+ $rType->def('name', $typeName);
+ $rType->def('details', $details);
+ $rType->def('title', $title);
+ $rType->def('context', $context);
+
+ $rTypes[$typeName] = $rType;
+ }
+
+ $result->def('types', $rTypes);
+
+ return $result;
+ }
+
+ /**
+ * Get all installed and enabled extensions
+ *
+ * @return mixed
+ *
+ * @since 3.7.0
+ */
+ private static function getEnabledExtensions()
+ {
+ $db = Factory::getDbo();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('component'))
+ ->where($db->quoteName('enabled') . ' = 1');
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList();
+ }
+
+ /**
+ * Get all the content languages.
+ *
+ * @return array Array of objects all content languages by language code.
+ *
+ * @since 3.7.0
+ */
+ public static function getContentLanguages()
+ {
+ return LanguageHelper::getContentLanguages(array(0, 1));
+ }
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param int $itemId The id of item for which we need the associated items
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public static function allowEdit($extensionName, $typeName, $itemId)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ if (method_exists($helper, 'allowEdit')) {
+ return $helper->allowEdit($typeName, $itemId);
+ }
+
+ return Factory::getUser()->authorise('core.edit', $extensionName);
+ }
+
+ /**
+ * Check if user is allowed to create items.
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ *
+ * @return boolean True on allowed.
+ *
+ * @since 3.7.0
+ */
+ public static function allowAdd($extensionName, $typeName)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ if (method_exists($helper, 'allowAdd')) {
+ return $helper->allowAdd($typeName);
+ }
+
+ return Factory::getUser()->authorise('core.create', $extensionName);
+ }
+
+ /**
+ * Check if an item is checked out
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param int $itemId The id of item for which we need the associated items
+ *
+ * @return boolean True if item is checked out.
+ *
+ * @since 3.7.0
+ */
+ public static function isCheckoutItem($extensionName, $typeName, $itemId)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ if (!self::typeSupportsCheckout($extensionName, $typeName)) {
+ return false;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ if (method_exists($helper, 'isCheckoutItem')) {
+ return $helper->isCheckoutItem($typeName, $itemId);
+ }
+
+ $item = self::getItem($extensionName, $typeName, $itemId);
+
+ $checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
+
+ return $item->{$checkedOutFieldName} != 0;
+ }
+
+ /**
+ * Check if user can checkin an item.
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param int $itemId The id of item for which we need the associated items
+ *
+ * @return boolean True on allowed.
+ *
+ * @since 3.7.0
+ */
+ public static function canCheckinItem($extensionName, $typeName, $itemId)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ if (!self::typeSupportsCheckout($extensionName, $typeName)) {
+ return true;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ if (method_exists($helper, 'canCheckinItem')) {
+ return $helper->canCheckinItem($typeName, $itemId);
+ }
+
+ $item = self::getItem($extensionName, $typeName, $itemId);
+
+ $checkedOutFieldName = $helper->getTypeFieldName($typeName, 'checked_out');
+
+ $userId = Factory::getUser()->id;
+
+ return ($item->{$checkedOutFieldName} == $userId || $item->{$checkedOutFieldName} == 0);
+ }
+
+ /**
+ * Check if the type supports checkout
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ *
+ * @return boolean True on allowed.
+ *
+ * @since 3.7.0
+ */
+ public static function typeSupportsCheckout($extensionName, $typeName)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ $support = $helper->getTypeSupport($typeName);
+
+ return !empty($support['checkout']);
+ }
+
+ /**
+ * Get a table field name for a type
+ *
+ * @param string $extensionName The extension name with com_
+ * @param string $typeName The item type
+ * @param string $fieldName The item type
+ *
+ * @return boolean True on allowed.
+ *
+ * @since 3.7.0
+ */
+ public static function getTypeFieldName($extensionName, $typeName, $fieldName)
+ {
+ if (!self::hasSupport($extensionName)) {
+ return false;
+ }
+
+ // Get the extension specific helper method
+ $helper = self::getExtensionHelper($extensionName);
+
+ return $helper->getTypeFieldName($typeName, $fieldName);
+ }
+
+ /**
+ * Gets the language filter system plugin extension id.
+ *
+ * @return integer The language filter system plugin extension id.
+ *
+ * @since 3.7.2
+ */
+ public static function getLanguagefilterPluginId()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('languagefilter'));
+ $db->setQuery($query);
+
+ try {
+ $result = (int) $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ return $result;
+ }
}
diff --git a/administrator/components/com_associations/src/Model/AssociationModel.php b/administrator/components/com_associations/src/Model/AssociationModel.php
index 54796fdedad5a..053a52275e2d7 100644
--- a/administrator/components/com_associations/src/Model/AssociationModel.php
+++ b/administrator/components/com_associations/src/Model/AssociationModel.php
@@ -1,4 +1,5 @@
loadForm('com_associations.association', 'association', array('control' => 'jform', 'load_data' => $loadData));
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
+ *
+ * @since 3.7.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_associations.association', 'association', array('control' => 'jform', 'load_data' => $loadData));
- return !empty($form) ? $form : false;
- }
+ return !empty($form) ? $form : false;
+ }
}
diff --git a/administrator/components/com_associations/src/Model/AssociationsModel.php b/administrator/components/com_associations/src/Model/AssociationsModel.php
index d63dc5bcf1586..d220949c89ae1 100644
--- a/administrator/components/com_associations/src/Model/AssociationsModel.php
+++ b/administrator/components/com_associations/src/Model/AssociationsModel.php
@@ -1,4 +1,5 @@
input->get('forcedLanguage', '', 'cmd');
- $forcedItemType = $app->input->get('forcedItemType', '', 'string');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- // Adjust the context to support forced component item types.
- if ($forcedItemType)
- {
- $this->context .= '.' . $forcedItemType;
- }
-
- $this->setState('itemtype', $this->getUserStateFromRequest($this->context . '.itemtype', 'itemtype', '', 'string'));
- $this->setState('language', $this->getUserStateFromRequest($this->context . '.language', 'language', '', 'string'));
-
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
- $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd'));
- $this->setState('filter.category_id', $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id', '', 'cmd'));
- $this->setState('filter.menutype', $this->getUserStateFromRequest($this->context . '.filter.menutype', 'filter_menutype', '', 'string'));
- $this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'string'));
- $this->setState('filter.level', $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level', '', 'cmd'));
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language.
- if (!empty($forcedLanguage))
- {
- $this->setState('language', $forcedLanguage);
- }
-
- // Force a component item type.
- if (!empty($forcedItemType))
- {
- $this->setState('itemtype', $forcedItemType);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 3.7.0
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('itemtype');
- $id .= ':' . $this->getState('language');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.category_id');
- $id .= ':' . $this->getState('filter.menutype');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.level');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery|boolean
- *
- * @since 3.7.0
- */
- protected function getListQuery()
- {
- $type = null;
-
- list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
-
- $extension = AssociationsHelper::getSupportedExtension($extensionName);
- $types = $extension->get('types');
-
- if (\array_key_exists($typeName, $types))
- {
- $type = $types[$typeName];
- }
-
- if (\is_null($type))
- {
- return false;
- }
-
- // Create a new query object.
- $user = Factory::getUser();
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $details = $type->get('details');
-
- if (!\array_key_exists('support', $details))
- {
- return false;
- }
-
- $support = $details['support'];
-
- if (!\array_key_exists('fields', $details))
- {
- return false;
- }
-
- $fields = $details['fields'];
-
- // Main query.
- $query->select($db->quoteName($fields['id'], 'id'))
- ->select($db->quoteName($fields['title'], 'title'))
- ->select($db->quoteName($fields['alias'], 'alias'));
-
- if (!\array_key_exists('tables', $details))
- {
- return false;
- }
-
- $tables = $details['tables'];
-
- foreach ($tables as $key => $table)
- {
- $query->from($db->quoteName($table, $key));
- }
-
- if (!\array_key_exists('joins', $details))
- {
- return false;
- }
-
- $joins = $details['joins'];
-
- foreach ($joins as $join)
- {
- $query->join($join['type'], $db->quoteName($join['condition']));
- }
-
- // Join over the language.
- $query->select($db->quoteName($fields['language'], 'language'))
- ->select($db->quoteName('l.title', 'language_title'))
- ->select($db->quoteName('l.image', 'language_image'))
- ->join(
- 'LEFT',
- $db->quoteName('#__languages', 'l'),
- $db->quoteName('l.lang_code') . ' = ' . $db->quoteName($fields['language'])
- );
- $extensionNameItem = $extensionName . '.item';
-
- // Join over the associations.
- $query->select('COUNT(' . $db->quoteName('asso2.id') . ') > 1 AS ' . $db->quoteName('association'))
- ->join(
- 'LEFT',
- $db->quoteName('#__associations', 'asso'),
- $db->quoteName('asso.id') . ' = ' . $db->quoteName($fields['id'])
- . ' AND ' . $db->quoteName('asso.context') . ' = :context'
- )
- ->join(
- 'LEFT',
- $db->quoteName('#__associations', 'asso2'),
- $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
- )
- ->bind(':context', $extensionNameItem);
-
- // Prepare the group by clause.
- $groupby = array(
- $fields['id'],
- $fields['title'],
- $fields['alias'],
- $fields['language'],
- 'l.title',
- 'l.image',
- );
-
- // Select author for ACL checks.
- if (!empty($fields['created_user_id']))
- {
- $query->select($db->quoteName($fields['created_user_id'], 'created_user_id'));
-
- $groupby[] = $fields['created_user_id'];
- }
-
- // Select checked out data for check in checkins.
- if (!empty($fields['checked_out']) && !empty($fields['checked_out_time']))
- {
- $query->select($db->quoteName($fields['checked_out'], 'checked_out'))
- ->select($db->quoteName($fields['checked_out_time'], 'checked_out_time'));
-
- // Join over the users.
- $query->select($db->quoteName('u.name', 'editor'))
- ->join(
- 'LEFT',
- $db->quoteName('#__users', 'u'),
- $db->quoteName('u.id') . ' = ' . $db->quoteName($fields['checked_out'])
- );
-
- $groupby[] = 'u.name';
- $groupby[] = $fields['checked_out'];
- $groupby[] = $fields['checked_out_time'];
- }
-
- // If component item type supports ordering, select the ordering also.
- if (!empty($fields['ordering']))
- {
- $query->select($db->quoteName($fields['ordering'], 'ordering'));
-
- $groupby[] = $fields['ordering'];
- }
-
- // If component item type supports state, select the item state also.
- if (!empty($fields['state']))
- {
- $query->select($db->quoteName($fields['state'], 'state'));
-
- $groupby[] = $fields['state'];
- }
-
- // If component item type supports level, select the level also.
- if (!empty($fields['level']))
- {
- $query->select($db->quoteName($fields['level'], 'level'));
-
- $groupby[] = $fields['level'];
- }
-
- // If component item type supports categories, select the category also.
- if (!empty($fields['catid']))
- {
- $query->select($db->quoteName($fields['catid'], 'catid'));
-
- // Join over the categories.
- $query->select($db->quoteName('c.title', 'category_title'))
- ->join(
- 'LEFT',
- $db->quoteName('#__categories', 'c'),
- $db->quoteName('c.id') . ' = ' . $db->quoteName($fields['catid'])
- );
-
- $groupby[] = 'c.title';
- $groupby[] = $fields['catid'];
- }
-
- // If component item type supports menu type, select the menu type also.
- if (!empty($fields['menutype']))
- {
- $query->select($db->quoteName($fields['menutype'], 'menutype'));
-
- // Join over the menu types.
- $query->select($db->quoteName('mt.title', 'menutype_title'))
- ->select($db->quoteName('mt.id', 'menutypeid'))
- ->join(
- 'LEFT',
- $db->quoteName('#__menu_types', 'mt'),
- $db->quoteName('mt.menutype') . ' = ' . $db->quoteName($fields['menutype'])
- );
-
- $groupby[] = 'mt.title';
- $groupby[] = 'mt.id';
- $groupby[] = $fields['menutype'];
- }
-
- // If component item type supports access level, select the access level also.
- if (\array_key_exists('acl', $support) && $support['acl'] == true && !empty($fields['access']))
- {
- $query->select($db->quoteName($fields['access'], 'access'));
-
- // Join over the access levels.
- $query->select($db->quoteName('ag.title', 'access_level'))
- ->join(
- 'LEFT',
- $db->quoteName('#__viewlevels', 'ag'),
- $db->quoteName('ag.id') . ' = ' . $db->quoteName($fields['access'])
- );
-
- $groupby[] = 'ag.title';
- $groupby[] = $fields['access'];
-
- // Implement View Level Access.
- if (!$user->authorise('core.admin', $extensionName))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName($fields['access']), $groups);
- }
- }
-
- // If component item type is menus we need to remove the root item and the administrator menu.
- if ($extensionName === 'com_menus')
- {
- $query->where($db->quoteName($fields['id']) . ' > 1')
- ->where($db->quoteName('a.client_id') . ' = 0');
- }
-
- // If component item type is category we need to remove all other component categories.
- if ($typeName === 'category')
- {
- $query->where($db->quoteName('a.extension') . ' = :extensionname')
- ->bind(':extensionname', $extensionName);
- }
- elseif ($typeNameExploded = explode('.', $typeName))
- {
- if (\count($typeNameExploded) > 1 && array_pop($typeNameExploded) === 'category')
- {
- $section = implode('.', $typeNameExploded);
- $extensionNameSection = $extensionName . '.' . $section;
- $query->where($db->quoteName('a.extension') . ' = :extensionsection')
- ->bind(':extensionsection', $extensionNameSection);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('language'))
- {
- $query->where($db->quoteName($fields['language']) . ' = :language')
- ->bind(':language', $language);
- }
-
- // Filter by item state.
- $state = $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName($fields['state']) . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif ($state === '')
- {
- $query->whereIn($db->quoteName($fields['state']), [0, 1]);
- }
-
- // Filter on the category.
- $baselevel = 1;
-
- if ($categoryId = $this->getState('filter.category_id'))
- {
- $categoryTable = Table::getInstance('Category', 'JTable');
- $categoryTable->load($categoryId);
- $baselevel = (int) $categoryTable->level;
-
- $lft = (int) $categoryTable->lft;
- $rgt = (int) $categoryTable->rgt;
- $query->where($db->quoteName('c.lft') . ' >= :lft')
- ->where($db->quoteName('c.rgt') . ' <= :rgt')
- ->bind(':lft', $lft, ParameterType::INTEGER)
- ->bind(':rgt', $rgt, ParameterType::INTEGER);
- }
-
- // Filter on the level.
- if ($level = $this->getState('filter.level'))
- {
- $queryLevel = ((int) $level + (int) $baselevel - 1);
- $query->where($db->quoteName('a.level') . ' <= :alevel')
- ->bind(':alevel', $queryLevel, ParameterType::INTEGER);
- }
-
- // Filter by menu type.
- if ($menutype = $this->getState('filter.menutype'))
- {
- $query->where($db->quoteName($fields['menutype']) . ' = :menutype2')
- ->bind(':menutype2', $menutype);
- }
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- $access = (int) $access;
- $query->where($db->quoteName($fields['access']) . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Filter by search in name.
- if ($search = $this->getState('filter.search'))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName($fields['id']) . ' = :searchid')
- ->bind(':searchid', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName($fields['title']) . ' LIKE :title'
- . ' OR ' . $db->quoteName($fields['alias']) . ' LIKE :alias)'
- )
- ->bind(':title', $search)
- ->bind(':alias', $search);
- }
- }
-
- // Add the group by clause
- $query->group($db->quoteName($groupby));
-
- // Add the list ordering clause
- $listOrdering = $this->state->get('list.ordering', 'id');
- $orderDirn = $this->state->get('list.direction', 'ASC');
-
- $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));
-
- return $query;
- }
-
- /**
- * Delete associations from #__associations table.
- *
- * @param string $context The associations context. Empty for all.
- * @param string $key The associations key. Empty for all.
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- */
- public function purge($context = '', $key = '')
- {
- $app = Factory::getApplication();
- $db = $this->getDatabase();
- $query = $db->getQuery(true)->delete($db->quoteName('#__associations'));
-
- // Filter by associations context.
- if ($context)
- {
- $query->where($db->quoteName('context') . ' = :context')
- ->bind(':context', $context);
- }
-
- // Filter by key.
- if ($key)
- {
- $query->where($db->quoteName('key') . ' = :key')
- ->bind(':key', $key);
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- $app->enqueueMessage(Text::_('COM_ASSOCIATIONS_PURGE_FAILED'), 'error');
-
- return false;
- }
-
- $app->enqueueMessage(
- Text::_((int) $db->getAffectedRows() > 0 ? 'COM_ASSOCIATIONS_PURGE_SUCCESS' : 'COM_ASSOCIATIONS_PURGE_NONE'),
- 'message'
- );
-
- return true;
- }
-
- /**
- * Delete orphans from the #__associations table.
- *
- * @param string $context The associations context. Empty for all.
- * @param string $key The associations key. Empty for all.
- *
- * @return boolean True on success
- *
- * @since 3.7.0
- */
- public function clean($context = '', $key = '')
- {
- $app = Factory::getApplication();
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('key') . ', COUNT(*)')
- ->from($db->quoteName('#__associations'))
- ->group($db->quoteName('key'))
- ->having('COUNT(*) = 1');
-
- // Filter by associations context.
- if ($context)
- {
- $query->where($db->quoteName('context') . ' = :context')
- ->bind(':context', $context);
- }
-
- // Filter by key.
- if ($key)
- {
- $query->where($db->quoteName('key') . ' = :key')
- ->bind(':key', $key);
- }
-
- $db->setQuery($query);
-
- $assocKeys = $db->loadObjectList();
-
- $count = 0;
-
- // We have orphans. Let's delete them.
- foreach ($assocKeys as $value)
- {
- $query->clear()
- ->delete($db->quoteName('#__associations'))
- ->where($db->quoteName('key') . ' = :valuekey')
- ->bind(':valuekey', $value->key);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- $app->enqueueMessage(Text::_('COM_ASSOCIATIONS_DELETE_ORPHANS_FAILED'), 'error');
-
- return false;
- }
-
- $count += (int) $db->getAffectedRows();
- }
-
- $app->enqueueMessage(
- Text::_($count > 0 ? 'COM_ASSOCIATIONS_DELETE_ORPHANS_SUCCESS' : 'COM_ASSOCIATIONS_DELETE_ORPHANS_NONE'),
- 'message'
- );
-
- return true;
- }
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.7
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id',
+ 'title',
+ 'ordering',
+ 'itemtype',
+ 'language',
+ 'association',
+ 'menutype',
+ 'menutype_title',
+ 'level',
+ 'state',
+ 'category_id',
+ 'category_title',
+ 'access',
+ 'access_level',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function populateState($ordering = 'ordering', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+ $forcedItemType = $app->input->get('forcedItemType', '', 'string');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ // Adjust the context to support forced component item types.
+ if ($forcedItemType) {
+ $this->context .= '.' . $forcedItemType;
+ }
+
+ $this->setState('itemtype', $this->getUserStateFromRequest($this->context . '.itemtype', 'itemtype', '', 'string'));
+ $this->setState('language', $this->getUserStateFromRequest($this->context . '.language', 'language', '', 'string'));
+
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+ $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'cmd'));
+ $this->setState('filter.category_id', $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id', '', 'cmd'));
+ $this->setState('filter.menutype', $this->getUserStateFromRequest($this->context . '.filter.menutype', 'filter_menutype', '', 'string'));
+ $this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'string'));
+ $this->setState('filter.level', $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level', '', 'cmd'));
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language.
+ if (!empty($forcedLanguage)) {
+ $this->setState('language', $forcedLanguage);
+ }
+
+ // Force a component item type.
+ if (!empty($forcedItemType)) {
+ $this->setState('itemtype', $forcedItemType);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 3.7.0
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('itemtype');
+ $id .= ':' . $this->getState('language');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.category_id');
+ $id .= ':' . $this->getState('filter.menutype');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.level');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery|boolean
+ *
+ * @since 3.7.0
+ */
+ protected function getListQuery()
+ {
+ $type = null;
+
+ list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
+
+ $extension = AssociationsHelper::getSupportedExtension($extensionName);
+ $types = $extension->get('types');
+
+ if (\array_key_exists($typeName, $types)) {
+ $type = $types[$typeName];
+ }
+
+ if (\is_null($type)) {
+ return false;
+ }
+
+ // Create a new query object.
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $details = $type->get('details');
+
+ if (!\array_key_exists('support', $details)) {
+ return false;
+ }
+
+ $support = $details['support'];
+
+ if (!\array_key_exists('fields', $details)) {
+ return false;
+ }
+
+ $fields = $details['fields'];
+
+ // Main query.
+ $query->select($db->quoteName($fields['id'], 'id'))
+ ->select($db->quoteName($fields['title'], 'title'))
+ ->select($db->quoteName($fields['alias'], 'alias'));
+
+ if (!\array_key_exists('tables', $details)) {
+ return false;
+ }
+
+ $tables = $details['tables'];
+
+ foreach ($tables as $key => $table) {
+ $query->from($db->quoteName($table, $key));
+ }
+
+ if (!\array_key_exists('joins', $details)) {
+ return false;
+ }
+
+ $joins = $details['joins'];
+
+ foreach ($joins as $join) {
+ $query->join($join['type'], $db->quoteName($join['condition']));
+ }
+
+ // Join over the language.
+ $query->select($db->quoteName($fields['language'], 'language'))
+ ->select($db->quoteName('l.title', 'language_title'))
+ ->select($db->quoteName('l.image', 'language_image'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__languages', 'l'),
+ $db->quoteName('l.lang_code') . ' = ' . $db->quoteName($fields['language'])
+ );
+ $extensionNameItem = $extensionName . '.item';
+
+ // Join over the associations.
+ $query->select('COUNT(' . $db->quoteName('asso2.id') . ') > 1 AS ' . $db->quoteName('association'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'asso'),
+ $db->quoteName('asso.id') . ' = ' . $db->quoteName($fields['id'])
+ . ' AND ' . $db->quoteName('asso.context') . ' = :context'
+ )
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'asso2'),
+ $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
+ )
+ ->bind(':context', $extensionNameItem);
+
+ // Prepare the group by clause.
+ $groupby = array(
+ $fields['id'],
+ $fields['title'],
+ $fields['alias'],
+ $fields['language'],
+ 'l.title',
+ 'l.image',
+ );
+
+ // Select author for ACL checks.
+ if (!empty($fields['created_user_id'])) {
+ $query->select($db->quoteName($fields['created_user_id'], 'created_user_id'));
+
+ $groupby[] = $fields['created_user_id'];
+ }
+
+ // Select checked out data for check in checkins.
+ if (!empty($fields['checked_out']) && !empty($fields['checked_out_time'])) {
+ $query->select($db->quoteName($fields['checked_out'], 'checked_out'))
+ ->select($db->quoteName($fields['checked_out_time'], 'checked_out_time'));
+
+ // Join over the users.
+ $query->select($db->quoteName('u.name', 'editor'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'u'),
+ $db->quoteName('u.id') . ' = ' . $db->quoteName($fields['checked_out'])
+ );
+
+ $groupby[] = 'u.name';
+ $groupby[] = $fields['checked_out'];
+ $groupby[] = $fields['checked_out_time'];
+ }
+
+ // If component item type supports ordering, select the ordering also.
+ if (!empty($fields['ordering'])) {
+ $query->select($db->quoteName($fields['ordering'], 'ordering'));
+
+ $groupby[] = $fields['ordering'];
+ }
+
+ // If component item type supports state, select the item state also.
+ if (!empty($fields['state'])) {
+ $query->select($db->quoteName($fields['state'], 'state'));
+
+ $groupby[] = $fields['state'];
+ }
+
+ // If component item type supports level, select the level also.
+ if (!empty($fields['level'])) {
+ $query->select($db->quoteName($fields['level'], 'level'));
+
+ $groupby[] = $fields['level'];
+ }
+
+ // If component item type supports categories, select the category also.
+ if (!empty($fields['catid'])) {
+ $query->select($db->quoteName($fields['catid'], 'catid'));
+
+ // Join over the categories.
+ $query->select($db->quoteName('c.title', 'category_title'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__categories', 'c'),
+ $db->quoteName('c.id') . ' = ' . $db->quoteName($fields['catid'])
+ );
+
+ $groupby[] = 'c.title';
+ $groupby[] = $fields['catid'];
+ }
+
+ // If component item type supports menu type, select the menu type also.
+ if (!empty($fields['menutype'])) {
+ $query->select($db->quoteName($fields['menutype'], 'menutype'));
+
+ // Join over the menu types.
+ $query->select($db->quoteName('mt.title', 'menutype_title'))
+ ->select($db->quoteName('mt.id', 'menutypeid'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__menu_types', 'mt'),
+ $db->quoteName('mt.menutype') . ' = ' . $db->quoteName($fields['menutype'])
+ );
+
+ $groupby[] = 'mt.title';
+ $groupby[] = 'mt.id';
+ $groupby[] = $fields['menutype'];
+ }
+
+ // If component item type supports access level, select the access level also.
+ if (\array_key_exists('acl', $support) && $support['acl'] == true && !empty($fields['access'])) {
+ $query->select($db->quoteName($fields['access'], 'access'));
+
+ // Join over the access levels.
+ $query->select($db->quoteName('ag.title', 'access_level'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__viewlevels', 'ag'),
+ $db->quoteName('ag.id') . ' = ' . $db->quoteName($fields['access'])
+ );
+
+ $groupby[] = 'ag.title';
+ $groupby[] = $fields['access'];
+
+ // Implement View Level Access.
+ if (!$user->authorise('core.admin', $extensionName)) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName($fields['access']), $groups);
+ }
+ }
+
+ // If component item type is menus we need to remove the root item and the administrator menu.
+ if ($extensionName === 'com_menus') {
+ $query->where($db->quoteName($fields['id']) . ' > 1')
+ ->where($db->quoteName('a.client_id') . ' = 0');
+ }
+
+ // If component item type is category we need to remove all other component categories.
+ if ($typeName === 'category') {
+ $query->where($db->quoteName('a.extension') . ' = :extensionname')
+ ->bind(':extensionname', $extensionName);
+ } elseif ($typeNameExploded = explode('.', $typeName)) {
+ if (\count($typeNameExploded) > 1 && array_pop($typeNameExploded) === 'category') {
+ $section = implode('.', $typeNameExploded);
+ $extensionNameSection = $extensionName . '.' . $section;
+ $query->where($db->quoteName('a.extension') . ' = :extensionsection')
+ ->bind(':extensionsection', $extensionNameSection);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('language')) {
+ $query->where($db->quoteName($fields['language']) . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Filter by item state.
+ $state = $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName($fields['state']) . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif ($state === '') {
+ $query->whereIn($db->quoteName($fields['state']), [0, 1]);
+ }
+
+ // Filter on the category.
+ $baselevel = 1;
+
+ if ($categoryId = $this->getState('filter.category_id')) {
+ $categoryTable = Table::getInstance('Category', 'JTable');
+ $categoryTable->load($categoryId);
+ $baselevel = (int) $categoryTable->level;
+
+ $lft = (int) $categoryTable->lft;
+ $rgt = (int) $categoryTable->rgt;
+ $query->where($db->quoteName('c.lft') . ' >= :lft')
+ ->where($db->quoteName('c.rgt') . ' <= :rgt')
+ ->bind(':lft', $lft, ParameterType::INTEGER)
+ ->bind(':rgt', $rgt, ParameterType::INTEGER);
+ }
+
+ // Filter on the level.
+ if ($level = $this->getState('filter.level')) {
+ $queryLevel = ((int) $level + (int) $baselevel - 1);
+ $query->where($db->quoteName('a.level') . ' <= :alevel')
+ ->bind(':alevel', $queryLevel, ParameterType::INTEGER);
+ }
+
+ // Filter by menu type.
+ if ($menutype = $this->getState('filter.menutype')) {
+ $query->where($db->quoteName($fields['menutype']) . ' = :menutype2')
+ ->bind(':menutype2', $menutype);
+ }
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ $access = (int) $access;
+ $query->where($db->quoteName($fields['access']) . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Filter by search in name.
+ if ($search = $this->getState('filter.search')) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName($fields['id']) . ' = :searchid')
+ ->bind(':searchid', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName($fields['title']) . ' LIKE :title'
+ . ' OR ' . $db->quoteName($fields['alias']) . ' LIKE :alias)')
+ ->bind(':title', $search)
+ ->bind(':alias', $search);
+ }
+ }
+
+ // Add the group by clause
+ $query->group($db->quoteName($groupby));
+
+ // Add the list ordering clause
+ $listOrdering = $this->state->get('list.ordering', 'id');
+ $orderDirn = $this->state->get('list.direction', 'ASC');
+
+ $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));
+
+ return $query;
+ }
+
+ /**
+ * Delete associations from #__associations table.
+ *
+ * @param string $context The associations context. Empty for all.
+ * @param string $key The associations key. Empty for all.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ */
+ public function purge($context = '', $key = '')
+ {
+ $app = Factory::getApplication();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)->delete($db->quoteName('#__associations'));
+
+ // Filter by associations context.
+ if ($context) {
+ $query->where($db->quoteName('context') . ' = :context')
+ ->bind(':context', $context);
+ }
+
+ // Filter by key.
+ if ($key) {
+ $query->where($db->quoteName('key') . ' = :key')
+ ->bind(':key', $key);
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ $app->enqueueMessage(Text::_('COM_ASSOCIATIONS_PURGE_FAILED'), 'error');
+
+ return false;
+ }
+
+ $app->enqueueMessage(
+ Text::_((int) $db->getAffectedRows() > 0 ? 'COM_ASSOCIATIONS_PURGE_SUCCESS' : 'COM_ASSOCIATIONS_PURGE_NONE'),
+ 'message'
+ );
+
+ return true;
+ }
+
+ /**
+ * Delete orphans from the #__associations table.
+ *
+ * @param string $context The associations context. Empty for all.
+ * @param string $key The associations key. Empty for all.
+ *
+ * @return boolean True on success
+ *
+ * @since 3.7.0
+ */
+ public function clean($context = '', $key = '')
+ {
+ $app = Factory::getApplication();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('key') . ', COUNT(*)')
+ ->from($db->quoteName('#__associations'))
+ ->group($db->quoteName('key'))
+ ->having('COUNT(*) = 1');
+
+ // Filter by associations context.
+ if ($context) {
+ $query->where($db->quoteName('context') . ' = :context')
+ ->bind(':context', $context);
+ }
+
+ // Filter by key.
+ if ($key) {
+ $query->where($db->quoteName('key') . ' = :key')
+ ->bind(':key', $key);
+ }
+
+ $db->setQuery($query);
+
+ $assocKeys = $db->loadObjectList();
+
+ $count = 0;
+
+ // We have orphans. Let's delete them.
+ foreach ($assocKeys as $value) {
+ $query->clear()
+ ->delete($db->quoteName('#__associations'))
+ ->where($db->quoteName('key') . ' = :valuekey')
+ ->bind(':valuekey', $value->key);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ $app->enqueueMessage(Text::_('COM_ASSOCIATIONS_DELETE_ORPHANS_FAILED'), 'error');
+
+ return false;
+ }
+
+ $count += (int) $db->getAffectedRows();
+ }
+
+ $app->enqueueMessage(
+ Text::_($count > 0 ? 'COM_ASSOCIATIONS_DELETE_ORPHANS_SUCCESS' : 'COM_ASSOCIATIONS_DELETE_ORPHANS_NONE'),
+ 'message'
+ );
+
+ return true;
+ }
}
diff --git a/administrator/components/com_associations/src/View/Association/HtmlView.php b/administrator/components/com_associations/src/View/Association/HtmlView.php
index c54f8b80dcfeb..4fb58586283af 100644
--- a/administrator/components/com_associations/src/View/Association/HtmlView.php
+++ b/administrator/components/com_associations/src/View/Association/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
-
- // Check for errors.
- if (\count($errors = $model->getErrors()))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->app = Factory::getApplication();
- $this->form = $model->getForm();
- /** @var Input $input */
- $input = $this->app->input;
- $this->referenceId = $input->get('id', 0, 'int');
-
- [$extensionName, $typeName] = explode('.', $input->get('itemtype', '', 'string'), 2);
-
- /** @var Registry $extension */
- $extension = AssociationsHelper::getSupportedExtension($extensionName);
- $types = $extension->get('types');
-
- if (\array_key_exists($typeName, $types))
- {
- $this->type = $types[$typeName];
- $this->typeSupports = [];
- $details = $this->type->get('details');
- $this->save2copy = false;
-
- if (\array_key_exists('support', $details))
- {
- $support = $details['support'];
- $this->typeSupports = $support;
- }
-
- if (!empty($this->typeSupports['save2copy']))
- {
- $this->save2copy = true;
- }
- }
-
- $this->extensionName = $extensionName;
- $this->typeName = $typeName;
- $this->itemType = $extensionName . '.' . $typeName;
-
- $languageField = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'language');
- $referenceId = $input->get('id', 0, 'int');
- $reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
-
- $this->referenceLanguage = $reference[$languageField];
- $this->referenceTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
- $this->referenceTitleValue = $reference[$this->referenceTitle];
-
- // Check for special case category
- $typeNameExploded = explode('.', $typeName);
-
- if (array_pop($typeNameExploded) === 'category')
- {
- $this->typeName = 'category';
-
- if ($typeNameExploded)
- {
- $extensionName .= '.' . implode('.', $typeNameExploded);
- }
-
- $options = [
- 'option' => 'com_categories',
- 'view' => 'category',
- 'extension' => $extensionName,
- 'tmpl' => 'component',
- ];
- }
- else
- {
- $options = [
- 'option' => $extensionName,
- 'view' => $typeName,
- 'extension' => $extensionName,
- 'tmpl' => 'component',
- ];
- }
-
- // Reference and target edit links.
- $this->editUri = 'index.php?' . http_build_query($options);
-
- // Get target language.
- $this->targetId = '0';
- $this->targetLanguage = '';
- $this->defaultTargetSrc = '';
- $this->targetAction = '';
- $this->targetTitle = '';
-
- if ($target = $input->get('target', '', 'string'))
- {
- $matches = preg_split("#[\:]+#", $target);
- $this->targetAction = $matches[2];
- $this->targetId = $matches[1];
- $this->targetLanguage = $matches[0];
- $this->targetTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
- $task = $typeName . '.' . $this->targetAction;
-
- /**
- * Let's put the target src into a variable to use in the javascript code
- * to avoid race conditions when the reference iframe loads.
- */
- $this->document->addScriptOptions('targetSrc', Route::_($this->editUri . '&task=' . $task . '&id=' . (int) $this->targetId));
- $this->form->setValue('itemlanguage', '', $this->targetLanguage . ':' . $this->targetId . ':' . $this->targetAction);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- *
- * @throws \Exception
- */
- protected function addToolbar(): void
- {
- // Hide main menu.
- $this->app->input->set('hidemainmenu', 1);
-
- $helper = AssociationsHelper::getExtensionHelper($this->extensionName);
- $title = $helper->getTypeTitle($this->typeName);
-
- $languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
-
- if ($this->typeName === 'category')
- {
- $languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
- }
-
- ToolbarHelper::title(
- Text::sprintf(
- 'COM_ASSOCIATIONS_TITLE_EDIT',
- Text::_($this->extensionName),
- Text::_($languageKey)
- ),
- 'language assoc'
- );
-
- $bar = Toolbar::getInstance();
-
- $bar->appendButton(
- 'Custom', ' '
- . Text::_('COM_ASSOCIATIONS_SAVE_REFERENCE') . ' ', 'reference'
- );
-
- $bar->appendButton(
- 'Custom', ' '
- . Text::_('COM_ASSOCIATIONS_SAVE_TARGET') . ' ', 'target'
- );
-
- if ($this->typeName === 'category' || $this->extensionName === 'com_menus' || $this->save2copy === true)
- {
- ToolbarHelper::custom('copy', 'copy.png', '', 'COM_ASSOCIATIONS_COPY_REFERENCE', false);
- }
-
- ToolbarHelper::cancel('association.cancel', 'JTOOLBAR_CLOSE');
- ToolbarHelper::help('Multilingual_Associations:_Edit');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ protected $items = [];
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ *
+ * @since 3.7.0
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * Selected item type properties.
+ *
+ * @var Registry
+ *
+ * @since 3.7.0
+ */
+ protected $itemType;
+
+ /**
+ * The application
+ *
+ * @var AdministratorApplication
+ * @since 3.7.0
+ */
+ protected $app;
+
+ /**
+ * The ID of the reference language
+ *
+ * @var integer
+ * @since 3.7.0
+ */
+ protected $referenceId = 0;
+
+ /**
+ * The type name
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $typeName = '';
+
+ /**
+ * The reference language
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $referenceLanguage = '';
+
+ /**
+ * The title of the reference language
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $referenceTitle = '';
+
+ /**
+ * The value of the reference title
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $referenceTitleValue = '';
+
+ /**
+ * The URL to the edit screen
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $editUri = '';
+
+ /**
+ * The ID of the target field
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $targetId = '';
+
+ /**
+ * The target language
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $targetLanguage = '';
+
+ /**
+ * The source of the target field
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $defaultTargetSrc = '';
+
+ /**
+ * The action to perform for the target field
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $targetAction = '';
+
+ /**
+ * The title of the target field
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $targetTitle = '';
+
+ /**
+ * The edit form
+ *
+ * @var Form
+ * @since 3.7.0
+ */
+ protected $form;
+
+ /**
+ * Set if the option is set to save as copy
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ private $save2copy = false;
+
+ /**
+ * The type of language
+ *
+ * @var Registry
+ * @since 3.7.0
+ */
+ private $type;
+
+ /**
+ * The supported types
+ *
+ * @var array
+ * @since 3.7.0
+ */
+ private $typeSupports = [];
+
+ /**
+ * The extension name
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ private $extensionName = '';
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ *
+ * @throws \Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var AssociationModel $model */
+ $model = $this->getModel();
+
+ // Check for errors.
+ if (\count($errors = $model->getErrors())) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->app = Factory::getApplication();
+ $this->form = $model->getForm();
+ /** @var Input $input */
+ $input = $this->app->input;
+ $this->referenceId = $input->get('id', 0, 'int');
+
+ [$extensionName, $typeName] = explode('.', $input->get('itemtype', '', 'string'), 2);
+
+ /** @var Registry $extension */
+ $extension = AssociationsHelper::getSupportedExtension($extensionName);
+ $types = $extension->get('types');
+
+ if (\array_key_exists($typeName, $types)) {
+ $this->type = $types[$typeName];
+ $this->typeSupports = [];
+ $details = $this->type->get('details');
+ $this->save2copy = false;
+
+ if (\array_key_exists('support', $details)) {
+ $support = $details['support'];
+ $this->typeSupports = $support;
+ }
+
+ if (!empty($this->typeSupports['save2copy'])) {
+ $this->save2copy = true;
+ }
+ }
+
+ $this->extensionName = $extensionName;
+ $this->typeName = $typeName;
+ $this->itemType = $extensionName . '.' . $typeName;
+
+ $languageField = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'language');
+ $referenceId = $input->get('id', 0, 'int');
+ $reference = ArrayHelper::fromObject(AssociationsHelper::getItem($extensionName, $typeName, $referenceId));
+
+ $this->referenceLanguage = $reference[$languageField];
+ $this->referenceTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
+ $this->referenceTitleValue = $reference[$this->referenceTitle];
+
+ // Check for special case category
+ $typeNameExploded = explode('.', $typeName);
+
+ if (array_pop($typeNameExploded) === 'category') {
+ $this->typeName = 'category';
+
+ if ($typeNameExploded) {
+ $extensionName .= '.' . implode('.', $typeNameExploded);
+ }
+
+ $options = [
+ 'option' => 'com_categories',
+ 'view' => 'category',
+ 'extension' => $extensionName,
+ 'tmpl' => 'component',
+ ];
+ } else {
+ $options = [
+ 'option' => $extensionName,
+ 'view' => $typeName,
+ 'extension' => $extensionName,
+ 'tmpl' => 'component',
+ ];
+ }
+
+ // Reference and target edit links.
+ $this->editUri = 'index.php?' . http_build_query($options);
+
+ // Get target language.
+ $this->targetId = '0';
+ $this->targetLanguage = '';
+ $this->defaultTargetSrc = '';
+ $this->targetAction = '';
+ $this->targetTitle = '';
+
+ if ($target = $input->get('target', '', 'string')) {
+ $matches = preg_split("#[\:]+#", $target);
+ $this->targetAction = $matches[2];
+ $this->targetId = $matches[1];
+ $this->targetLanguage = $matches[0];
+ $this->targetTitle = AssociationsHelper::getTypeFieldName($extensionName, $typeName, 'title');
+ $task = $typeName . '.' . $this->targetAction;
+
+ /**
+ * Let's put the target src into a variable to use in the javascript code
+ * to avoid race conditions when the reference iframe loads.
+ */
+ $this->document->addScriptOptions('targetSrc', Route::_($this->editUri . '&task=' . $task . '&id=' . (int) $this->targetId));
+ $this->form->setValue('itemlanguage', '', $this->targetLanguage . ':' . $this->targetId . ':' . $this->targetAction);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ *
+ * @throws \Exception
+ */
+ protected function addToolbar(): void
+ {
+ // Hide main menu.
+ $this->app->input->set('hidemainmenu', 1);
+
+ $helper = AssociationsHelper::getExtensionHelper($this->extensionName);
+ $title = $helper->getTypeTitle($this->typeName);
+
+ $languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
+
+ if ($this->typeName === 'category') {
+ $languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
+ }
+
+ ToolbarHelper::title(
+ Text::sprintf(
+ 'COM_ASSOCIATIONS_TITLE_EDIT',
+ Text::_($this->extensionName),
+ Text::_($languageKey)
+ ),
+ 'language assoc'
+ );
+
+ $bar = Toolbar::getInstance();
+
+ $bar->appendButton(
+ 'Custom',
+ ' '
+ . Text::_('COM_ASSOCIATIONS_SAVE_REFERENCE') . ' ',
+ 'reference'
+ );
+
+ $bar->appendButton(
+ 'Custom',
+ ' '
+ . Text::_('COM_ASSOCIATIONS_SAVE_TARGET') . ' ',
+ 'target'
+ );
+
+ if ($this->typeName === 'category' || $this->extensionName === 'com_menus' || $this->save2copy === true) {
+ ToolbarHelper::custom('copy', 'copy.png', '', 'COM_ASSOCIATIONS_COPY_REFERENCE', false);
+ }
+
+ ToolbarHelper::cancel('association.cancel', 'JTOOLBAR_CLOSE');
+ ToolbarHelper::help('Multilingual_Associations:_Edit');
+ }
}
diff --git a/administrator/components/com_associations/src/View/Associations/HtmlView.php b/administrator/components/com_associations/src/View/Associations/HtmlView.php
index 12dc60323e892..e3e14c96bfbbb 100644
--- a/administrator/components/com_associations/src/View/Associations/HtmlView.php
+++ b/administrator/components/com_associations/src/View/Associations/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!Associations::isEnabled())
- {
- $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . AssociationsHelper::getLanguagefilterPluginId());
- Factory::getApplication()->enqueueMessage(Text::sprintf('COM_ASSOCIATIONS_ERROR_NO_ASSOC', $link), 'warning');
- }
- elseif ($this->state->get('itemtype') != '' && $this->state->get('language') != '')
- {
- $type = null;
-
- list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
-
- $extension = AssociationsHelper::getSupportedExtension($extensionName);
-
- $types = $extension->get('types');
-
- if (\array_key_exists($typeName, $types))
- {
- $type = $types[$typeName];
- }
-
- $this->itemType = $type;
-
- if (\is_null($type))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_ASSOCIATIONS_ERROR_NO_TYPE'), 'warning');
- }
- else
- {
- $this->extensionName = $extensionName;
- $this->typeName = $typeName;
- $this->typeSupports = array();
- $this->typeFields = array();
-
- $details = $type->get('details');
-
- if (\array_key_exists('support', $details))
- {
- $support = $details['support'];
- $this->typeSupports = $support;
- }
-
- if (\array_key_exists('fields', $details))
- {
- $fields = $details['fields'];
- $this->typeFields = $fields;
- }
-
- // Dynamic filter form.
- // This selectors doesn't have to activate the filter bar.
- unset($this->activeFilters['itemtype']);
- unset($this->activeFilters['language']);
-
- // Remove filters options depending on selected type.
- if (empty($support['state']))
- {
- unset($this->activeFilters['state']);
- $this->filterForm->removeField('state', 'filter');
- }
-
- if (empty($support['category']))
- {
- unset($this->activeFilters['category_id']);
- $this->filterForm->removeField('category_id', 'filter');
- }
-
- if ($extensionName !== 'com_menus')
- {
- unset($this->activeFilters['menutype']);
- $this->filterForm->removeField('menutype', 'filter');
- }
-
- if (empty($support['level']))
- {
- unset($this->activeFilters['level']);
- $this->filterForm->removeField('level', 'filter');
- }
-
- if (empty($support['acl']))
- {
- unset($this->activeFilters['access']);
- $this->filterForm->removeField('access', 'filter');
- }
-
- // Add extension attribute to category filter.
- if (empty($support['catid']))
- {
- $this->filterForm->setFieldAttribute('category_id', 'extension', $extensionName, 'filter');
-
- if ($this->getLayout() == 'modal')
- {
- // We need to change the category filter to only show categories tagged to All or to the forced language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
- }
- }
- }
-
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
-
- $linkParameters = array(
- 'layout' => 'edit',
- 'itemtype' => $extensionName . '.' . $typeName,
- 'task' => 'association.edit',
- );
-
- $this->editUri = 'index.php?option=com_associations&view=association&' . http_build_query($linkParameters);
- }
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new \Exception(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addToolbar()
- {
- $user = $this->getCurrentUser();
-
- if (isset($this->typeName) && isset($this->extensionName))
- {
- $helper = AssociationsHelper::getExtensionHelper($this->extensionName);
- $title = $helper->getTypeTitle($this->typeName);
-
- $languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
-
- if ($this->typeName === 'category')
- {
- $languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
- }
-
- ToolbarHelper::title(
- Text::sprintf(
- 'COM_ASSOCIATIONS_TITLE_LIST', Text::_($this->extensionName), Text::_($languageKey)
- ), 'language assoc'
- );
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_ASSOCIATIONS_TITLE_LIST_SELECT'), 'language assoc');
- }
-
- if ($user->authorise('core.admin', 'com_associations') || $user->authorise('core.options', 'com_associations'))
- {
- if (!isset($this->typeName))
- {
- ToolbarHelper::custom('associations.purge', 'purge', '', 'COM_ASSOCIATIONS_PURGE', false, false);
- ToolbarHelper::custom('associations.clean', 'refresh', '', 'COM_ASSOCIATIONS_DELETE_ORPHANS', false, false);
- }
-
- ToolbarHelper::preferences('com_associations');
- }
-
- ToolbarHelper::help('Multilingual_Associations');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 3.7.0
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var object
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * Selected item type properties.
+ *
+ * @var \Joomla\Registry\Registry
+ *
+ * @since 3.7.0
+ */
+ public $itemType = null;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!Associations::isEnabled()) {
+ $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . AssociationsHelper::getLanguagefilterPluginId());
+ Factory::getApplication()->enqueueMessage(Text::sprintf('COM_ASSOCIATIONS_ERROR_NO_ASSOC', $link), 'warning');
+ } elseif ($this->state->get('itemtype') != '' && $this->state->get('language') != '') {
+ $type = null;
+
+ list($extensionName, $typeName) = explode('.', $this->state->get('itemtype'), 2);
+
+ $extension = AssociationsHelper::getSupportedExtension($extensionName);
+
+ $types = $extension->get('types');
+
+ if (\array_key_exists($typeName, $types)) {
+ $type = $types[$typeName];
+ }
+
+ $this->itemType = $type;
+
+ if (\is_null($type)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_ASSOCIATIONS_ERROR_NO_TYPE'), 'warning');
+ } else {
+ $this->extensionName = $extensionName;
+ $this->typeName = $typeName;
+ $this->typeSupports = array();
+ $this->typeFields = array();
+
+ $details = $type->get('details');
+
+ if (\array_key_exists('support', $details)) {
+ $support = $details['support'];
+ $this->typeSupports = $support;
+ }
+
+ if (\array_key_exists('fields', $details)) {
+ $fields = $details['fields'];
+ $this->typeFields = $fields;
+ }
+
+ // Dynamic filter form.
+ // This selectors doesn't have to activate the filter bar.
+ unset($this->activeFilters['itemtype']);
+ unset($this->activeFilters['language']);
+
+ // Remove filters options depending on selected type.
+ if (empty($support['state'])) {
+ unset($this->activeFilters['state']);
+ $this->filterForm->removeField('state', 'filter');
+ }
+
+ if (empty($support['category'])) {
+ unset($this->activeFilters['category_id']);
+ $this->filterForm->removeField('category_id', 'filter');
+ }
+
+ if ($extensionName !== 'com_menus') {
+ unset($this->activeFilters['menutype']);
+ $this->filterForm->removeField('menutype', 'filter');
+ }
+
+ if (empty($support['level'])) {
+ unset($this->activeFilters['level']);
+ $this->filterForm->removeField('level', 'filter');
+ }
+
+ if (empty($support['acl'])) {
+ unset($this->activeFilters['access']);
+ $this->filterForm->removeField('access', 'filter');
+ }
+
+ // Add extension attribute to category filter.
+ if (empty($support['catid'])) {
+ $this->filterForm->setFieldAttribute('category_id', 'extension', $extensionName, 'filter');
+
+ if ($this->getLayout() == 'modal') {
+ // We need to change the category filter to only show categories tagged to All or to the forced language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
+ }
+ }
+ }
+
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+
+ $linkParameters = array(
+ 'layout' => 'edit',
+ 'itemtype' => $extensionName . '.' . $typeName,
+ 'task' => 'association.edit',
+ );
+
+ $this->editUri = 'index.php?option=com_associations&view=association&' . http_build_query($linkParameters);
+ }
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new \Exception(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addToolbar()
+ {
+ $user = $this->getCurrentUser();
+
+ if (isset($this->typeName) && isset($this->extensionName)) {
+ $helper = AssociationsHelper::getExtensionHelper($this->extensionName);
+ $title = $helper->getTypeTitle($this->typeName);
+
+ $languageKey = strtoupper($this->extensionName . '_' . $title . 'S');
+
+ if ($this->typeName === 'category') {
+ $languageKey = strtoupper($this->extensionName) . '_CATEGORIES';
+ }
+
+ ToolbarHelper::title(
+ Text::sprintf(
+ 'COM_ASSOCIATIONS_TITLE_LIST',
+ Text::_($this->extensionName),
+ Text::_($languageKey)
+ ),
+ 'language assoc'
+ );
+ } else {
+ ToolbarHelper::title(Text::_('COM_ASSOCIATIONS_TITLE_LIST_SELECT'), 'language assoc');
+ }
+
+ if ($user->authorise('core.admin', 'com_associations') || $user->authorise('core.options', 'com_associations')) {
+ if (!isset($this->typeName)) {
+ ToolbarHelper::custom('associations.purge', 'purge', '', 'COM_ASSOCIATIONS_PURGE', false, false);
+ ToolbarHelper::custom('associations.clean', 'refresh', '', 'COM_ASSOCIATIONS_DELETE_ORPHANS', false, false);
+ }
+
+ ToolbarHelper::preferences('com_associations');
+ }
+
+ ToolbarHelper::help('Multilingual_Associations');
+ }
}
diff --git a/administrator/components/com_associations/tmpl/association/edit.php b/administrator/components/com_associations/tmpl/association/edit.php
index 422799a43312a..0f7b5bc1b4a1a 100644
--- a/administrator/components/com_associations/tmpl/association/edit.php
+++ b/administrator/components/com_associations/tmpl/association/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->usePreset('com_associations.sidebyside')
- ->useScript('webcomponent.core-loader');
+ ->useScript('form.validate')
+ ->usePreset('com_associations.sidebyside')
+ ->useScript('webcomponent.core-loader');
$options = [
- 'layout' => $this->app->input->get('layout', '', 'string'),
- 'itemtype' => $this->itemType,
- 'id' => $this->referenceId,
+ 'layout' => $this->app->input->get('layout', '', 'string'),
+ 'itemtype' => $this->itemType,
+ 'id' => $this->referenceId,
];
?>
+ data-show-reference=""
+ data-hide-reference="">
diff --git a/administrator/components/com_associations/tmpl/associations/default.php b/administrator/components/com_associations/tmpl/associations/default.php
index fd7879f38d399..ec539fed43377 100644
--- a/administrator/components/com_associations/tmpl/associations/default.php
+++ b/administrator/components/com_associations/tmpl/associations/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('com_associations.admin-associations-default')
- ->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('table.columns')
+ ->useScript('multiselect');
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$canManageCheckin = Factory::getUser()->authorise('core.manage', 'com_checkin');
$iconStates = array(
- -2 => 'icon-trash',
- 0 => 'icon-times',
- 1 => 'icon-check',
- 2 => 'icon-folder',
+ -2 => 'icon-trash',
+ 0 => 'icon-times',
+ 1 => 'icon-check',
+ 2 => 'icon-folder',
);
Text::script('COM_ASSOCIATIONS_PURGE_CONFIRM_PROMPT', true);
?>
diff --git a/administrator/components/com_associations/tmpl/associations/modal.php b/administrator/components/com_associations/tmpl/associations/modal.php
index b46c16594488a..398e271296b1c 100644
--- a/administrator/components/com_associations/tmpl/associations/modal.php
+++ b/administrator/components/com_associations/tmpl/associations/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if ($app->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('multiselect')
- ->useScript('com_associations.admin-associations-modal');
+ ->useScript('com_associations.admin-associations-modal');
$function = $app->input->getCmd('function', 'jSelectAssociation');
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -35,136 +35,136 @@
$canManageCheckin = Factory::getUser()->authorise('core.manage', 'com_checkin');
$iconStates = array(
- -2 => 'icon-trash',
- 0 => 'icon-times',
- 1 => 'icon-check',
- 2 => 'icon-folder',
+ -2 => 'icon-trash',
+ 0 => 'icon-times',
+ 1 => 'icon-check',
+ 2 => 'icon-folder',
);
$this->document->addScriptOptions('associations-modal', ['func' => $function]);
?>
diff --git a/administrator/components/com_banners/helpers/banners.php b/administrator/components/com_banners/helpers/banners.php
index 97747c9c6c728..cf15f619cc341 100644
--- a/administrator/components/com_banners/helpers/banners.php
+++ b/administrator/components/com_banners/helpers/banners.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Banners component helper.
@@ -18,5 +21,4 @@
*/
class BannersHelper extends \Joomla\Component\Banners\Administrator\Helper\BannersHelper
{
-
}
diff --git a/administrator/components/com_banners/services/provider.php b/administrator/components/com_banners/services/provider.php
index 6f629fedef70b..02bb499b3d240 100644
--- a/administrator/components/com_banners/services/provider.php
+++ b/administrator/components/com_banners/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Banners'));
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Banners'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Banners'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Banners'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Banners'));
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Banners'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Banners'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Banners'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new BannersComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new BannersComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_banners/src/Controller/BannerController.php b/administrator/components/com_banners/src/Controller/BannerController.php
index bd3f642f963c9..d424d91e96148 100644
--- a/administrator/components/com_banners/src/Controller/BannerController.php
+++ b/administrator/components/com_banners/src/Controller/BannerController.php
@@ -1,4 +1,5 @@
input->getInt('filter_category_id');
- $categoryId = ArrayHelper::getValue($data, 'catid', $filter, 'int');
-
- if ($categoryId)
- {
- // If the category has been passed in the URL check it.
- return $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
- }
-
- // In the absence of better information, revert to the component permissions.
- return parent::allowAdd($data);
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
- $categoryId = 0;
-
- if ($recordId)
- {
- $categoryId = (int) $this->getModel()->getItem($recordId)->catid;
- }
-
- if ($categoryId)
- {
- // The category has been set. Check the category permissions.
- return $this->app->getIdentity()->authorise('core.edit', $this->option . '.category.' . $categoryId);
- }
-
- // Since there is no asset tracking, revert to the component permissions.
- return parent::allowEdit($data, $key);
- }
-
- /**
- * Method to run batch operations.
- *
- * @param string $model The model
- *
- * @return boolean True on success.
- *
- * @since 2.5
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('Banner', '', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_banners&view=banners' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
+ use VersionableControllerTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_BANNERS_BANNER';
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $filter = $this->input->getInt('filter_category_id');
+ $categoryId = ArrayHelper::getValue($data, 'catid', $filter, 'int');
+
+ if ($categoryId) {
+ // If the category has been passed in the URL check it.
+ return $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
+ }
+
+ // In the absence of better information, revert to the component permissions.
+ return parent::allowAdd($data);
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+ $categoryId = 0;
+
+ if ($recordId) {
+ $categoryId = (int) $this->getModel()->getItem($recordId)->catid;
+ }
+
+ if ($categoryId) {
+ // The category has been set. Check the category permissions.
+ return $this->app->getIdentity()->authorise('core.edit', $this->option . '.category.' . $categoryId);
+ }
+
+ // Since there is no asset tracking, revert to the component permissions.
+ return parent::allowEdit($data, $key);
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param string $model The model
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('Banner', '', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_banners&view=banners' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
}
diff --git a/administrator/components/com_banners/src/Controller/BannersController.php b/administrator/components/com_banners/src/Controller/BannersController.php
index 8dcc0df9c05ef..ac71135b75a78 100644
--- a/administrator/components/com_banners/src/Controller/BannersController.php
+++ b/administrator/components/com_banners/src/Controller/BannersController.php
@@ -1,4 +1,5 @@
registerTask('sticky_unpublish', 'sticky_publish');
- }
+ $this->registerTask('sticky_unpublish', 'sticky_publish');
+ }
- /**
- * Method to get a model object, loading it if required.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Banner', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Banner', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
- /**
- * Stick items
- *
- * @return void
- *
- * @since 1.6
- */
- public function sticky_publish()
- {
- // Check for request forgeries.
- $this->checkToken();
+ /**
+ * Stick items
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function sticky_publish()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
- $ids = (array) $this->input->get('cid', array(), 'int');
- $values = array('sticky_publish' => 1, 'sticky_unpublish' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($values, $task, 0, 'int');
+ $ids = (array) $this->input->get('cid', array(), 'int');
+ $values = array('sticky_publish' => 1, 'sticky_unpublish' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('COM_BANNERS_NO_BANNERS_SELECTED'), 'warning');
- }
- else
- {
- // Get the model.
- /** @var \Joomla\Component\Banners\Administrator\Model\BannerModel $model */
- $model = $this->getModel();
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('COM_BANNERS_NO_BANNERS_SELECTED'), 'warning');
+ } else {
+ // Get the model.
+ /** @var \Joomla\Component\Banners\Administrator\Model\BannerModel $model */
+ $model = $this->getModel();
- // Change the state of the records.
- if (!$model->stick($ids, $value))
- {
- $this->app->enqueueMessage($model->getError(), 'warning');
- }
- else
- {
- if ($value == 1)
- {
- $ntext = 'COM_BANNERS_N_BANNERS_STUCK';
- }
- else
- {
- $ntext = 'COM_BANNERS_N_BANNERS_UNSTUCK';
- }
+ // Change the state of the records.
+ if (!$model->stick($ids, $value)) {
+ $this->app->enqueueMessage($model->getError(), 'warning');
+ } else {
+ if ($value == 1) {
+ $ntext = 'COM_BANNERS_N_BANNERS_STUCK';
+ } else {
+ $ntext = 'COM_BANNERS_N_BANNERS_UNSTUCK';
+ }
- $this->setMessage(Text::plural($ntext, \count($ids)));
- }
- }
+ $this->setMessage(Text::plural($ntext, \count($ids)));
+ }
+ }
- $this->setRedirect('index.php?option=com_banners&view=banners');
- }
+ $this->setRedirect('index.php?option=com_banners&view=banners');
+ }
}
diff --git a/administrator/components/com_banners/src/Controller/ClientController.php b/administrator/components/com_banners/src/Controller/ClientController.php
index 9b28ec21f5827..b0404380e461a 100644
--- a/administrator/components/com_banners/src/Controller/ClientController.php
+++ b/administrator/components/com_banners/src/Controller/ClientController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Client', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_banners/src/Controller/DisplayController.php b/administrator/components/com_banners/src/Controller/DisplayController.php
index 41468a8b71fe4..2c5b25491313c 100644
--- a/administrator/components/com_banners/src/Controller/DisplayController.php
+++ b/administrator/components/com_banners/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'banners');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
+ $view = $this->input->get('view', 'banners');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
- // Check for edit form.
- if ($view == 'banner' && $layout == 'edit' && !$this->checkEditId('com_banners.edit.banner', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($view == 'banner' && $layout == 'edit' && !$this->checkEditId('com_banners.edit.banner', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_banners&view=banners', false));
+ $this->setRedirect(Route::_('index.php?option=com_banners&view=banners', false));
- return false;
- }
- elseif ($view == 'client' && $layout == 'edit' && !$this->checkEditId('com_banners.edit.client', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ return false;
+ } elseif ($view == 'client' && $layout == 'edit' && !$this->checkEditId('com_banners.edit.client', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_banners&view=clients', false));
+ $this->setRedirect(Route::_('index.php?option=com_banners&view=clients', false));
- return false;
- }
+ return false;
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_banners/src/Controller/TracksController.php b/administrator/components/com_banners/src/Controller/TracksController.php
index fbca3db5c12b0..1d0df14cde2c0 100644
--- a/administrator/components/com_banners/src/Controller/TracksController.php
+++ b/administrator/components/com_banners/src/Controller/TracksController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to remove a record.
- *
- * @return void
- *
- * @since 1.6
- */
- public function delete()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- // Get the model.
- /** @var \Joomla\Component\Banners\Administrator\Model\TracksModel $model */
- $model = $this->getModel();
-
- // Load the filter state.
- $model->setState('filter.type', $this->app->getUserState($this->context . '.filter.type'));
- $model->setState('filter.begin', $this->app->getUserState($this->context . '.filter.begin'));
- $model->setState('filter.end', $this->app->getUserState($this->context . '.filter.end'));
- $model->setState('filter.category_id', $this->app->getUserState($this->context . '.filter.category_id'));
- $model->setState('filter.client_id', $this->app->getUserState($this->context . '.filter.client_id'));
- $model->setState('list.limit', 0);
- $model->setState('list.start', 0);
-
- $count = $model->getTotal();
-
- // Remove the items.
- if (!$model->delete())
- {
- $this->app->enqueueMessage($model->getError(), 'warning');
- }
- elseif ($count > 0)
- {
- $this->setMessage(Text::plural('COM_BANNERS_TRACKS_N_ITEMS_DELETED', $count));
- }
- else
- {
- $this->setMessage(Text::_('COM_BANNERS_TRACKS_NO_ITEMS_DELETED'));
- }
-
- $this->setRedirect('index.php?option=com_banners&view=tracks');
- }
-
- /**
- * Display method for the raw track data.
- *
- * @param boolean $cachable If true, the view output will be cached
- * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
- *
- * @return static This object to support chaining.
- *
- * @since 1.5
- * @todo This should be done as a view, not here!
- */
- public function display($cachable = false, $urlparams = array())
- {
- // Get the document object.
- $vName = 'tracks';
-
- // Get and render the view.
- if ($view = $this->getView($vName, 'raw'))
- {
- // Check for request forgeries.
- $this->checkToken('GET');
-
- // Get the model for the view.
- /** @var \Joomla\Component\Banners\Administrator\Model\TracksModel $model */
- $model = $this->getModel($vName);
-
- // Load the filter state.
- $app = $this->app;
-
- $model->setState('filter.type', $app->getUserState($this->context . '.filter.type'));
- $model->setState('filter.begin', $app->getUserState($this->context . '.filter.begin'));
- $model->setState('filter.end', $app->getUserState($this->context . '.filter.end'));
- $model->setState('filter.category_id', $app->getUserState($this->context . '.filter.category_id'));
- $model->setState('filter.client_id', $app->getUserState($this->context . '.filter.client_id'));
- $model->setState('list.limit', 0);
- $model->setState('list.start', 0);
-
- $form = $this->input->get('jform', array(), 'array');
-
- $model->setState('basename', $form['basename']);
- $model->setState('compressed', $form['compressed']);
-
- // Create one year cookies.
- $cookieLifeTime = time() + 365 * 86400;
- $cookieDomain = $app->get('cookie_domain', '');
- $cookiePath = $app->get('cookie_path', '/');
- $isHttpsForced = $app->isHttpsForced();
-
- $this->input->cookie->set(
- ApplicationHelper::getHash($this->context . '.basename'),
- $form['basename'],
- $cookieLifeTime,
- $cookiePath,
- $cookieDomain,
- $isHttpsForced,
- true
- );
-
- $this->input->cookie->set(
- ApplicationHelper::getHash($this->context . '.compressed'),
- $form['compressed'],
- $cookieLifeTime,
- $cookiePath,
- $cookieDomain,
- $isHttpsForced,
- true
- );
-
- // Push the model into the view (as default).
- $view->setModel($model, true);
-
- // Push document object into the view.
- $view->document = $this->app->getDocument();
-
- $view->display();
- }
-
- return $this;
- }
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $context = 'com_banners.tracks';
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Tracks', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to remove a record.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function delete()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ // Get the model.
+ /** @var \Joomla\Component\Banners\Administrator\Model\TracksModel $model */
+ $model = $this->getModel();
+
+ // Load the filter state.
+ $model->setState('filter.type', $this->app->getUserState($this->context . '.filter.type'));
+ $model->setState('filter.begin', $this->app->getUserState($this->context . '.filter.begin'));
+ $model->setState('filter.end', $this->app->getUserState($this->context . '.filter.end'));
+ $model->setState('filter.category_id', $this->app->getUserState($this->context . '.filter.category_id'));
+ $model->setState('filter.client_id', $this->app->getUserState($this->context . '.filter.client_id'));
+ $model->setState('list.limit', 0);
+ $model->setState('list.start', 0);
+
+ $count = $model->getTotal();
+
+ // Remove the items.
+ if (!$model->delete()) {
+ $this->app->enqueueMessage($model->getError(), 'warning');
+ } elseif ($count > 0) {
+ $this->setMessage(Text::plural('COM_BANNERS_TRACKS_N_ITEMS_DELETED', $count));
+ } else {
+ $this->setMessage(Text::_('COM_BANNERS_TRACKS_NO_ITEMS_DELETED'));
+ }
+
+ $this->setRedirect('index.php?option=com_banners&view=tracks');
+ }
+
+ /**
+ * Display method for the raw track data.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static This object to support chaining.
+ *
+ * @since 1.5
+ * @todo This should be done as a view, not here!
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ // Get the document object.
+ $vName = 'tracks';
+
+ // Get and render the view.
+ if ($view = $this->getView($vName, 'raw')) {
+ // Check for request forgeries.
+ $this->checkToken('GET');
+
+ // Get the model for the view.
+ /** @var \Joomla\Component\Banners\Administrator\Model\TracksModel $model */
+ $model = $this->getModel($vName);
+
+ // Load the filter state.
+ $app = $this->app;
+
+ $model->setState('filter.type', $app->getUserState($this->context . '.filter.type'));
+ $model->setState('filter.begin', $app->getUserState($this->context . '.filter.begin'));
+ $model->setState('filter.end', $app->getUserState($this->context . '.filter.end'));
+ $model->setState('filter.category_id', $app->getUserState($this->context . '.filter.category_id'));
+ $model->setState('filter.client_id', $app->getUserState($this->context . '.filter.client_id'));
+ $model->setState('list.limit', 0);
+ $model->setState('list.start', 0);
+
+ $form = $this->input->get('jform', array(), 'array');
+
+ $model->setState('basename', $form['basename']);
+ $model->setState('compressed', $form['compressed']);
+
+ // Create one year cookies.
+ $cookieLifeTime = time() + 365 * 86400;
+ $cookieDomain = $app->get('cookie_domain', '');
+ $cookiePath = $app->get('cookie_path', '/');
+ $isHttpsForced = $app->isHttpsForced();
+
+ $this->input->cookie->set(
+ ApplicationHelper::getHash($this->context . '.basename'),
+ $form['basename'],
+ $cookieLifeTime,
+ $cookiePath,
+ $cookieDomain,
+ $isHttpsForced,
+ true
+ );
+
+ $this->input->cookie->set(
+ ApplicationHelper::getHash($this->context . '.compressed'),
+ $form['compressed'],
+ $cookieLifeTime,
+ $cookiePath,
+ $cookieDomain,
+ $isHttpsForced,
+ true
+ );
+
+ // Push the model into the view (as default).
+ $view->setModel($model, true);
+
+ // Push document object into the view.
+ $view->document = $this->app->getDocument();
+
+ $view->display();
+ }
+
+ return $this;
+ }
}
diff --git a/administrator/components/com_banners/src/Extension/BannersComponent.php b/administrator/components/com_banners/src/Extension/BannersComponent.php
index 2a52518e079e1..95308a9d9c6bd 100644
--- a/administrator/components/com_banners/src/Extension/BannersComponent.php
+++ b/administrator/components/com_banners/src/Extension/BannersComponent.php
@@ -1,4 +1,5 @@
setDatabase($container->get(DatabaseInterface::class));
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $banner = new Banner();
+ $banner->setDatabase($container->get(DatabaseInterface::class));
- $this->getRegistry()->register('banner', $banner);
- }
+ $this->getRegistry()->register('banner', $banner);
+ }
- /**
- * Returns the table for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getTableNameForSection(string $section = null)
- {
- return 'banners';
- }
+ /**
+ * Returns the table for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getTableNameForSection(string $section = null)
+ {
+ return 'banners';
+ }
}
diff --git a/administrator/components/com_banners/src/Field/BannerclientField.php b/administrator/components/com_banners/src/Field/BannerclientField.php
index 5c0f33863d2a9..c4832c82cf724 100644
--- a/administrator/components/com_banners/src/Field/BannerclientField.php
+++ b/administrator/components/com_banners/src/Field/BannerclientField.php
@@ -1,4 +1,5 @@
id . '\').value=\'0\';"';
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $onclick = ' onclick="document.getElementById(\'' . $this->id . '\').value=\'0\';"';
- return ' '
- . ''
- . ' ' . Text::_('JRESET') . '
';
- }
+ return ' '
+ . ''
+ . ' ' . Text::_('JRESET') . '
';
+ }
}
diff --git a/administrator/components/com_banners/src/Field/ImpmadeField.php b/administrator/components/com_banners/src/Field/ImpmadeField.php
index f385b8531e88a..236b112a3c5b4 100644
--- a/administrator/components/com_banners/src/Field/ImpmadeField.php
+++ b/administrator/components/com_banners/src/Field/ImpmadeField.php
@@ -1,4 +1,5 @@
id . '\').value=\'0\';"';
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $onclick = ' onclick="document.getElementById(\'' . $this->id . '\').value=\'0\';"';
- return ' '
- . ''
- . ' ' . Text::_('JRESET') . '
';
- }
+ return ' '
+ . ''
+ . ' ' . Text::_('JRESET') . '
';
+ }
}
diff --git a/administrator/components/com_banners/src/Field/ImptotalField.php b/administrator/components/com_banners/src/Field/ImptotalField.php
index 54550bbaa5749..3dae74ffefe61 100644
--- a/administrator/components/com_banners/src/Field/ImptotalField.php
+++ b/administrator/components/com_banners/src/Field/ImptotalField.php
@@ -1,4 +1,5 @@
id . '_unlimited\').checked=document.getElementById(\'' . $this->id
- . '\').value==\'\';"';
- $onclick = ' onclick="if (document.getElementById(\'' . $this->id . '_unlimited\').checked) document.getElementById(\'' . $this->id
- . '\').value=\'\';"';
- $value = empty($this->value) ? '' : $this->value;
- $checked = empty($this->value) ? ' checked="checked"' : '';
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $class = ' class="form-control validate-numeric text_area"';
+ $onchange = ' onchange="document.getElementById(\'' . $this->id . '_unlimited\').checked=document.getElementById(\'' . $this->id
+ . '\').value==\'\';"';
+ $onclick = ' onclick="if (document.getElementById(\'' . $this->id . '_unlimited\').checked) document.getElementById(\'' . $this->id
+ . '\').value=\'\';"';
+ $value = empty($this->value) ? '' : $this->value;
+ $checked = empty($this->value) ? ' checked="checked"' : '';
- return ' '
- . ' '
- . '' . Text::_('COM_BANNERS_UNLIMITED') . ' ';
- }
+ return ' '
+ . ' '
+ . '' . Text::_('COM_BANNERS_UNLIMITED') . ' ';
+ }
}
diff --git a/administrator/components/com_banners/src/Helper/BannersHelper.php b/administrator/components/com_banners/src/Helper/BannersHelper.php
index 824318b5beadd..7c2b4f1b8a496 100644
--- a/administrator/components/com_banners/src/Helper/BannersHelper.php
+++ b/administrator/components/com_banners/src/Helper/BannersHelper.php
@@ -1,4 +1,5 @@
getIdentity();
-
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName('#__banners'))
- ->where(
- [
- $db->quoteName('reset') . ' <= :date',
- $db->quoteName('reset') . ' IS NOT NULL',
- ]
- )
- ->bind(':date', $date)
- ->extendWhere(
- 'AND',
- [
- $db->quoteName('checked_out') . ' IS NULL',
- $db->quoteName('checked_out') . ' = :userId',
- ],
- 'OR'
- )
- ->bind(':userId', $user->id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $rows = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- foreach ($rows as $row)
- {
- $purchaseType = $row->purchase_type;
-
- if ($purchaseType < 0 && $row->cid)
- {
- /** @var \Joomla\Component\Banners\Administrator\Table\ClientTable $client */
- $client = Table::getInstance('ClientTable', '\\Joomla\\Component\\Banners\\Administrator\\Table\\');
- $client->load($row->cid);
- $purchaseType = $client->purchase_type;
- }
-
- if ($purchaseType < 0)
- {
- $params = ComponentHelper::getParams('com_banners');
- $purchaseType = $params->get('purchase_type');
- }
-
- switch ($purchaseType)
- {
- case 1:
- $reset = null;
- break;
- case 2:
- $date = Factory::getDate('+1 year ' . date('Y-m-d'));
- $reset = $date->toSql();
- break;
- case 3:
- $date = Factory::getDate('+1 month ' . date('Y-m-d'));
- $reset = $date->toSql();
- break;
- case 4:
- $date = Factory::getDate('+7 day ' . date('Y-m-d'));
- $reset = $date->toSql();
- break;
- case 5:
- $date = Factory::getDate('+1 day ' . date('Y-m-d'));
- $reset = $date->toSql();
- break;
- }
-
- // Update the row ordering field.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__banners'))
- ->set(
- [
- $db->quoteName('reset') . ' = :reset',
- $db->quoteName('impmade') . ' = 0',
- $db->quoteName('clicks') . ' = 0',
- ]
- )
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':reset', $reset, $reset === null ? ParameterType::NULL : ParameterType::STRING)
- ->bind(':id', $row->id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Get client list in text/value format for a select field
- *
- * @return array
- */
- public static function getClientOptions()
- {
- $options = array();
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id', 'value'),
- $db->quoteName('name', 'text'),
- ]
- )
- ->from($db->quoteName('#__banner_clients', 'a'))
- ->where($db->quoteName('a.state') . ' = 1')
- ->order($db->quoteName('a.name'));
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('COM_BANNERS_NO_CLIENT')));
-
- return $options;
- }
+ /**
+ * Update / reset the banners
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ public static function updateReset()
+ {
+ $db = Factory::getDbo();
+ $date = Factory::getDate();
+ $app = Factory::getApplication();
+ $user = $app->getIdentity();
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__banners'))
+ ->where(
+ [
+ $db->quoteName('reset') . ' <= :date',
+ $db->quoteName('reset') . ' IS NOT NULL',
+ ]
+ )
+ ->bind(':date', $date)
+ ->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('checked_out') . ' IS NULL',
+ $db->quoteName('checked_out') . ' = :userId',
+ ],
+ 'OR'
+ )
+ ->bind(':userId', $user->id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $rows = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ foreach ($rows as $row) {
+ $purchaseType = $row->purchase_type;
+
+ if ($purchaseType < 0 && $row->cid) {
+ /** @var \Joomla\Component\Banners\Administrator\Table\ClientTable $client */
+ $client = Table::getInstance('ClientTable', '\\Joomla\\Component\\Banners\\Administrator\\Table\\');
+ $client->load($row->cid);
+ $purchaseType = $client->purchase_type;
+ }
+
+ if ($purchaseType < 0) {
+ $params = ComponentHelper::getParams('com_banners');
+ $purchaseType = $params->get('purchase_type');
+ }
+
+ switch ($purchaseType) {
+ case 1:
+ $reset = null;
+ break;
+ case 2:
+ $date = Factory::getDate('+1 year ' . date('Y-m-d'));
+ $reset = $date->toSql();
+ break;
+ case 3:
+ $date = Factory::getDate('+1 month ' . date('Y-m-d'));
+ $reset = $date->toSql();
+ break;
+ case 4:
+ $date = Factory::getDate('+7 day ' . date('Y-m-d'));
+ $reset = $date->toSql();
+ break;
+ case 5:
+ $date = Factory::getDate('+1 day ' . date('Y-m-d'));
+ $reset = $date->toSql();
+ break;
+ }
+
+ // Update the row ordering field.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__banners'))
+ ->set(
+ [
+ $db->quoteName('reset') . ' = :reset',
+ $db->quoteName('impmade') . ' = 0',
+ $db->quoteName('clicks') . ' = 0',
+ ]
+ )
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':reset', $reset, $reset === null ? ParameterType::NULL : ParameterType::STRING)
+ ->bind(':id', $row->id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get client list in text/value format for a select field
+ *
+ * @return array
+ */
+ public static function getClientOptions()
+ {
+ $options = array();
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id', 'value'),
+ $db->quoteName('name', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__banner_clients', 'a'))
+ ->where($db->quoteName('a.state') . ' = 1')
+ ->order($db->quoteName('a.name'));
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('COM_BANNERS_NO_CLIENT')));
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_banners/src/Model/BannerModel.php b/administrator/components/com_banners/src/Model/BannerModel.php
index 92e34088576cf..e4ee583f465a6 100644
--- a/administrator/components/com_banners/src/Model/BannerModel.php
+++ b/administrator/components/com_banners/src/Model/BannerModel.php
@@ -1,4 +1,5 @@
'batchClient',
- 'language_id' => 'batchLanguage'
- );
-
- /**
- * Batch client changes for a group of banners.
- *
- * @param string $value The new value matching a client.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- protected function batchClient($value, $pks, $contexts)
- {
- // Set the variables
- $user = Factory::getUser();
-
- /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $table */
- $table = $this->getTable();
-
- foreach ($pks as $pk)
- {
- if (!$user->authorise('core.edit', $contexts[$pk]))
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
-
- return false;
- }
-
- $table->reset();
- $table->load($pk);
- $table->cid = (int) $value;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->state != -2)
- {
- return false;
- }
-
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.delete', 'com_banners.category.' . (int) $record->catid);
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * A method to preprocess generating a new title in order to allow tables with alternative names
- * for alias and title to use the batch move and copy methods
- *
- * @param integer $categoryId The target category id
- * @param Table $table The JTable within which move or copy is taking place
- *
- * @return void
- *
- * @since 3.8.12
- */
- public function generateTitle($categoryId, $table)
- {
- // Alter the title & alias
- $data = $this->generateNewTitle($categoryId, $table->alias, $table->name);
- $table->name = $data['0'];
- $table->alias = $data['1'];
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- // Check against the category.
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.edit.state', 'com_banners.category.' . (int) $record->catid);
- }
-
- // Default to component settings if category not known.
- return parent::canEditState($record);
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form. [optional]
- * @param boolean $loadData True if the form is to load its own data (default case), false if not. [optional]
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_banners.banner', 'banner', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('publish_up', 'disabled', 'true');
- $form->setFieldAttribute('publish_down', 'disabled', 'true');
- $form->setFieldAttribute('state', 'disabled', 'true');
- $form->setFieldAttribute('sticky', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('publish_up', 'filter', 'unset');
- $form->setFieldAttribute('publish_down', 'filter', 'unset');
- $form->setFieldAttribute('state', 'filter', 'unset');
- $form->setFieldAttribute('sticky', 'filter', 'unset');
- }
-
- // Don't allow to change the created_by user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_by', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $app = Factory::getApplication();
- $data = $app->getUserState('com_banners.edit.banner.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Prime some default values.
- if ($this->getState('banner.id') == 0)
- {
- $filters = (array) $app->getUserState('com_banners.banners.filter');
- $filterCatId = $filters['category_id'] ?? null;
-
- $data->set('catid', $app->input->getInt('catid', $filterCatId));
- }
- }
-
- $this->preprocessData('com_banners.banner', $data);
-
- return $data;
- }
-
- /**
- * Method to stick records.
- *
- * @param array $pks The ids of the items to publish.
- * @param integer $value The value of the published state
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function stick(&$pks, $value = 1)
- {
- /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $table */
- $table = $this->getTable();
- $pks = (array) $pks;
-
- // Access checks.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- if (!$this->canEditState($table))
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
- }
- }
- }
-
- // Attempt to change the state of the records.
- if (!$table->stick($pks, $value, Factory::getUser()->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- return true;
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param Table $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('catid') . ' = ' . (int) $table->catid,
- $db->quoteName('state') . ' >= 0',
- ];
- }
-
- /**
- * Prepare and sanitise the table prior to saving.
- *
- * @param Table $table A Table object.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- $date = Factory::getDate();
- $user = Factory::getUser();
-
- if (empty($table->id))
- {
- // Set the values
- $table->created = $date->toSql();
- $table->created_by = $user->id;
-
- // Set ordering to the last item if not set
- if (empty($table->ordering))
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('MAX(' . $db->quoteName('ordering') . ')')
- ->from($db->quoteName('#__banners'));
-
- $db->setQuery($query);
- $max = $db->loadResult();
-
- $table->ordering = $max + 1;
- }
- }
- else
- {
- // Set the values
- $table->modified = $date->toSql();
- $table->modified_by = $user->id;
- }
-
- // Increment the content version number.
- $table->version++;
- }
-
- /**
- * Allows preprocessing of the Form object.
- *
- * @param Form $form The form object
- * @param array $data The data to be merged into the form object
- * @param string $group The plugin group to be executed
- *
- * @return void
- *
- * @since 3.6.1
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- if ($this->canCreateCategory())
- {
- $form->setFieldAttribute('catid', 'allowAdd', 'true');
-
- // Add a prefix for categories created on the fly.
- $form->setFieldAttribute('catid', 'customPrefix', '#new#');
- }
-
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $input = Factory::getApplication()->input;
-
- // Create new category, if needed.
- $createCategory = true;
-
- // If category ID is provided, check if it's valid.
- if (is_numeric($data['catid']) && $data['catid'])
- {
- $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_banners');
- }
-
- // Save New Category
- if ($createCategory && $this->canCreateCategory())
- {
- $category = [
- // Remove #new# prefix, if exists.
- 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
- 'parent_id' => 1,
- 'extension' => 'com_banners',
- 'language' => $data['language'],
- 'published' => 1,
- ];
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
- $categoryModel = Factory::getApplication()->bootComponent('com_categories')
- ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
-
- // Create new category.
- if (!$categoryModel->save($category))
- {
- $this->setError($categoryModel->getError());
-
- return false;
- }
-
- // Get the new category ID.
- $data['catid'] = $categoryModel->getState('category.id');
- }
-
- // Alter the name for save as copy
- if ($input->get('task') == 'save2copy')
- {
- /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $origTable */
- $origTable = clone $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['name'] == $origTable->name)
- {
- list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
- $data['name'] = $name;
- $data['alias'] = $alias;
- }
- else
- {
- if ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
- }
-
- $data['state'] = 0;
- }
-
- return parent::save($data);
- }
-
- /**
- * Is the user allowed to create an on the fly category?
- *
- * @return boolean
- *
- * @since 3.6.1
- */
- private function canCreateCategory()
- {
- return Factory::getUser()->authorise('core.create', 'com_banners');
- }
+ use VersionableModelTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_BANNERS_BANNER';
+
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_banners.banner';
+
+ /**
+ * Batch copy/move command. If set to false, the batch copy/move command is not supported
+ *
+ * @var string
+ */
+ protected $batch_copymove = 'category_id';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'client_id' => 'batchClient',
+ 'language_id' => 'batchLanguage'
+ );
+
+ /**
+ * Batch client changes for a group of banners.
+ *
+ * @param string $value The new value matching a client.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ protected function batchClient($value, $pks, $contexts)
+ {
+ // Set the variables
+ $user = Factory::getUser();
+
+ /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $table */
+ $table = $this->getTable();
+
+ foreach ($pks as $pk) {
+ if (!$user->authorise('core.edit', $contexts[$pk])) {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+
+ $table->reset();
+ $table->load($pk);
+ $table->cid = (int) $value;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->state != -2) {
+ return false;
+ }
+
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.delete', 'com_banners.category.' . (int) $record->catid);
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * A method to preprocess generating a new title in order to allow tables with alternative names
+ * for alias and title to use the batch move and copy methods
+ *
+ * @param integer $categoryId The target category id
+ * @param Table $table The JTable within which move or copy is taking place
+ *
+ * @return void
+ *
+ * @since 3.8.12
+ */
+ public function generateTitle($categoryId, $table)
+ {
+ // Alter the title & alias
+ $data = $this->generateNewTitle($categoryId, $table->alias, $table->name);
+ $table->name = $data['0'];
+ $table->alias = $data['1'];
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ // Check against the category.
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.edit.state', 'com_banners.category.' . (int) $record->catid);
+ }
+
+ // Default to component settings if category not known.
+ return parent::canEditState($record);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form. [optional]
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not. [optional]
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_banners.banner', 'banner', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('publish_up', 'disabled', 'true');
+ $form->setFieldAttribute('publish_down', 'disabled', 'true');
+ $form->setFieldAttribute('state', 'disabled', 'true');
+ $form->setFieldAttribute('sticky', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('publish_up', 'filter', 'unset');
+ $form->setFieldAttribute('publish_down', 'filter', 'unset');
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ $form->setFieldAttribute('sticky', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $app = Factory::getApplication();
+ $data = $app->getUserState('com_banners.edit.banner.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Prime some default values.
+ if ($this->getState('banner.id') == 0) {
+ $filters = (array) $app->getUserState('com_banners.banners.filter');
+ $filterCatId = $filters['category_id'] ?? null;
+
+ $data->set('catid', $app->input->getInt('catid', $filterCatId));
+ }
+ }
+
+ $this->preprocessData('com_banners.banner', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to stick records.
+ *
+ * @param array $pks The ids of the items to publish.
+ * @param integer $value The value of the published state
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function stick(&$pks, $value = 1)
+ {
+ /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $table */
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ // Access checks.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ if (!$this->canEditState($table)) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
+ }
+ }
+ }
+
+ // Attempt to change the state of the records.
+ if (!$table->stick($pks, $value, Factory::getUser()->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param Table $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('catid') . ' = ' . (int) $table->catid,
+ $db->quoteName('state') . ' >= 0',
+ ];
+ }
+
+ /**
+ * Prepare and sanitise the table prior to saving.
+ *
+ * @param Table $table A Table object.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ $date = Factory::getDate();
+ $user = Factory::getUser();
+
+ if (empty($table->id)) {
+ // Set the values
+ $table->created = $date->toSql();
+ $table->created_by = $user->id;
+
+ // Set ordering to the last item if not set
+ if (empty($table->ordering)) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('MAX(' . $db->quoteName('ordering') . ')')
+ ->from($db->quoteName('#__banners'));
+
+ $db->setQuery($query);
+ $max = $db->loadResult();
+
+ $table->ordering = $max + 1;
+ }
+ } else {
+ // Set the values
+ $table->modified = $date->toSql();
+ $table->modified_by = $user->id;
+ }
+
+ // Increment the content version number.
+ $table->version++;
+ }
+
+ /**
+ * Allows preprocessing of the Form object.
+ *
+ * @param Form $form The form object
+ * @param array $data The data to be merged into the form object
+ * @param string $group The plugin group to be executed
+ *
+ * @return void
+ *
+ * @since 3.6.1
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ if ($this->canCreateCategory()) {
+ $form->setFieldAttribute('catid', 'allowAdd', 'true');
+
+ // Add a prefix for categories created on the fly.
+ $form->setFieldAttribute('catid', 'customPrefix', '#new#');
+ }
+
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $input = Factory::getApplication()->input;
+
+ // Create new category, if needed.
+ $createCategory = true;
+
+ // If category ID is provided, check if it's valid.
+ if (is_numeric($data['catid']) && $data['catid']) {
+ $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_banners');
+ }
+
+ // Save New Category
+ if ($createCategory && $this->canCreateCategory()) {
+ $category = [
+ // Remove #new# prefix, if exists.
+ 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
+ 'parent_id' => 1,
+ 'extension' => 'com_banners',
+ 'language' => $data['language'],
+ 'published' => 1,
+ ];
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
+ $categoryModel = Factory::getApplication()->bootComponent('com_categories')
+ ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
+
+ // Create new category.
+ if (!$categoryModel->save($category)) {
+ $this->setError($categoryModel->getError());
+
+ return false;
+ }
+
+ // Get the new category ID.
+ $data['catid'] = $categoryModel->getState('category.id');
+ }
+
+ // Alter the name for save as copy
+ if ($input->get('task') == 'save2copy') {
+ /** @var \Joomla\Component\Banners\Administrator\Table\BannerTable $origTable */
+ $origTable = clone $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['name'] == $origTable->name) {
+ list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
+ $data['name'] = $name;
+ $data['alias'] = $alias;
+ } else {
+ if ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+ }
+
+ $data['state'] = 0;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Is the user allowed to create an on the fly category?
+ *
+ * @return boolean
+ *
+ * @since 3.6.1
+ */
+ private function canCreateCategory()
+ {
+ return Factory::getUser()->authorise('core.create', 'com_banners');
+ }
}
diff --git a/administrator/components/com_banners/src/Model/BannersModel.php b/administrator/components/com_banners/src/Model/BannersModel.php
index 03a7307861c01..c8def963266d9 100644
--- a/administrator/components/com_banners/src/Model/BannersModel.php
+++ b/administrator/components/com_banners/src/Model/BannersModel.php
@@ -1,4 +1,5 @@
cache['categoryorders']))
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- 'MAX(' . $db->quoteName('ordering') . ') AS ' . $db->quoteName('max'),
- $db->quoteName('catid'),
- ]
- )
- ->from($db->quoteName('#__banners'))
- ->group($db->quoteName('catid'));
- $db->setQuery($query);
- $this->cache['categoryorders'] = $db->loadAssocList('catid', 0);
- }
+ /**
+ * Method to get the maximum ordering value for each category.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function &getCategoryOrders()
+ {
+ if (!isset($this->cache['categoryorders'])) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'MAX(' . $db->quoteName('ordering') . ') AS ' . $db->quoteName('max'),
+ $db->quoteName('catid'),
+ ]
+ )
+ ->from($db->quoteName('#__banners'))
+ ->group($db->quoteName('catid'));
+ $db->setQuery($query);
+ $this->cache['categoryorders'] = $db->loadAssocList('catid', 0);
+ }
- return $this->cache['categoryorders'];
- }
+ return $this->cache['categoryorders'];
+ }
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.name'),
- $db->quoteName('a.alias'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.catid'),
- $db->quoteName('a.clicks'),
- $db->quoteName('a.metakey'),
- $db->quoteName('a.sticky'),
- $db->quoteName('a.impmade'),
- $db->quoteName('a.imptotal'),
- $db->quoteName('a.state'),
- $db->quoteName('a.ordering'),
- $db->quoteName('a.purchase_type'),
- $db->quoteName('a.language'),
- $db->quoteName('a.publish_up'),
- $db->quoteName('a.publish_down'),
- ]
- )
- )
- ->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- $db->quoteName('uc.name', 'editor'),
- $db->quoteName('c.title', 'category_title'),
- $db->quoteName('cl.name', 'client_name'),
- $db->quoteName('cl.purchase_type', 'client_purchase_type'),
- ]
- )
- ->from($db->quoteName('#__banners', 'a'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
- ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'))
- ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'));
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.name'),
+ $db->quoteName('a.alias'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.catid'),
+ $db->quoteName('a.clicks'),
+ $db->quoteName('a.metakey'),
+ $db->quoteName('a.sticky'),
+ $db->quoteName('a.impmade'),
+ $db->quoteName('a.imptotal'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.ordering'),
+ $db->quoteName('a.purchase_type'),
+ $db->quoteName('a.language'),
+ $db->quoteName('a.publish_up'),
+ $db->quoteName('a.publish_down'),
+ ]
+ )
+ )
+ ->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ $db->quoteName('uc.name', 'editor'),
+ $db->quoteName('c.title', 'category_title'),
+ $db->quoteName('cl.name', 'client_name'),
+ $db->quoteName('cl.purchase_type', 'client_purchase_type'),
+ ]
+ )
+ ->from($db->quoteName('#__banners', 'a'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
+ ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'))
+ ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'));
- // Filter by published state
- $published = (string) $this->getState('filter.published');
+ // Filter by published state
+ $published = (string) $this->getState('filter.published');
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.state') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->where($db->quoteName('a.state') . ' IN (0, 1)');
- }
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.state') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->where($db->quoteName('a.state') . ' IN (0, 1)');
+ }
- // Filter by category.
- $categoryId = $this->getState('filter.category_id');
+ // Filter by category.
+ $categoryId = $this->getState('filter.category_id');
- if (is_numeric($categoryId))
- {
- $categoryId = (int) $categoryId;
- $query->where($db->quoteName('a.catid') . ' = :categoryId')
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- }
+ if (is_numeric($categoryId)) {
+ $categoryId = (int) $categoryId;
+ $query->where($db->quoteName('a.catid') . ' = :categoryId')
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ }
- // Filter by client.
- $clientId = $this->getState('filter.client_id');
+ // Filter by client.
+ $clientId = $this->getState('filter.client_id');
- if (is_numeric($clientId))
- {
- $clientId = (int) $clientId;
- $query->where($db->quoteName('a.cid') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
+ if (is_numeric($clientId)) {
+ $clientId = (int) $clientId;
+ $query->where($db->quoteName('a.cid') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
- // Filter by search in title
- if ($search = $this->getState('filter.search'))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName('a.name') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
- }
+ // Filter by search in title
+ if ($search = $this->getState('filter.search')) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName('a.name') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+ }
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
- // Filter on the level.
- if ($level = (int) $this->getState('filter.level'))
- {
- $query->where($db->quoteName('c.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
+ // Filter on the level.
+ if ($level = (int) $this->getState('filter.level')) {
+ $query->where($db->quoteName('c.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 'a.name');
- $orderDirn = $this->state->get('list.direction', 'ASC');
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 'a.name');
+ $orderDirn = $this->state->get('list.direction', 'ASC');
- if ($orderCol === 'a.ordering' || $orderCol === 'category_title')
- {
- $ordering = [
- $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
- $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
- ];
- }
- else
- {
- if ($orderCol === 'client_name')
- {
- $orderCol = 'cl.name';
- }
+ if ($orderCol === 'a.ordering' || $orderCol === 'category_title') {
+ $ordering = [
+ $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
+ $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
+ ];
+ } else {
+ if ($orderCol === 'client_name') {
+ $orderCol = 'cl.name';
+ }
- $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
- }
+ $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
+ }
- $query->order($ordering);
+ $query->order($ordering);
- return $query;
- }
+ return $query;
+ }
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.category_id');
- $id .= ':' . $this->getState('filter.client_id');
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . $this->getState('filter.level');
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.category_id');
+ $id .= ':' . $this->getState('filter.client_id');
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.level');
- return parent::getStoreId($id);
- }
+ return parent::getStoreId($id);
+ }
- /**
- * Returns a reference to the a Table object, always creating it.
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 1.6
- */
- public function getTable($type = 'Banner', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
+ /**
+ * Returns a reference to the a Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'Banner', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.name', $direction = 'asc')
- {
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_banners'));
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.name', $direction = 'asc')
+ {
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_banners'));
- // List state information.
- parent::populateState($ordering, $direction);
- }
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
}
diff --git a/administrator/components/com_banners/src/Model/ClientModel.php b/administrator/components/com_banners/src/Model/ClientModel.php
index bbe474d565744..87178304a3bb4 100644
--- a/administrator/components/com_banners/src/Model/ClientModel.php
+++ b/administrator/components/com_banners/src/Model/ClientModel.php
@@ -1,4 +1,5 @@
id) || $record->state != -2)
- {
- return false;
- }
-
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.delete', 'com_banners.category.' . (int) $record->catid);
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record.
- * Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
-
- if (!empty($record->catid))
- {
- return $user->authorise('core.edit.state', 'com_banners.category.' . (int) $record->catid);
- }
-
- return $user->authorise('core.edit.state', 'com_banners');
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_banners.client', 'client', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_banners.edit.client.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_banners.client', $data);
-
- return $data;
- }
-
- /**
- * Prepare and sanitise the table prior to saving.
- *
- * @param Table $table A Table object.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
- }
+ use VersionableModelTrait;
+
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_banners.client';
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->state != -2) {
+ return false;
+ }
+
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.delete', 'com_banners.category.' . (int) $record->catid);
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record.
+ * Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+
+ if (!empty($record->catid)) {
+ return $user->authorise('core.edit.state', 'com_banners.category.' . (int) $record->catid);
+ }
+
+ return $user->authorise('core.edit.state', 'com_banners');
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_banners.client', 'client', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_banners.edit.client.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_banners.client', $data);
+
+ return $data;
+ }
+
+ /**
+ * Prepare and sanitise the table prior to saving.
+ *
+ * @param Table $table A Table object.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
+ }
}
diff --git a/administrator/components/com_banners/src/Model/ClientsModel.php b/administrator/components/com_banners/src/Model/ClientsModel.php
index fea2e7b870bf4..89234ba998155 100644
--- a/administrator/components/com_banners/src/Model/ClientsModel.php
+++ b/administrator/components/com_banners/src/Model/ClientsModel.php
@@ -1,4 +1,5 @@
setState('params', ComponentHelper::getParams('com_banners'));
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.purchase_type');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $defaultPurchase = (int) ComponentHelper::getParams('com_banners')->get('purchase_type', 3);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.name'),
- $db->quoteName('a.contact'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.state'),
- $db->quoteName('a.metakey'),
- $db->quoteName('a.purchase_type'),
- ]
- )
- )
- ->select(
- [
- 'COUNT(' . $db->quoteName('b.id') . ') AS ' . $db->quoteName('nbanners'),
- $db->quoteName('uc.name', 'editor'),
- ]
- );
-
- $query->from($db->quoteName('#__banner_clients', 'a'));
-
- // Join over the banners for counting
- $query->join('LEFT', $db->quoteName('#__banners', 'b'), $db->quoteName('a.id') . ' = ' . $db->quoteName('b.cid'));
-
- // Join over the users for the checked out user.
- $query->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
-
- // Filter by published state
- $published = (string) $this->getState('filter.state');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.state') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->where($db->quoteName('a.state') . ' IN (0, 1)');
- }
-
- $query->group(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.name'),
- $db->quoteName('a.contact'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.state'),
- $db->quoteName('a.metakey'),
- $db->quoteName('a.purchase_type'),
- $db->quoteName('uc.name'),
- ]
- );
-
- // Filter by search in title
- if ($search = trim($this->getState('filter.search', '')))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', $search) . '%';
- $query->where($db->quoteName('a.name') . ' LIKE :search')
- ->bind(':search', $search);
- }
- }
-
- // Filter by purchase type
- if ($purchaseType = (int) $this->getState('filter.purchase_type'))
- {
- if ($defaultPurchase === $purchaseType)
- {
- $query->where('(' . $db->quoteName('a.purchase_type') . ' = :type OR ' . $db->quoteName('a.purchase_type') . ' = -1)');
- }
- else
- {
- $query->where($db->quoteName('a.purchase_type') . ' = :type');
- }
-
- $query->bind(':type', $purchaseType, ParameterType::INTEGER);
- }
-
- // Add the list ordering clause.
- $query->order(
- $db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
- );
-
- return $query;
- }
-
- /**
- * Overrides the getItems method to attach additional metrics to the list.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 3.6
- */
- public function getItems()
- {
- // Get a storage key.
- $store = $this->getStoreId('getItems');
-
- // Try to load the data from internal storage.
- if (!empty($this->cache[$store]))
- {
- return $this->cache[$store];
- }
-
- // Load the list items.
- $items = parent::getItems();
-
- // If empty or an error, just return.
- if (empty($items))
- {
- return array();
- }
-
- // Getting the following metric by joins is WAY TOO SLOW.
- // Faster to do three queries for very large banner trees.
-
- // Get the clients in the list.
- $db = $this->getDatabase();
- $clientIds = array_column($items, 'id');
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('cid'),
- 'COUNT(' . $db->quoteName('cid') . ') AS ' . $db->quoteName('count_published'),
- ]
- )
- ->from($db->quoteName('#__banners'))
- ->where($db->quoteName('state') . ' = :state')
- ->whereIn($db->quoteName('cid'), $clientIds)
- ->group($db->quoteName('cid'))
- ->bind(':state', $state, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- // Get the published banners count.
- try
- {
- $state = 1;
- $countPublished = $db->loadAssocList('cid', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get the unpublished banners count.
- try
- {
- $state = 0;
- $countUnpublished = $db->loadAssocList('cid', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get the trashed banners count.
- try
- {
- $state = -2;
- $countTrashed = $db->loadAssocList('cid', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get the archived banners count.
- try
- {
- $state = 2;
- $countArchived = $db->loadAssocList('cid', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Inject the values back into the array.
- foreach ($items as $item)
- {
- $item->count_published = isset($countPublished[$item->id]) ? $countPublished[$item->id] : 0;
- $item->count_unpublished = isset($countUnpublished[$item->id]) ? $countUnpublished[$item->id] : 0;
- $item->count_trashed = isset($countTrashed[$item->id]) ? $countTrashed[$item->id] : 0;
- $item->count_archived = isset($countArchived[$item->id]) ? $countArchived[$item->id] : 0;
- }
-
- // Add the items to the internal cache.
- $this->cache[$store] = $items;
-
- return $this->cache[$store];
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'name', 'a.name',
+ 'contact', 'a.contact',
+ 'state', 'a.state',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'purchase_type', 'a.purchase_type'
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.name', $direction = 'asc')
+ {
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_banners'));
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.purchase_type');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $defaultPurchase = (int) ComponentHelper::getParams('com_banners')->get('purchase_type', 3);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.name'),
+ $db->quoteName('a.contact'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.metakey'),
+ $db->quoteName('a.purchase_type'),
+ ]
+ )
+ )
+ ->select(
+ [
+ 'COUNT(' . $db->quoteName('b.id') . ') AS ' . $db->quoteName('nbanners'),
+ $db->quoteName('uc.name', 'editor'),
+ ]
+ );
+
+ $query->from($db->quoteName('#__banner_clients', 'a'));
+
+ // Join over the banners for counting
+ $query->join('LEFT', $db->quoteName('#__banners', 'b'), $db->quoteName('a.id') . ' = ' . $db->quoteName('b.cid'));
+
+ // Join over the users for the checked out user.
+ $query->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
+
+ // Filter by published state
+ $published = (string) $this->getState('filter.state');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.state') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->where($db->quoteName('a.state') . ' IN (0, 1)');
+ }
+
+ $query->group(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.name'),
+ $db->quoteName('a.contact'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.metakey'),
+ $db->quoteName('a.purchase_type'),
+ $db->quoteName('uc.name'),
+ ]
+ );
+
+ // Filter by search in title
+ if ($search = trim($this->getState('filter.search', ''))) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', $search) . '%';
+ $query->where($db->quoteName('a.name') . ' LIKE :search')
+ ->bind(':search', $search);
+ }
+ }
+
+ // Filter by purchase type
+ if ($purchaseType = (int) $this->getState('filter.purchase_type')) {
+ if ($defaultPurchase === $purchaseType) {
+ $query->where('(' . $db->quoteName('a.purchase_type') . ' = :type OR ' . $db->quoteName('a.purchase_type') . ' = -1)');
+ } else {
+ $query->where($db->quoteName('a.purchase_type') . ' = :type');
+ }
+
+ $query->bind(':type', $purchaseType, ParameterType::INTEGER);
+ }
+
+ // Add the list ordering clause.
+ $query->order(
+ $db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
+ );
+
+ return $query;
+ }
+
+ /**
+ * Overrides the getItems method to attach additional metrics to the list.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 3.6
+ */
+ public function getItems()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId('getItems');
+
+ // Try to load the data from internal storage.
+ if (!empty($this->cache[$store])) {
+ return $this->cache[$store];
+ }
+
+ // Load the list items.
+ $items = parent::getItems();
+
+ // If empty or an error, just return.
+ if (empty($items)) {
+ return array();
+ }
+
+ // Getting the following metric by joins is WAY TOO SLOW.
+ // Faster to do three queries for very large banner trees.
+
+ // Get the clients in the list.
+ $db = $this->getDatabase();
+ $clientIds = array_column($items, 'id');
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('cid'),
+ 'COUNT(' . $db->quoteName('cid') . ') AS ' . $db->quoteName('count_published'),
+ ]
+ )
+ ->from($db->quoteName('#__banners'))
+ ->where($db->quoteName('state') . ' = :state')
+ ->whereIn($db->quoteName('cid'), $clientIds)
+ ->group($db->quoteName('cid'))
+ ->bind(':state', $state, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ // Get the published banners count.
+ try {
+ $state = 1;
+ $countPublished = $db->loadAssocList('cid', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get the unpublished banners count.
+ try {
+ $state = 0;
+ $countUnpublished = $db->loadAssocList('cid', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get the trashed banners count.
+ try {
+ $state = -2;
+ $countTrashed = $db->loadAssocList('cid', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get the archived banners count.
+ try {
+ $state = 2;
+ $countArchived = $db->loadAssocList('cid', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Inject the values back into the array.
+ foreach ($items as $item) {
+ $item->count_published = isset($countPublished[$item->id]) ? $countPublished[$item->id] : 0;
+ $item->count_unpublished = isset($countUnpublished[$item->id]) ? $countUnpublished[$item->id] : 0;
+ $item->count_trashed = isset($countTrashed[$item->id]) ? $countTrashed[$item->id] : 0;
+ $item->count_archived = isset($countArchived[$item->id]) ? $countArchived[$item->id] : 0;
+ }
+
+ // Add the items to the internal cache.
+ $this->cache[$store] = $items;
+
+ return $this->cache[$store];
+ }
}
diff --git a/administrator/components/com_banners/src/Model/DownloadModel.php b/administrator/components/com_banners/src/Model/DownloadModel.php
index 5cd2d4ff61a11..c4502ddcad978 100644
--- a/administrator/components/com_banners/src/Model/DownloadModel.php
+++ b/administrator/components/com_banners/src/Model/DownloadModel.php
@@ -1,4 +1,5 @@
input;
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $input = Factory::getApplication()->input;
- $this->setState('basename', $input->cookie->getString(ApplicationHelper::getHash($this->_context . '.basename'), '__SITE__'));
- $this->setState('compressed', $input->cookie->getInt(ApplicationHelper::getHash($this->_context . '.compressed'), 1));
- }
+ $this->setState('basename', $input->cookie->getString(ApplicationHelper::getHash($this->_context . '.basename'), '__SITE__'));
+ $this->setState('compressed', $input->cookie->getInt(ApplicationHelper::getHash($this->_context . '.compressed'), 1));
+ }
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_banners.download', 'download', array('control' => 'jform', 'load_data' => $loadData));
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_banners.download', 'download', array('control' => 'jform', 'load_data' => $loadData));
- if (empty($form))
- {
- return false;
- }
+ if (empty($form)) {
+ return false;
+ }
- return $form;
- }
+ return $form;
+ }
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- $data = (object) array(
- 'basename' => $this->getState('basename'),
- 'compressed' => $this->getState('compressed'),
- );
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ $data = (object) array(
+ 'basename' => $this->getState('basename'),
+ 'compressed' => $this->getState('compressed'),
+ );
- $this->preprocessData('com_banners.download', $data);
+ $this->preprocessData('com_banners.download', $data);
- return $data;
- }
+ return $data;
+ }
}
diff --git a/administrator/components/com_banners/src/Model/TracksModel.php b/administrator/components/com_banners/src/Model/TracksModel.php
index b9e56e73e6c42..0e25c6e409016 100644
--- a/administrator/components/com_banners/src/Model/TracksModel.php
+++ b/administrator/components/com_banners/src/Model/TracksModel.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Banners\Administrator\Model;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Banners\Administrator\Model;
use Joomla\Archive\Archive;
use Joomla\CMS\Component\ComponentHelper;
@@ -27,524 +27,464 @@
*/
class TracksModel extends ListModel
{
- /**
- * The base name
- *
- * @var string
- * @since 1.6
- */
- protected $basename;
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- *
- * @since 1.6
- */
- public function __construct($config = array())
- {
- if (empty($config['filter_fields']))
- {
- $config['filter_fields'] = array(
- 'b.name', 'banner_name',
- 'cl.name', 'client_name', 'client_id',
- 'c.title', 'category_title', 'category_id',
- 'track_type', 'a.track_type', 'type',
- 'count', 'a.count',
- 'track_date', 'a.track_date', 'end', 'begin',
- 'level', 'c.level',
- );
- }
-
- parent::__construct($config);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'b.name', $direction = 'asc')
- {
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_banners'));
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- [
- $db->quoteName('a.track_date'),
- $db->quoteName('a.track_type'),
- $db->quoteName('a.count'),
- $db->quoteName('b.name', 'banner_name'),
- $db->quoteName('cl.name', 'client_name'),
- $db->quoteName('c.title', 'category_title'),
- ]
- );
-
- // From tracks table.
- $query->from($db->quoteName('#__banner_tracks', 'a'));
-
- // Join with the banners.
- $query->join('LEFT', $db->quoteName('#__banners', 'b'), $db->quoteName('b.id') . ' = ' . $db->quoteName('a.banner_id'));
-
- // Join with the client.
- $query->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('b.cid'));
-
- // Join with the category.
- $query->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('b.catid'));
-
- // Filter by type.
-
- if ($type = (int) $this->getState('filter.type'))
- {
- $query->where($db->quoteName('a.track_type') . ' = :type')
- ->bind(':type', $type, ParameterType::INTEGER);
- }
-
- // Filter by client.
- $clientId = $this->getState('filter.client_id');
-
- if (is_numeric($clientId))
- {
- $clientId = (int) $clientId;
- $query->where($db->quoteName('b.cid') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- // Filter by category.
- $categoryId = $this->getState('filter.category_id');
-
- if (is_numeric($categoryId))
- {
- $categoryId = (int) $categoryId;
- $query->where($db->quoteName('b.catid') . ' = :categoryId')
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- }
-
- // Filter by begin date.
- if ($begin = $this->getState('filter.begin'))
- {
- $query->where($db->quoteName('a.track_date') . ' >= :begin')
- ->bind(':begin', $begin);
- }
-
- // Filter by end date.
- if ($end = $this->getState('filter.end'))
- {
- $query->where($db->quoteName('a.track_date') . ' <= :end')
- ->bind(':end', $end);
- }
-
- // Filter on the level.
- if ($level = (int) $this->getState('filter.level'))
- {
- $query->where($db->quoteName('c.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter by search in banner name or client name.
- if ($search = $this->getState('filter.search'))
- {
- $search = '%' . StringHelper::strtolower($search) . '%';
- $query->where('(LOWER(' . $db->quoteName('b.name') . ') LIKE :search1 OR LOWER(' . $db->quoteName('cl.name') . ') LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
-
- // Add the list ordering clause.
- $query->order(
- $db->quoteName($db->escape($this->getState('list.ordering', 'b.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
- );
-
- return $query;
- }
-
- /**
- * Method to delete rows.
- *
- * @return boolean Returns true on success, false on failure.
- */
- public function delete()
- {
- $user = Factory::getUser();
- $categoryId = (int) $this->getState('category_id');
-
- // Access checks.
- if ($categoryId)
- {
- $allow = $user->authorise('core.delete', 'com_banners.category.' . $categoryId);
- }
- else
- {
- $allow = $user->authorise('core.delete', 'com_banners');
- }
-
- if ($allow)
- {
- // Delete tracks from this banner
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__banner_tracks'));
-
- // Filter by type
- if ($type = (int) $this->getState('filter.type'))
- {
- $query->where($db->quoteName('track_type') . ' = :type')
- ->bind(':type', $type, ParameterType::INTEGER);
- }
-
- // Filter by begin date
- if ($begin = $this->getState('filter.begin'))
- {
- $query->where($db->quoteName('track_date') . ' >= :begin')
- ->bind(':begin', $begin);
- }
-
- // Filter by end date
- if ($end = $this->getState('filter.end'))
- {
- $query->where($db->quoteName('track_date') . ' <= :end')
- ->bind(':end', $end);
- }
-
- $subQuery = $db->getQuery(true);
- $subQuery->select($db->quoteName('id'))
- ->from($db->quoteName('#__banners'));
-
- // Filter by client
- if ($clientId = (int) $this->getState('filter.client_id'))
- {
- $subQuery->where($db->quoteName('cid') . ' = :clientId');
- $query->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- // Filter by category
- if ($categoryId)
- {
- $subQuery->where($db->quoteName('catid') . ' = :categoryId');
- $query->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- }
-
- $query->where($db->quoteName('banner_id') . ' IN (' . $subQuery . ')');
-
- $db->setQuery($query);
- $this->setError((string) $query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- else
- {
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
- }
-
- return true;
- }
-
- /**
- * Get file name
- *
- * @return string The file name
- *
- * @since 1.6
- */
- public function getBaseName()
- {
- if (!isset($this->basename))
- {
- $basename = str_replace('__SITE__', Factory::getApplication()->get('sitename'), $this->getState('basename'));
- $categoryId = $this->getState('filter.category_id');
-
- if (is_numeric($categoryId))
- {
- if ($categoryId > 0)
- {
- $basename = str_replace('__CATID__', $categoryId, $basename);
- }
- else
- {
- $basename = str_replace('__CATID__', '', $basename);
- }
-
- $categoryName = $this->getCategoryName();
- $basename = str_replace('__CATNAME__', $categoryName, $basename);
- }
- else
- {
- $basename = str_replace(array('__CATID__', '__CATNAME__'), '', $basename);
- }
-
- $clientId = $this->getState('filter.client_id');
-
- if (is_numeric($clientId))
- {
- if ($clientId > 0)
- {
- $basename = str_replace('__CLIENTID__', $clientId, $basename);
- }
- else
- {
- $basename = str_replace('__CLIENTID__', '', $basename);
- }
-
- $clientName = $this->getClientName();
- $basename = str_replace('__CLIENTNAME__', $clientName, $basename);
- }
- else
- {
- $basename = str_replace(array('__CLIENTID__', '__CLIENTNAME__'), '', $basename);
- }
-
- $type = $this->getState('filter.type');
-
- if ($type > 0)
- {
- $basename = str_replace('__TYPE__', $type, $basename);
- $typeName = Text::_('COM_BANNERS_TYPE' . $type);
- $basename = str_replace('__TYPENAME__', $typeName, $basename);
- }
- else
- {
- $basename = str_replace(array('__TYPE__', '__TYPENAME__'), '', $basename);
- }
-
- $begin = $this->getState('filter.begin');
-
- if (!empty($begin))
- {
- $basename = str_replace('__BEGIN__', $begin, $basename);
- }
- else
- {
- $basename = str_replace('__BEGIN__', '', $basename);
- }
-
- $end = $this->getState('filter.end');
-
- if (!empty($end))
- {
- $basename = str_replace('__END__', $end, $basename);
- }
- else
- {
- $basename = str_replace('__END__', '', $basename);
- }
-
- $this->basename = $basename;
- }
-
- return $this->basename;
- }
-
- /**
- * Get the category name.
- *
- * @return string The category name
- *
- * @since 1.6
- */
- protected function getCategoryName()
- {
- $categoryId = (int) $this->getState('filter.category_id');
-
- if ($categoryId)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('id') . ' = :categoryId')
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $name = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $name;
- }
-
- return Text::_('COM_BANNERS_NOCATEGORYNAME');
- }
-
- /**
- * Get the client name
- *
- * @return string The client name.
- *
- * @since 1.6
- */
- protected function getClientName()
- {
- $clientId = (int) $this->getState('filter.client_id');
-
- if ($clientId)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('name'))
- ->from($db->quoteName('#__banner_clients'))
- ->where($db->quoteName('id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $name = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $name;
- }
-
- return Text::_('COM_BANNERS_NOCLIENTNAME');
- }
-
- /**
- * Get the file type.
- *
- * @return string The file type
- *
- * @since 1.6
- */
- public function getFileType()
- {
- return $this->getState('compressed') ? 'zip' : 'csv';
- }
-
- /**
- * Get the mime type.
- *
- * @return string The mime type.
- *
- * @since 1.6
- */
- public function getMimeType()
- {
- return $this->getState('compressed') ? 'application/zip' : 'text/csv';
- }
-
- /**
- * Get the content
- *
- * @return string The content.
- *
- * @since 1.6
- */
- public function getContent()
- {
- if (!isset($this->content))
- {
- $this->content = '"' . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_NAME')) . '","'
- . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_CLIENT')) . '","'
- . str_replace('"', '""', Text::_('JCATEGORY')) . '","'
- . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_TYPE')) . '","'
- . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_COUNT')) . '","'
- . str_replace('"', '""', Text::_('JDATE')) . '"' . "\n";
-
- foreach ($this->getItems() as $item)
- {
- $this->content .= '"' . str_replace('"', '""', $item->banner_name) . '","'
- . str_replace('"', '""', $item->client_name) . '","'
- . str_replace('"', '""', $item->category_title) . '","'
- . str_replace('"', '""', ($item->track_type == 1 ? Text::_('COM_BANNERS_IMPRESSION') : Text::_('COM_BANNERS_CLICK'))) . '","'
- . str_replace('"', '""', $item->count) . '","'
- . str_replace('"', '""', $item->track_date) . '"' . "\n";
- }
-
- if ($this->getState('compressed'))
- {
- $app = Factory::getApplication();
-
- $files = array(
- 'track' => array(
- 'name' => $this->getBaseName() . '.csv',
- 'data' => $this->content,
- 'time' => time()
- )
- );
- $ziproot = $app->get('tmp_path') . '/' . uniqid('banners_tracks_') . '.zip';
-
- // Run the packager
- $delete = Folder::files($app->get('tmp_path') . '/', uniqid('banners_tracks_'), false, true);
-
- if (!empty($delete))
- {
- if (!File::delete($delete))
- {
- // File::delete throws an error
- $this->setError(Text::_('COM_BANNERS_ERR_ZIP_DELETE_FAILURE'));
-
- return false;
- }
- }
-
- $archive = new Archive;
-
- if (!$packager = $archive->getAdapter('zip'))
- {
- $this->setError(Text::_('COM_BANNERS_ERR_ZIP_ADAPTER_FAILURE'));
-
- return false;
- }
- elseif (!$packager->create($ziproot, $files))
- {
- $this->setError(Text::_('COM_BANNERS_ERR_ZIP_CREATE_FAILURE'));
-
- return false;
- }
-
- $this->content = file_get_contents($ziproot);
-
- // Remove tmp zip file, it's no longer needed.
- File::delete($ziproot);
- }
- }
-
- return $this->content;
- }
+ /**
+ * The base name
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $basename;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'b.name', 'banner_name',
+ 'cl.name', 'client_name', 'client_id',
+ 'c.title', 'category_title', 'category_id',
+ 'track_type', 'a.track_type', 'type',
+ 'count', 'a.count',
+ 'track_date', 'a.track_date', 'end', 'begin',
+ 'level', 'c.level',
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'b.name', $direction = 'asc')
+ {
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_banners'));
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ [
+ $db->quoteName('a.track_date'),
+ $db->quoteName('a.track_type'),
+ $db->quoteName('a.count'),
+ $db->quoteName('b.name', 'banner_name'),
+ $db->quoteName('cl.name', 'client_name'),
+ $db->quoteName('c.title', 'category_title'),
+ ]
+ );
+
+ // From tracks table.
+ $query->from($db->quoteName('#__banner_tracks', 'a'));
+
+ // Join with the banners.
+ $query->join('LEFT', $db->quoteName('#__banners', 'b'), $db->quoteName('b.id') . ' = ' . $db->quoteName('a.banner_id'));
+
+ // Join with the client.
+ $query->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('b.cid'));
+
+ // Join with the category.
+ $query->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('b.catid'));
+
+ // Filter by type.
+
+ if ($type = (int) $this->getState('filter.type')) {
+ $query->where($db->quoteName('a.track_type') . ' = :type')
+ ->bind(':type', $type, ParameterType::INTEGER);
+ }
+
+ // Filter by client.
+ $clientId = $this->getState('filter.client_id');
+
+ if (is_numeric($clientId)) {
+ $clientId = (int) $clientId;
+ $query->where($db->quoteName('b.cid') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ // Filter by category.
+ $categoryId = $this->getState('filter.category_id');
+
+ if (is_numeric($categoryId)) {
+ $categoryId = (int) $categoryId;
+ $query->where($db->quoteName('b.catid') . ' = :categoryId')
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ }
+
+ // Filter by begin date.
+ if ($begin = $this->getState('filter.begin')) {
+ $query->where($db->quoteName('a.track_date') . ' >= :begin')
+ ->bind(':begin', $begin);
+ }
+
+ // Filter by end date.
+ if ($end = $this->getState('filter.end')) {
+ $query->where($db->quoteName('a.track_date') . ' <= :end')
+ ->bind(':end', $end);
+ }
+
+ // Filter on the level.
+ if ($level = (int) $this->getState('filter.level')) {
+ $query->where($db->quoteName('c.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter by search in banner name or client name.
+ if ($search = $this->getState('filter.search')) {
+ $search = '%' . StringHelper::strtolower($search) . '%';
+ $query->where('(LOWER(' . $db->quoteName('b.name') . ') LIKE :search1 OR LOWER(' . $db->quoteName('cl.name') . ') LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+
+ // Add the list ordering clause.
+ $query->order(
+ $db->quoteName($db->escape($this->getState('list.ordering', 'b.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
+ );
+
+ return $query;
+ }
+
+ /**
+ * Method to delete rows.
+ *
+ * @return boolean Returns true on success, false on failure.
+ */
+ public function delete()
+ {
+ $user = Factory::getUser();
+ $categoryId = (int) $this->getState('category_id');
+
+ // Access checks.
+ if ($categoryId) {
+ $allow = $user->authorise('core.delete', 'com_banners.category.' . $categoryId);
+ } else {
+ $allow = $user->authorise('core.delete', 'com_banners');
+ }
+
+ if ($allow) {
+ // Delete tracks from this banner
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__banner_tracks'));
+
+ // Filter by type
+ if ($type = (int) $this->getState('filter.type')) {
+ $query->where($db->quoteName('track_type') . ' = :type')
+ ->bind(':type', $type, ParameterType::INTEGER);
+ }
+
+ // Filter by begin date
+ if ($begin = $this->getState('filter.begin')) {
+ $query->where($db->quoteName('track_date') . ' >= :begin')
+ ->bind(':begin', $begin);
+ }
+
+ // Filter by end date
+ if ($end = $this->getState('filter.end')) {
+ $query->where($db->quoteName('track_date') . ' <= :end')
+ ->bind(':end', $end);
+ }
+
+ $subQuery = $db->getQuery(true);
+ $subQuery->select($db->quoteName('id'))
+ ->from($db->quoteName('#__banners'));
+
+ // Filter by client
+ if ($clientId = (int) $this->getState('filter.client_id')) {
+ $subQuery->where($db->quoteName('cid') . ' = :clientId');
+ $query->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ // Filter by category
+ if ($categoryId) {
+ $subQuery->where($db->quoteName('catid') . ' = :categoryId');
+ $query->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ }
+
+ $query->where($db->quoteName('banner_id') . ' IN (' . $subQuery . ')');
+
+ $db->setQuery($query);
+ $this->setError((string) $query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ } else {
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
+ }
+
+ return true;
+ }
+
+ /**
+ * Get file name
+ *
+ * @return string The file name
+ *
+ * @since 1.6
+ */
+ public function getBaseName()
+ {
+ if (!isset($this->basename)) {
+ $basename = str_replace('__SITE__', Factory::getApplication()->get('sitename'), $this->getState('basename'));
+ $categoryId = $this->getState('filter.category_id');
+
+ if (is_numeric($categoryId)) {
+ if ($categoryId > 0) {
+ $basename = str_replace('__CATID__', $categoryId, $basename);
+ } else {
+ $basename = str_replace('__CATID__', '', $basename);
+ }
+
+ $categoryName = $this->getCategoryName();
+ $basename = str_replace('__CATNAME__', $categoryName, $basename);
+ } else {
+ $basename = str_replace(array('__CATID__', '__CATNAME__'), '', $basename);
+ }
+
+ $clientId = $this->getState('filter.client_id');
+
+ if (is_numeric($clientId)) {
+ if ($clientId > 0) {
+ $basename = str_replace('__CLIENTID__', $clientId, $basename);
+ } else {
+ $basename = str_replace('__CLIENTID__', '', $basename);
+ }
+
+ $clientName = $this->getClientName();
+ $basename = str_replace('__CLIENTNAME__', $clientName, $basename);
+ } else {
+ $basename = str_replace(array('__CLIENTID__', '__CLIENTNAME__'), '', $basename);
+ }
+
+ $type = $this->getState('filter.type');
+
+ if ($type > 0) {
+ $basename = str_replace('__TYPE__', $type, $basename);
+ $typeName = Text::_('COM_BANNERS_TYPE' . $type);
+ $basename = str_replace('__TYPENAME__', $typeName, $basename);
+ } else {
+ $basename = str_replace(array('__TYPE__', '__TYPENAME__'), '', $basename);
+ }
+
+ $begin = $this->getState('filter.begin');
+
+ if (!empty($begin)) {
+ $basename = str_replace('__BEGIN__', $begin, $basename);
+ } else {
+ $basename = str_replace('__BEGIN__', '', $basename);
+ }
+
+ $end = $this->getState('filter.end');
+
+ if (!empty($end)) {
+ $basename = str_replace('__END__', $end, $basename);
+ } else {
+ $basename = str_replace('__END__', '', $basename);
+ }
+
+ $this->basename = $basename;
+ }
+
+ return $this->basename;
+ }
+
+ /**
+ * Get the category name.
+ *
+ * @return string The category name
+ *
+ * @since 1.6
+ */
+ protected function getCategoryName()
+ {
+ $categoryId = (int) $this->getState('filter.category_id');
+
+ if ($categoryId) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('id') . ' = :categoryId')
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $name = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $name;
+ }
+
+ return Text::_('COM_BANNERS_NOCATEGORYNAME');
+ }
+
+ /**
+ * Get the client name
+ *
+ * @return string The client name.
+ *
+ * @since 1.6
+ */
+ protected function getClientName()
+ {
+ $clientId = (int) $this->getState('filter.client_id');
+
+ if ($clientId) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('name'))
+ ->from($db->quoteName('#__banner_clients'))
+ ->where($db->quoteName('id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $name = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $name;
+ }
+
+ return Text::_('COM_BANNERS_NOCLIENTNAME');
+ }
+
+ /**
+ * Get the file type.
+ *
+ * @return string The file type
+ *
+ * @since 1.6
+ */
+ public function getFileType()
+ {
+ return $this->getState('compressed') ? 'zip' : 'csv';
+ }
+
+ /**
+ * Get the mime type.
+ *
+ * @return string The mime type.
+ *
+ * @since 1.6
+ */
+ public function getMimeType()
+ {
+ return $this->getState('compressed') ? 'application/zip' : 'text/csv';
+ }
+
+ /**
+ * Get the content
+ *
+ * @return string The content.
+ *
+ * @since 1.6
+ */
+ public function getContent()
+ {
+ if (!isset($this->content)) {
+ $this->content = '"' . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_NAME')) . '","'
+ . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_CLIENT')) . '","'
+ . str_replace('"', '""', Text::_('JCATEGORY')) . '","'
+ . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_TYPE')) . '","'
+ . str_replace('"', '""', Text::_('COM_BANNERS_HEADING_COUNT')) . '","'
+ . str_replace('"', '""', Text::_('JDATE')) . '"' . "\n";
+
+ foreach ($this->getItems() as $item) {
+ $this->content .= '"' . str_replace('"', '""', $item->banner_name) . '","'
+ . str_replace('"', '""', $item->client_name) . '","'
+ . str_replace('"', '""', $item->category_title) . '","'
+ . str_replace('"', '""', ($item->track_type == 1 ? Text::_('COM_BANNERS_IMPRESSION') : Text::_('COM_BANNERS_CLICK'))) . '","'
+ . str_replace('"', '""', $item->count) . '","'
+ . str_replace('"', '""', $item->track_date) . '"' . "\n";
+ }
+
+ if ($this->getState('compressed')) {
+ $app = Factory::getApplication();
+
+ $files = array(
+ 'track' => array(
+ 'name' => $this->getBaseName() . '.csv',
+ 'data' => $this->content,
+ 'time' => time()
+ )
+ );
+ $ziproot = $app->get('tmp_path') . '/' . uniqid('banners_tracks_') . '.zip';
+
+ // Run the packager
+ $delete = Folder::files($app->get('tmp_path') . '/', uniqid('banners_tracks_'), false, true);
+
+ if (!empty($delete)) {
+ if (!File::delete($delete)) {
+ // File::delete throws an error
+ $this->setError(Text::_('COM_BANNERS_ERR_ZIP_DELETE_FAILURE'));
+
+ return false;
+ }
+ }
+
+ $archive = new Archive();
+
+ if (!$packager = $archive->getAdapter('zip')) {
+ $this->setError(Text::_('COM_BANNERS_ERR_ZIP_ADAPTER_FAILURE'));
+
+ return false;
+ } elseif (!$packager->create($ziproot, $files)) {
+ $this->setError(Text::_('COM_BANNERS_ERR_ZIP_CREATE_FAILURE'));
+
+ return false;
+ }
+
+ $this->content = file_get_contents($ziproot);
+
+ // Remove tmp zip file, it's no longer needed.
+ File::delete($ziproot);
+ }
+ }
+
+ return $this->content;
+ }
}
diff --git a/administrator/components/com_banners/src/Service/Html/Banner.php b/administrator/components/com_banners/src/Service/Html/Banner.php
index 3573e949be327..2380a599e7faf 100644
--- a/administrator/components/com_banners/src/Service/Html/Banner.php
+++ b/administrator/components/com_banners/src/Service/Html/Banner.php
@@ -1,4 +1,5 @@
',
- Text::_('COM_BANNERS_BATCH_CLIENT_LABEL'),
- '',
- '',
- '' . Text::_('COM_BANNERS_BATCH_CLIENT_NOCHANGE') . ' ',
- '' . Text::_('COM_BANNERS_NO_CLIENT') . ' ',
- HTMLHelper::_('select.options', static::clientlist(), 'value', 'text'),
- ' '
- )
- );
- }
+ /**
+ * Display a batch widget for the client selector.
+ *
+ * @return string The necessary HTML for the widget.
+ *
+ * @since 2.5
+ */
+ public function clients()
+ {
+ // Create the batch selector to change the client on a selection list.
+ return implode(
+ "\n",
+ array(
+ '',
+ Text::_('COM_BANNERS_BATCH_CLIENT_LABEL'),
+ ' ',
+ '',
+ '' . Text::_('COM_BANNERS_BATCH_CLIENT_NOCHANGE') . ' ',
+ '' . Text::_('COM_BANNERS_NO_CLIENT') . ' ',
+ HTMLHelper::_('select.options', static::clientlist(), 'value', 'text'),
+ ' '
+ )
+ );
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 1.6
- */
- public function clientlist()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id', 'value'),
- $db->quoteName('name', 'text'),
- ]
- )
- ->from($db->quoteName('#__banner_clients'))
- ->order($db->quoteName('name'));
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.6
+ */
+ public function clientlist()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id', 'value'),
+ $db->quoteName('name', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__banner_clients'))
+ ->order($db->quoteName('name'));
- // Get the options.
- $db->setQuery($query);
+ // Get the options.
+ $db->setQuery($query);
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
- return $options;
- }
+ return $options;
+ }
- /**
- * Returns a pinned state on a grid
- *
- * @param integer $value The state value.
- * @param integer $i The row index
- * @param boolean $enabled An optional setting for access control on the action.
- * @param string $checkbox An optional prefix for checkboxes.
- *
- * @return string The Html code
- *
- * @see HTMLHelperJGrid::state
- * @since 2.5.5
- */
- public function pinned($value, $i, $enabled = true, $checkbox = 'cb')
- {
- $states = array(
- 1 => array(
- 'sticky_unpublish',
- 'COM_BANNERS_BANNERS_PINNED',
- 'COM_BANNERS_BANNERS_HTML_PIN_BANNER',
- 'COM_BANNERS_BANNERS_PINNED',
- true,
- 'publish',
- 'publish'
- ),
- 0 => array(
- 'sticky_publish',
- 'COM_BANNERS_BANNERS_UNPINNED',
- 'COM_BANNERS_BANNERS_HTML_UNPIN_BANNER',
- 'COM_BANNERS_BANNERS_UNPINNED',
- true,
- 'unpublish',
- 'unpublish'
- ),
- );
+ /**
+ * Returns a pinned state on a grid
+ *
+ * @param integer $value The state value.
+ * @param integer $i The row index
+ * @param boolean $enabled An optional setting for access control on the action.
+ * @param string $checkbox An optional prefix for checkboxes.
+ *
+ * @return string The Html code
+ *
+ * @see HTMLHelperJGrid::state
+ * @since 2.5.5
+ */
+ public function pinned($value, $i, $enabled = true, $checkbox = 'cb')
+ {
+ $states = array(
+ 1 => array(
+ 'sticky_unpublish',
+ 'COM_BANNERS_BANNERS_PINNED',
+ 'COM_BANNERS_BANNERS_HTML_PIN_BANNER',
+ 'COM_BANNERS_BANNERS_PINNED',
+ true,
+ 'publish',
+ 'publish'
+ ),
+ 0 => array(
+ 'sticky_publish',
+ 'COM_BANNERS_BANNERS_UNPINNED',
+ 'COM_BANNERS_BANNERS_HTML_UNPIN_BANNER',
+ 'COM_BANNERS_BANNERS_UNPINNED',
+ true,
+ 'unpublish',
+ 'unpublish'
+ ),
+ );
- return HTMLHelper::_('jgrid.state', $states, $value, $i, 'banners.', $enabled, true, $checkbox);
- }
+ return HTMLHelper::_('jgrid.state', $states, $value, $i, 'banners.', $enabled, true, $checkbox);
+ }
}
diff --git a/administrator/components/com_banners/src/Table/BannerTable.php b/administrator/components/com_banners/src/Table/BannerTable.php
index dd23a61538042..24b6600492ed5 100644
--- a/administrator/components/com_banners/src/Table/BannerTable.php
+++ b/administrator/components/com_banners/src/Table/BannerTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_banners.banner';
-
- parent::__construct('#__banners', 'id', $db);
-
- $this->created = Factory::getDate()->toSql();
- $this->setColumnAlias('published', 'state');
- }
-
- /**
- * Increase click count
- *
- * @return void
- */
- public function clicks()
- {
- $id = (int) $this->id;
- $query = $this->_db->getQuery(true)
- ->update($this->_db->quoteName('#__banners'))
- ->set($this->_db->quoteName('clicks') . ' = ' . $this->_db->quoteName('clicks') . ' + 1')
- ->where($this->_db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
-
- $this->_db->setQuery($query);
- $this->_db->execute();
- }
-
- /**
- * Overloaded check function
- *
- * @return boolean
- *
- * @see Table::check
- * @since 1.5
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Set name
- $this->name = htmlspecialchars_decode($this->name, ENT_QUOTES);
-
- // Set alias
- if (trim($this->alias) == '')
- {
- $this->alias = $this->name;
- }
-
- $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
-
- if (trim(str_replace('-', '', $this->alias)) == '')
- {
- $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
- }
-
- // Check for a valid category.
- if (!$this->catid = (int) $this->catid)
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
-
- return false;
- }
-
- // Set created date if not set.
- if (!(int) $this->created)
- {
- $this->created = Factory::getDate()->toSql();
- }
-
- // Set publish_up, publish_down to null if not set
- if (!$this->publish_up)
- {
- $this->publish_up = null;
- }
-
- if (!$this->publish_down)
- {
- $this->publish_down = null;
- }
-
- // Check the publish down date is not earlier than publish up.
- if (!\is_null($this->publish_down) && !\is_null($this->publish_up) && $this->publish_down < $this->publish_up)
- {
- $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
-
- return false;
- }
-
- // Set ordering
- if ($this->state < 0)
- {
- // Set ordering to 0 if state is archived or trashed
- $this->ordering = 0;
- }
- elseif (empty($this->ordering))
- {
- // Set ordering to last if ordering was 0
- $this->ordering = self::getNextOrder($this->_db->quoteName('catid') . ' = ' . ((int) $this->catid) . ' AND ' . $this->_db->quoteName('state') . ' >= 0');
- }
-
- // Set modified to created if not set
- if (!$this->modified)
- {
- $this->modified = $this->created;
- }
-
- // Set modified_by to created_by if not set
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_by;
- }
-
- return true;
- }
-
- /**
- * Overloaded bind function
- *
- * @param mixed $array An associative array or object to bind to the \JTable instance.
- * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean True on success
- *
- * @since 1.5
- */
- public function bind($array, $ignore = array())
- {
- if (isset($array['params']) && \is_array($array['params']))
- {
- $registry = new Registry($array['params']);
-
- if ((int) $registry->get('width', 0) < 0)
- {
- $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_NEGATIVE_NOT_PERMITTED', Text::_('COM_BANNERS_FIELD_WIDTH_LABEL')));
-
- return false;
- }
-
- if ((int) $registry->get('height', 0) < 0)
- {
- $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_NEGATIVE_NOT_PERMITTED', Text::_('COM_BANNERS_FIELD_HEIGHT_LABEL')));
-
- return false;
- }
-
- // Converts the width and height to an absolute numeric value:
- $width = abs((int) $registry->get('width', 0));
- $height = abs((int) $registry->get('height', 0));
-
- // Sets the width and height to an empty string if = 0
- $registry->set('width', $width ?: '');
- $registry->set('height', $height ?: '');
-
- $array['params'] = (string) $registry;
- }
-
- if (isset($array['imptotal']))
- {
- $array['imptotal'] = abs((int) $array['imptotal']);
- }
-
- return parent::bind($array, $ignore);
- }
-
- /**
- * Method to store a row
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success, false on failure.
- */
- public function store($updateNulls = true)
- {
- $db = $this->getDbo();
-
- if (empty($this->id))
- {
- $purchaseType = $this->purchase_type;
-
- if ($purchaseType < 0 && $this->cid)
- {
- $client = new ClientTable($db);
- $client->load($this->cid);
- $purchaseType = $client->purchase_type;
- }
-
- if ($purchaseType < 0)
- {
- $purchaseType = ComponentHelper::getParams('com_banners')->get('purchase_type');
- }
-
- switch ($purchaseType)
- {
- case 1:
- $this->reset = null;
- break;
- case 2:
- $date = Factory::getDate('+1 year ' . date('Y-m-d'));
- $this->reset = $date->toSql();
- break;
- case 3:
- $date = Factory::getDate('+1 month ' . date('Y-m-d'));
- $this->reset = $date->toSql();
- break;
- case 4:
- $date = Factory::getDate('+7 day ' . date('Y-m-d'));
- $this->reset = $date->toSql();
- break;
- case 5:
- $date = Factory::getDate('+1 day ' . date('Y-m-d'));
- $this->reset = $date->toSql();
- break;
- }
-
- // Store the row
- parent::store($updateNulls);
- }
- else
- {
- // Get the old row
- /** @var BannerTable $oldrow */
- $oldrow = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $db));
-
- if (!$oldrow->load($this->id) && $oldrow->getError())
- {
- $this->setError($oldrow->getError());
- }
-
- // Verify that the alias is unique
- /** @var BannerTable $table */
- $table = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $db));
-
- if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0))
- {
- $this->setError(Text::_('COM_BANNERS_ERROR_UNIQUE_ALIAS'));
-
- return false;
- }
-
- // Store the new row
- parent::store($updateNulls);
-
- // Need to reorder ?
- if ($oldrow->state >= 0 && ($this->state < 0 || $oldrow->catid != $this->catid))
- {
- // Reorder the oldrow
- $this->reorder($this->_db->quoteName('catid') . ' = ' . ((int) $oldrow->catid) . ' AND ' . $this->_db->quoteName('state') . ' >= 0');
- }
- }
-
- return \count($this->getErrors()) == 0;
- }
-
- /**
- * Method to set the sticky state for a row or list of rows in the database
- * table. The method respects checked out rows by other users and will attempt
- * to checkin rows that it can after adjustments are made.
- *
- * @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used.
- * @param integer $state The sticky state. eg. [0 = unsticked, 1 = sticked]
- * @param integer $userId The user id of the user performing the operation.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function stick($pks = null, $state = 1, $userId = 0)
- {
- $k = $this->_tbl_key;
-
- // Sanitize input.
- $pks = ArrayHelper::toInteger($pks);
- $userId = (int) $userId;
- $state = (int) $state;
-
- // If there are no primary keys set check to see if the instance key is set.
- if (empty($pks))
- {
- if ($this->$k)
- {
- $pks = array($this->$k);
- }
- // Nothing to set publishing state on, return false.
- else
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
-
- return false;
- }
- }
-
- // Get an instance of the table
- /** @var BannerTable $table */
- $table = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $this->_db));
-
- // For all keys
- foreach ($pks as $pk)
- {
- // Load the banner
- if (!$table->load($pk))
- {
- $this->setError($table->getError());
- }
-
- // Verify checkout
- if (\is_null($table->checked_out) || $table->checked_out == $userId)
- {
- // Change the state
- $table->sticky = $state;
- $table->checked_out = null;
- $table->checked_out_time = null;
-
- // Check the row
- $table->check();
-
- // Store the row
- if (!$table->store())
- {
- $this->setError($table->getError());
- }
- }
- }
-
- return \count($this->getErrors()) == 0;
- }
-
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 1.5
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_banners.banner';
+
+ parent::__construct('#__banners', 'id', $db);
+
+ $this->created = Factory::getDate()->toSql();
+ $this->setColumnAlias('published', 'state');
+ }
+
+ /**
+ * Increase click count
+ *
+ * @return void
+ */
+ public function clicks()
+ {
+ $id = (int) $this->id;
+ $query = $this->_db->getQuery(true)
+ ->update($this->_db->quoteName('#__banners'))
+ ->set($this->_db->quoteName('clicks') . ' = ' . $this->_db->quoteName('clicks') . ' + 1')
+ ->where($this->_db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+
+ $this->_db->setQuery($query);
+ $this->_db->execute();
+ }
+
+ /**
+ * Overloaded check function
+ *
+ * @return boolean
+ *
+ * @see Table::check
+ * @since 1.5
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Set name
+ $this->name = htmlspecialchars_decode($this->name, ENT_QUOTES);
+
+ // Set alias
+ if (trim($this->alias) == '') {
+ $this->alias = $this->name;
+ }
+
+ $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
+
+ if (trim(str_replace('-', '', $this->alias)) == '') {
+ $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
+ }
+
+ // Check for a valid category.
+ if (!$this->catid = (int) $this->catid) {
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
+
+ return false;
+ }
+
+ // Set created date if not set.
+ if (!(int) $this->created) {
+ $this->created = Factory::getDate()->toSql();
+ }
+
+ // Set publish_up, publish_down to null if not set
+ if (!$this->publish_up) {
+ $this->publish_up = null;
+ }
+
+ if (!$this->publish_down) {
+ $this->publish_down = null;
+ }
+
+ // Check the publish down date is not earlier than publish up.
+ if (!\is_null($this->publish_down) && !\is_null($this->publish_up) && $this->publish_down < $this->publish_up) {
+ $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
+
+ return false;
+ }
+
+ // Set ordering
+ if ($this->state < 0) {
+ // Set ordering to 0 if state is archived or trashed
+ $this->ordering = 0;
+ } elseif (empty($this->ordering)) {
+ // Set ordering to last if ordering was 0
+ $this->ordering = self::getNextOrder($this->_db->quoteName('catid') . ' = ' . ((int) $this->catid) . ' AND ' . $this->_db->quoteName('state') . ' >= 0');
+ }
+
+ // Set modified to created if not set
+ if (!$this->modified) {
+ $this->modified = $this->created;
+ }
+
+ // Set modified_by to created_by if not set
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_by;
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded bind function
+ *
+ * @param mixed $array An associative array or object to bind to the \JTable instance.
+ * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.5
+ */
+ public function bind($array, $ignore = array())
+ {
+ if (isset($array['params']) && \is_array($array['params'])) {
+ $registry = new Registry($array['params']);
+
+ if ((int) $registry->get('width', 0) < 0) {
+ $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_NEGATIVE_NOT_PERMITTED', Text::_('COM_BANNERS_FIELD_WIDTH_LABEL')));
+
+ return false;
+ }
+
+ if ((int) $registry->get('height', 0) < 0) {
+ $this->setError(Text::sprintf('JLIB_DATABASE_ERROR_NEGATIVE_NOT_PERMITTED', Text::_('COM_BANNERS_FIELD_HEIGHT_LABEL')));
+
+ return false;
+ }
+
+ // Converts the width and height to an absolute numeric value:
+ $width = abs((int) $registry->get('width', 0));
+ $height = abs((int) $registry->get('height', 0));
+
+ // Sets the width and height to an empty string if = 0
+ $registry->set('width', $width ?: '');
+ $registry->set('height', $height ?: '');
+
+ $array['params'] = (string) $registry;
+ }
+
+ if (isset($array['imptotal'])) {
+ $array['imptotal'] = abs((int) $array['imptotal']);
+ }
+
+ return parent::bind($array, $ignore);
+ }
+
+ /**
+ * Method to store a row
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success, false on failure.
+ */
+ public function store($updateNulls = true)
+ {
+ $db = $this->getDbo();
+
+ if (empty($this->id)) {
+ $purchaseType = $this->purchase_type;
+
+ if ($purchaseType < 0 && $this->cid) {
+ $client = new ClientTable($db);
+ $client->load($this->cid);
+ $purchaseType = $client->purchase_type;
+ }
+
+ if ($purchaseType < 0) {
+ $purchaseType = ComponentHelper::getParams('com_banners')->get('purchase_type');
+ }
+
+ switch ($purchaseType) {
+ case 1:
+ $this->reset = null;
+ break;
+ case 2:
+ $date = Factory::getDate('+1 year ' . date('Y-m-d'));
+ $this->reset = $date->toSql();
+ break;
+ case 3:
+ $date = Factory::getDate('+1 month ' . date('Y-m-d'));
+ $this->reset = $date->toSql();
+ break;
+ case 4:
+ $date = Factory::getDate('+7 day ' . date('Y-m-d'));
+ $this->reset = $date->toSql();
+ break;
+ case 5:
+ $date = Factory::getDate('+1 day ' . date('Y-m-d'));
+ $this->reset = $date->toSql();
+ break;
+ }
+
+ // Store the row
+ parent::store($updateNulls);
+ } else {
+ // Get the old row
+ /** @var BannerTable $oldrow */
+ $oldrow = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $db));
+
+ if (!$oldrow->load($this->id) && $oldrow->getError()) {
+ $this->setError($oldrow->getError());
+ }
+
+ // Verify that the alias is unique
+ /** @var BannerTable $table */
+ $table = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $db));
+
+ if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0)) {
+ $this->setError(Text::_('COM_BANNERS_ERROR_UNIQUE_ALIAS'));
+
+ return false;
+ }
+
+ // Store the new row
+ parent::store($updateNulls);
+
+ // Need to reorder ?
+ if ($oldrow->state >= 0 && ($this->state < 0 || $oldrow->catid != $this->catid)) {
+ // Reorder the oldrow
+ $this->reorder($this->_db->quoteName('catid') . ' = ' . ((int) $oldrow->catid) . ' AND ' . $this->_db->quoteName('state') . ' >= 0');
+ }
+ }
+
+ return \count($this->getErrors()) == 0;
+ }
+
+ /**
+ * Method to set the sticky state for a row or list of rows in the database
+ * table. The method respects checked out rows by other users and will attempt
+ * to checkin rows that it can after adjustments are made.
+ *
+ * @param mixed $pks An optional array of primary key values to update. If not set the instance property value is used.
+ * @param integer $state The sticky state. eg. [0 = unsticked, 1 = sticked]
+ * @param integer $userId The user id of the user performing the operation.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function stick($pks = null, $state = 1, $userId = 0)
+ {
+ $k = $this->_tbl_key;
+
+ // Sanitize input.
+ $pks = ArrayHelper::toInteger($pks);
+ $userId = (int) $userId;
+ $state = (int) $state;
+
+ // If there are no primary keys set check to see if the instance key is set.
+ if (empty($pks)) {
+ if ($this->$k) {
+ $pks = array($this->$k);
+ } else {
+ // Nothing to set publishing state on, return false.
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
+
+ return false;
+ }
+ }
+
+ // Get an instance of the table
+ /** @var BannerTable $table */
+ $table = Table::getInstance('BannerTable', __NAMESPACE__ . '\\', array('dbo' => $this->_db));
+
+ // For all keys
+ foreach ($pks as $pk) {
+ // Load the banner
+ if (!$table->load($pk)) {
+ $this->setError($table->getError());
+ }
+
+ // Verify checkout
+ if (\is_null($table->checked_out) || $table->checked_out == $userId) {
+ // Change the state
+ $table->sticky = $state;
+ $table->checked_out = null;
+ $table->checked_out_time = null;
+
+ // Check the row
+ $table->check();
+
+ // Store the row
+ if (!$table->store()) {
+ $this->setError($table->getError());
+ }
+ }
+ }
+
+ return \count($this->getErrors()) == 0;
+ }
+
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
}
diff --git a/administrator/components/com_banners/src/Table/ClientTable.php b/administrator/components/com_banners/src/Table/ClientTable.php
index 977dc5f656857..d6c74778e8ff6 100644
--- a/administrator/components/com_banners/src/Table/ClientTable.php
+++ b/administrator/components/com_banners/src/Table/ClientTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_banners.client';
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 1.5
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_banners.client';
- $this->setColumnAlias('published', 'state');
+ $this->setColumnAlias('published', 'state');
- parent::__construct('#__banner_clients', 'id', $db);
- }
+ parent::__construct('#__banner_clients', 'id', $db);
+ }
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
- /**
- * Overloaded check function
- *
- * @return boolean True if the object is ok
- *
- * @see Table::check()
- * @since 4.0.0
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
+ /**
+ * Overloaded check function
+ *
+ * @return boolean True if the object is ok
+ *
+ * @see Table::check()
+ * @since 4.0.0
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
- return false;
- }
+ return false;
+ }
- // Check for valid name
- if (trim($this->name) === '')
- {
- $this->setError(Text::_('COM_BANNERS_WARNING_PROVIDE_VALID_NAME'));
+ // Check for valid name
+ if (trim($this->name) === '') {
+ $this->setError(Text::_('COM_BANNERS_WARNING_PROVIDE_VALID_NAME'));
- return false;
- }
+ return false;
+ }
- // Check for valid contact
- if (trim($this->contact) === '')
- {
- $this->setError(Text::_('COM_BANNERS_PROVIDE_VALID_CONTACT'));
+ // Check for valid contact
+ if (trim($this->contact) === '') {
+ $this->setError(Text::_('COM_BANNERS_PROVIDE_VALID_CONTACT'));
- return false;
- }
+ return false;
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/administrator/components/com_banners/src/View/Banner/HtmlView.php b/administrator/components/com_banners/src/View/Banner/HtmlView.php
index a73f0d9f2415c..a5799dc0d45bb 100644
--- a/administrator/components/com_banners/src/View/Banner/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Banner/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->form = $model->getForm();
- $this->item = $model->getItem();
- $this->state = $model->getState();
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- * @throws Exception
- */
- protected function addToolbar(): void
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = ($this->item->id == 0);
- $checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Since we don't track these assets at the item level, use the category id.
- $canDo = ContentHelper::getActions('com_banners', 'category', $this->item->catid);
-
- ToolbarHelper::title($isNew ? Text::_('COM_BANNERS_MANAGER_BANNER_NEW') : Text::_('COM_BANNERS_MANAGER_BANNER_EDIT'), 'bookmark banners');
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if (!$checkedOut && ($canDo->get('core.edit') || \count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0))
- {
- ToolbarHelper::apply('banner.apply');
- $toolbarButtons[] = ['save', 'banner.save'];
-
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'banner.save2new'];
- }
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'banner.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('banner.cancel');
- }
- else
- {
- ToolbarHelper::cancel('banner.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit'))
- {
- ToolbarHelper::versions('com_banners.banner', $this->item->id);
- }
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Banners:_Edit');
- }
+ /**
+ * The Form object
+ *
+ * @var Form
+ * @since 1.5
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ * @since 1.5
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 1.5
+ */
+ protected $state;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.5
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var BannerModel $model */
+ $model = $this->getModel();
+ $this->form = $model->getForm();
+ $this->item = $model->getItem();
+ $this->state = $model->getState();
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws Exception
+ */
+ protected function addToolbar(): void
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Since we don't track these assets at the item level, use the category id.
+ $canDo = ContentHelper::getActions('com_banners', 'category', $this->item->catid);
+
+ ToolbarHelper::title($isNew ? Text::_('COM_BANNERS_MANAGER_BANNER_NEW') : Text::_('COM_BANNERS_MANAGER_BANNER_EDIT'), 'bookmark banners');
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if (!$checkedOut && ($canDo->get('core.edit') || \count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0)) {
+ ToolbarHelper::apply('banner.apply');
+ $toolbarButtons[] = ['save', 'banner.save'];
+
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'banner.save2new'];
+ }
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'banner.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('banner.cancel');
+ } else {
+ ToolbarHelper::cancel('banner.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit')) {
+ ToolbarHelper::versions('com_banners.banner', $this->item->id);
+ }
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Banners:_Edit');
+ }
}
diff --git a/administrator/components/com_banners/src/View/Banners/HtmlView.php b/administrator/components/com_banners/src/View/Banners/HtmlView.php
index 5019ffd9e592d..c1568c5978852 100644
--- a/administrator/components/com_banners/src/View/Banners/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Banners/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->categories = $model->getCategoryOrders();
- $this->items = $model->getItems();
- $this->pagination = $model->getPagination();
- $this->state = $model->getState();
- $this->filterForm = $model->getFilterForm();
- $this->activeFilters = $model->getActiveFilters();
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar(): void
- {
- $canDo = ContentHelper::getActions('com_banners', 'category', $this->state->get('filter.category_id'));
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_BANNERS'), 'bookmark banners');
-
- if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0)
- {
- $toolbar->addNew('banner.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || ($this->state->get('filter.published') == -2 && $canDo->get('core.delete'))))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- if ($this->state->get('filter.published') != 2)
- {
- $childBar->publish('banners.publish')->listCheck(true);
-
- $childBar->unpublish('banners.unpublish')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') != -1)
- {
- if ($this->state->get('filter.published') != 2)
- {
- $childBar->archive('banners.archive')->listCheck(true);
- }
- elseif ($this->state->get('filter.published') == 2)
- {
- $childBar->publish('publish')->task('banners.publish')->listCheck(true);
- }
- }
-
- $childBar->checkin('banners.checkin')->listCheck(true);
-
- if ($this->state->get('filter.published') != -2)
- {
- $childBar->trash('banners.trash')->listCheck(true);
- }
- }
-
- if ($this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('banners.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_banners')
- && $user->authorise('core.edit', 'com_banners')
- && $user->authorise('core.edit.state', 'com_banners'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if ($user->authorise('core.admin', 'com_banners') || $user->authorise('core.options', 'com_banners'))
- {
- $toolbar->preferences('com_banners');
- }
-
- $toolbar->help('Banners');
- }
+ /**
+ * The search tools form
+ *
+ * @var Form
+ * @since 1.6
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 1.6
+ */
+ public $activeFilters = [];
+
+ /**
+ * Category data
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $categories = [];
+
+ /**
+ * An array of items
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $items = [];
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var Registry
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var BannersModel $model */
+ $model = $this->getModel();
+ $this->categories = $model->getCategoryOrders();
+ $this->items = $model->getItems();
+ $this->pagination = $model->getPagination();
+ $this->state = $model->getState();
+ $this->filterForm = $model->getFilterForm();
+ $this->activeFilters = $model->getActiveFilters();
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar(): void
+ {
+ $canDo = ContentHelper::getActions('com_banners', 'category', $this->state->get('filter.category_id'));
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_BANNERS'), 'bookmark banners');
+
+ if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0) {
+ $toolbar->addNew('banner.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || ($this->state->get('filter.published') == -2 && $canDo->get('core.delete')))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ if ($this->state->get('filter.published') != 2) {
+ $childBar->publish('banners.publish')->listCheck(true);
+
+ $childBar->unpublish('banners.unpublish')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') != -1) {
+ if ($this->state->get('filter.published') != 2) {
+ $childBar->archive('banners.archive')->listCheck(true);
+ } elseif ($this->state->get('filter.published') == 2) {
+ $childBar->publish('publish')->task('banners.publish')->listCheck(true);
+ }
+ }
+
+ $childBar->checkin('banners.checkin')->listCheck(true);
+
+ if ($this->state->get('filter.published') != -2) {
+ $childBar->trash('banners.trash')->listCheck(true);
+ }
+ }
+
+ if ($this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('banners.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_banners')
+ && $user->authorise('core.edit', 'com_banners')
+ && $user->authorise('core.edit.state', 'com_banners')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if ($user->authorise('core.admin', 'com_banners') || $user->authorise('core.options', 'com_banners')) {
+ $toolbar->preferences('com_banners');
+ }
+
+ $toolbar->help('Banners');
+ }
}
diff --git a/administrator/components/com_banners/src/View/Client/HtmlView.php b/administrator/components/com_banners/src/View/Client/HtmlView.php
index b7b820267800c..c2670583dd591 100644
--- a/administrator/components/com_banners/src/View/Client/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Client/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->form = $model->getForm();
- $this->item = $model->getItem();
- $this->state = $model->getState();
- $this->canDo = ContentHelper::getActions('com_banners');
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- *
- * @throws Exception
- */
- protected function addToolbar(): void
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $isNew = ($this->item->id == 0);
- $checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $user->id);
- $canDo = $this->canDo;
-
- ToolbarHelper::title(
- $isNew ? Text::_('COM_BANNERS_MANAGER_CLIENT_NEW') : Text::_('COM_BANNERS_MANAGER_CLIENT_EDIT'),
- 'bookmark banners-clients'
- );
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if (!$checkedOut && ($canDo->get('core.edit') || $canDo->get('core.create')))
- {
- ToolbarHelper::apply('client.apply');
- $toolbarButtons[] = ['save', 'client.save'];
- }
-
- if (!$checkedOut && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'client.save2new'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'client.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('client.cancel');
- }
- else
- {
- ToolbarHelper::cancel('client.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit'))
- {
- ToolbarHelper::versions('com_banners.client', $this->item->id);
- }
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Banners:_New_or_Edit_Client');
- }
+ /**
+ * The Form object
+ *
+ * @var Form
+ * @since 1.5
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var CMSObject
+ * @since 1.5
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ * @since 1.5
+ */
+ protected $state;
+
+ /**
+ * Object containing permissions for the item
+ *
+ * @var CMSObject
+ * @since 1.5
+ */
+ protected $canDo;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.5
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var ClientModel $model */
+ $model = $this->getModel();
+ $this->form = $model->getForm();
+ $this->item = $model->getItem();
+ $this->state = $model->getState();
+ $this->canDo = ContentHelper::getActions('com_banners');
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ protected function addToolbar(): void
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $user->id);
+ $canDo = $this->canDo;
+
+ ToolbarHelper::title(
+ $isNew ? Text::_('COM_BANNERS_MANAGER_CLIENT_NEW') : Text::_('COM_BANNERS_MANAGER_CLIENT_EDIT'),
+ 'bookmark banners-clients'
+ );
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if (!$checkedOut && ($canDo->get('core.edit') || $canDo->get('core.create'))) {
+ ToolbarHelper::apply('client.apply');
+ $toolbarButtons[] = ['save', 'client.save'];
+ }
+
+ if (!$checkedOut && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'client.save2new'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'client.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('client.cancel');
+ } else {
+ ToolbarHelper::cancel('client.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit')) {
+ ToolbarHelper::versions('com_banners.client', $this->item->id);
+ }
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Banners:_New_or_Edit_Client');
+ }
}
diff --git a/administrator/components/com_banners/src/View/Clients/HtmlView.php b/administrator/components/com_banners/src/View/Clients/HtmlView.php
index b18edbf38b771..585816a6af862 100644
--- a/administrator/components/com_banners/src/View/Clients/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Clients/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->items = $model->getItems();
- $this->pagination = $model->getPagination();
- $this->state = $model->getState();
- $this->filterForm = $model->getFilterForm();
- $this->activeFilters = $model->getActiveFilters();
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar(): void
- {
- $canDo = ContentHelper::getActions('com_banners');
-
- ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_CLIENTS'), 'bookmark banners-clients');
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('client.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('clients.publish')->listCheck(true);
- $childBar->unpublish('clients.unpublish')->listCheck(true);
- $childBar->archive('clients.archive')->listCheck(true);
-
- if ($canDo->get('core.admin'))
- {
- $childBar->checkin('clients.checkin')->listCheck(true);
- }
-
- if (!$this->state->get('filter.state') == -2)
- {
- $childBar->trash('clients.trash')->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.state') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('clients.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_banners');
- }
-
- $toolbar->help('Banners:_Clients');
- }
+ /**
+ * The search tools form
+ *
+ * @var Form
+ * @since 1.6
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 1.6
+ */
+ public $activeFilters = [];
+
+ /**
+ * An array of items
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $items = [];
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var ClientsModel $model */
+ $model = $this->getModel();
+ $this->items = $model->getItems();
+ $this->pagination = $model->getPagination();
+ $this->state = $model->getState();
+ $this->filterForm = $model->getFilterForm();
+ $this->activeFilters = $model->getActiveFilters();
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar(): void
+ {
+ $canDo = ContentHelper::getActions('com_banners');
+
+ ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_CLIENTS'), 'bookmark banners-clients');
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('client.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('clients.publish')->listCheck(true);
+ $childBar->unpublish('clients.unpublish')->listCheck(true);
+ $childBar->archive('clients.archive')->listCheck(true);
+
+ if ($canDo->get('core.admin')) {
+ $childBar->checkin('clients.checkin')->listCheck(true);
+ }
+
+ if (!$this->state->get('filter.state') == -2) {
+ $childBar->trash('clients.trash')->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.state') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('clients.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_banners');
+ }
+
+ $toolbar->help('Banners:_Clients');
+ }
}
diff --git a/administrator/components/com_banners/src/View/Download/HtmlView.php b/administrator/components/com_banners/src/View/Download/HtmlView.php
index 6307ee8d06992..1b6ef86d72f74 100644
--- a/administrator/components/com_banners/src/View/Download/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Download/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->form = $model->getForm();
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var DownloadModel $model */
+ $model = $this->getModel();
+ $this->form = $model->getForm();
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_banners/src/View/Tracks/HtmlView.php b/administrator/components/com_banners/src/View/Tracks/HtmlView.php
index 5931f49a3dfc5..2fa559ab1495f 100644
--- a/administrator/components/com_banners/src/View/Tracks/HtmlView.php
+++ b/administrator/components/com_banners/src/View/Tracks/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->items = $model->getItems();
- $this->pagination = $model->getPagination();
- $this->state = $model->getState();
- $this->filterForm = $model->getFilterForm();
- $this->activeFilters = $model->getActiveFilters();
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar(): void
- {
- $canDo = ContentHelper::getActions('com_banners', 'category', $this->state->get('filter.category_id'));
-
- ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_TRACKS'), 'bookmark banners-tracks');
-
- $bar = Toolbar::getInstance('toolbar');
-
- if (!$this->isEmptyState)
- {
- $bar->popupButton()
- ->url(Route::_('index.php?option=com_banners&view=download&tmpl=component'))
- ->text('JTOOLBAR_EXPORT')
- ->selector('downloadModal')
- ->icon('icon-download')
- ->footer(''
- . Text::_('COM_BANNERS_CANCEL') . ' '
- . ''
- . Text::_('COM_BANNERS_TRACKS_EXPORT') . ' '
- );
- }
-
- if (!$this->isEmptyState && $canDo->get('core.delete'))
- {
- $bar->appendButton('Confirm', 'COM_BANNERS_DELETE_MSG', 'delete', 'COM_BANNERS_TRACKS_DELETE', 'tracks.delete', false);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_banners');
- }
-
- ToolbarHelper::help('Banners:_Tracks');
- }
+ /**
+ * The search tools form
+ *
+ * @var Form
+ * @since 1.6
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 1.6
+ */
+ public $activeFilters = [];
+
+ /**
+ * An array of items
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $items = [];
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var TracksModel $model */
+ $model = $this->getModel();
+ $this->items = $model->getItems();
+ $this->pagination = $model->getPagination();
+ $this->state = $model->getState();
+ $this->filterForm = $model->getFilterForm();
+ $this->activeFilters = $model->getActiveFilters();
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar(): void
+ {
+ $canDo = ContentHelper::getActions('com_banners', 'category', $this->state->get('filter.category_id'));
+
+ ToolbarHelper::title(Text::_('COM_BANNERS_MANAGER_TRACKS'), 'bookmark banners-tracks');
+
+ $bar = Toolbar::getInstance('toolbar');
+
+ if (!$this->isEmptyState) {
+ $bar->popupButton()
+ ->url(Route::_('index.php?option=com_banners&view=download&tmpl=component'))
+ ->text('JTOOLBAR_EXPORT')
+ ->selector('downloadModal')
+ ->icon('icon-download')
+ ->footer(''
+ . Text::_('COM_BANNERS_CANCEL') . ' '
+ . ''
+ . Text::_('COM_BANNERS_TRACKS_EXPORT') . ' ');
+ }
+
+ if (!$this->isEmptyState && $canDo->get('core.delete')) {
+ $bar->appendButton('Confirm', 'COM_BANNERS_DELETE_MSG', 'delete', 'COM_BANNERS_TRACKS_DELETE', 'tracks.delete', false);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_banners');
+ }
+
+ ToolbarHelper::help('Banners:_Tracks');
+ }
}
diff --git a/administrator/components/com_banners/src/View/Tracks/RawView.php b/administrator/components/com_banners/src/View/Tracks/RawView.php
index 17470ab692f79..3b7579f3c10df 100644
--- a/administrator/components/com_banners/src/View/Tracks/RawView.php
+++ b/administrator/components/com_banners/src/View/Tracks/RawView.php
@@ -1,4 +1,5 @@
getModel();
- $basename = $model->getBaseName();
- $fileType = $model->getFileType();
- $mimeType = $model->getMimeType();
- $content = $model->getContent();
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws Exception
+ */
+ public function display($tpl = null): void
+ {
+ /** @var TracksModel $model */
+ $model = $this->getModel();
+ $basename = $model->getBaseName();
+ $fileType = $model->getFileType();
+ $mimeType = $model->getMimeType();
+ $content = $model->getContent();
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- $this->document->setMimeEncoding($mimeType);
+ $this->document->setMimeEncoding($mimeType);
- /** @var CMSApplication $app */
- $app = Factory::getApplication();
- $app->setHeader(
- 'Content-disposition',
- 'attachment; filename="' . $basename . '.' . $fileType . '"; creation-date="' . Factory::getDate()->toRFC822() . '"',
- true
- );
- echo $content;
- }
+ /** @var CMSApplication $app */
+ $app = Factory::getApplication();
+ $app->setHeader(
+ 'Content-disposition',
+ 'attachment; filename="' . $basename . '.' . $fileType . '"; creation-date="' . Factory::getDate()->toRFC822() . '"',
+ true
+ );
+ echo $content;
+ }
}
diff --git a/administrator/components/com_banners/tmpl/banner/edit.php b/administrator/components/com_banners/tmpl/banner/edit.php
index 3320b3121114b..d6fe9c69f9a88 100644
--- a/administrator/components/com_banners/tmpl/banner/edit.php
+++ b/administrator/components/com_banners/tmpl/banner/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_banners.admin-banner-edit');
+ ->useScript('form.validate')
+ ->useScript('com_banners.admin-banner-edit');
?>
diff --git a/administrator/components/com_banners/tmpl/banners/default.php b/administrator/components/com_banners/tmpl/banners/default.php
index 22ec2c5d1310e..1f8a3d6fdd804 100644
--- a/administrator/components/com_banners/tmpl/banners/default.php
+++ b/administrator/components/com_banners/tmpl/banners/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$userId = $user->get('id');
@@ -30,173 +31,173 @@
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = $listOrder == 'a.ordering';
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_banners&task=banners.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_banners&task=banners.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_banners/tmpl/banners/default_batch_body.php b/administrator/components/com_banners/tmpl/banners/default_batch_body.php
index f53c0395e2d66..fbc990db6f439 100644
--- a/administrator/components/com_banners/tmpl/banners/default_batch_body.php
+++ b/administrator/components/com_banners/tmpl/banners/default_batch_body.php
@@ -1,4 +1,5 @@
-
-
+
+
diff --git a/administrator/components/com_banners/tmpl/banners/default_batch_footer.php b/administrator/components/com_banners/tmpl/banners/default_batch_footer.php
index 723541c31226c..d1282ec1246fe 100644
--- a/administrator/components/com_banners/tmpl/banners/default_batch_footer.php
+++ b/administrator/components/com_banners/tmpl/banners/default_batch_footer.php
@@ -1,4 +1,5 @@
-
+
-
+
diff --git a/administrator/components/com_banners/tmpl/banners/emptystate.php b/administrator/components/com_banners/tmpl/banners/emptystate.php
index ec83587c839c2..b4b0db976c3a1 100644
--- a/administrator/components/com_banners/tmpl/banners/emptystate.php
+++ b/administrator/components/com_banners/tmpl/banners/emptystate.php
@@ -1,4 +1,5 @@
'COM_BANNERS',
- 'formURL' => 'index.php?option=com_banners&view=banners',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners',
- 'icon' => 'icon-bookmark banners',
+ 'textPrefix' => 'COM_BANNERS',
+ 'formURL' => 'index.php?option=com_banners&view=banners',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners',
+ 'icon' => 'icon-bookmark banners',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_banners') || count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_banners&task=banner.add';
+if ($user->authorise('core.create', 'com_banners') || count($user->getAuthorisedCategories('com_banners', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_banners&task=banner.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_banners/tmpl/client/edit.php b/administrator/components/com_banners/tmpl/client/edit.php
index 9b8466a530183..bd0b60e087fb0 100644
--- a/administrator/components/com_banners/tmpl/client/edit.php
+++ b/administrator/components/com_banners/tmpl/client/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_banners/tmpl/clients/default.php b/administrator/components/com_banners/tmpl/clients/default.php
index 31da558bd75b3..8db12d77ee7f3 100644
--- a/administrator/components/com_banners/tmpl/clients/default.php
+++ b/administrator/components/com_banners/tmpl/clients/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$purchaseTypes = [
- '1' => 'UNLIMITED',
- '2' => 'YEARLY',
- '3' => 'MONTHLY',
- '4' => 'WEEKLY',
- '5' => 'DAILY',
+ '1' => 'UNLIMITED',
+ '2' => 'YEARLY',
+ '3' => 'MONTHLY',
+ '4' => 'WEEKLY',
+ '5' => 'DAILY',
];
$user = Factory::getUser();
$userId = $user->get('id');
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
-$params = $this->state->params ?? new CMSObject;
+$params = $this->state->params ?? new CMSObject();
?>
diff --git a/administrator/components/com_banners/tmpl/clients/emptystate.php b/administrator/components/com_banners/tmpl/clients/emptystate.php
index 1feedff923aac..c7e79091d9a11 100644
--- a/administrator/components/com_banners/tmpl/clients/emptystate.php
+++ b/administrator/components/com_banners/tmpl/clients/emptystate.php
@@ -1,4 +1,5 @@
'COM_BANNERS_CLIENT',
- 'formURL' => 'index.php?option=com_banners&view=clients',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners:_Clients',
- 'icon' => 'icon-bookmark banners',
+ 'textPrefix' => 'COM_BANNERS_CLIENT',
+ 'formURL' => 'index.php?option=com_banners&view=clients',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners:_Clients',
+ 'icon' => 'icon-bookmark banners',
];
-if (count(Factory::getApplication()->getIdentity()->getAuthorisedCategories('com_banners', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_banners&task=client.add';
+if (count(Factory::getApplication()->getIdentity()->getAuthorisedCategories('com_banners', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_banners&task=client.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_banners/tmpl/download/default.php b/administrator/components/com_banners/tmpl/download/default.php
index dc433005a22b7..dacd8035fa53b 100644
--- a/administrator/components/com_banners/tmpl/download/default.php
+++ b/administrator/components/com_banners/tmpl/download/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_banners/tmpl/tracks/default.php b/administrator/components/com_banners/tmpl/tracks/default.php
index 0e0113ec16c77..04ff5e1f2a0be 100644
--- a/administrator/components/com_banners/tmpl/tracks/default.php
+++ b/administrator/components/com_banners/tmpl/tracks/default.php
@@ -1,4 +1,5 @@
escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_banners/tmpl/tracks/emptystate.php b/administrator/components/com_banners/tmpl/tracks/emptystate.php
index 4ad37d7cafcab..0fe1cd2f152d7 100644
--- a/administrator/components/com_banners/tmpl/tracks/emptystate.php
+++ b/administrator/components/com_banners/tmpl/tracks/emptystate.php
@@ -1,4 +1,5 @@
'COM_BANNERS_TRACKS',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners:_Tracks',
- 'icon' => 'icon-bookmark banners',
+ 'textPrefix' => 'COM_BANNERS_TRACKS',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Banners:_Tracks',
+ 'icon' => 'icon-bookmark banners',
];
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_cache/services/provider.php b/administrator/components/com_cache/services/provider.php
index 09e3a1e2c42e0..01ca10a33e748 100644
--- a/administrator/components/com_cache/services/provider.php
+++ b/administrator/components/com_cache/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Cache'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Cache'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Cache'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Cache'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_cache/src/Controller/DisplayController.php b/administrator/components/com_cache/src/Controller/DisplayController.php
index ccbdcc9698348..afc321e25f8f5 100644
--- a/administrator/components/com_cache/src/Controller/DisplayController.php
+++ b/administrator/components/com_cache/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
getModel('Cache');
-
- $data = $model->getData();
-
- $size = 0;
-
- if (!empty($data))
- {
- foreach ($data as $d)
- {
- $size += $d->size;
- }
- }
-
- // Number bytes are returned in format xxx.xx MB
- $bytes = HTMLHelper::_('number.bytes', $size, 'MB', 1);
-
- if (!empty($bytes))
- {
- $result['amount'] = $bytes;
- $result['sronly'] = Text::sprintf('COM_CACHE_QUICKICON_SRONLY', $bytes);
- }
- else
- {
- $result['amount'] = 0;
- $result['sronly'] = Text::sprintf('COM_CACHE_QUICKICON_SRONLY_NOCACHE');
- }
-
- echo new JsonResponse($result);
- }
-
- /**
- * Method to delete a list of cache groups.
- *
- * @return void
- */
- public function delete()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $cid = (array) $this->input->post->get('cid', array(), 'string');
-
- if (empty($cid))
- {
- $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'warning');
- }
- else
- {
- $result = $this->getModel('cache')->cleanlist($cid);
-
- if ($result !== array())
- {
- $this->app->enqueueMessage(Text::sprintf('COM_CACHE_EXPIRED_ITEMS_DELETE_ERROR', implode(', ', $result)), 'error');
- }
- else
- {
- $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_HAVE_BEEN_DELETED'), 'message');
- }
- }
-
- $this->setRedirect('index.php?option=com_cache');
- }
-
- /**
- * Method to delete all cache groups.
- *
- * @return void
- *
- * @since 3.6.0
- */
- public function deleteAll()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /** @var \Joomla\Component\Cache\Administrator\Model\CacheModel $model */
- $model = $this->getModel('cache');
- $allCleared = true;
-
- $mCache = $model->getCache();
-
- foreach ($mCache->getAll() as $cache)
- {
- if ($mCache->clean($cache->group) === false)
- {
- $this->app->enqueueMessage(
- Text::sprintf(
- 'COM_CACHE_EXPIRED_ITEMS_DELETE_ERROR', Text::_('JADMINISTRATOR') . ' > ' . $cache->group
- ), 'error'
- );
- $allCleared = false;
- }
- }
-
- if ($allCleared)
- {
- $this->app->enqueueMessage(Text::_('COM_CACHE_MSG_ALL_CACHE_GROUPS_CLEARED'), 'message');
- }
- else
- {
- $this->app->enqueueMessage(Text::_('COM_CACHE_MSG_SOME_CACHE_GROUPS_CLEARED'), 'warning');
- }
-
- $this->app->triggerEvent('onAfterPurge', array());
- $this->setRedirect('index.php?option=com_cache&view=cache');
- }
-
- /**
- * Purge the cache.
- *
- * @return void
- */
- public function purge()
- {
- // Check for request forgeries
- $this->checkToken();
-
- if (!$this->getModel('cache')->purge())
- {
- $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_PURGING_ERROR'), 'error');
- }
- else
- {
- $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_HAVE_BEEN_PURGED'), 'message');
- }
-
- $this->setRedirect('index.php?option=com_cache&view=cache');
- }
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $default_view = 'cache';
+
+ /**
+ * Method to get The Cache Size
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Cache');
+
+ $data = $model->getData();
+
+ $size = 0;
+
+ if (!empty($data)) {
+ foreach ($data as $d) {
+ $size += $d->size;
+ }
+ }
+
+ // Number bytes are returned in format xxx.xx MB
+ $bytes = HTMLHelper::_('number.bytes', $size, 'MB', 1);
+
+ if (!empty($bytes)) {
+ $result['amount'] = $bytes;
+ $result['sronly'] = Text::sprintf('COM_CACHE_QUICKICON_SRONLY', $bytes);
+ } else {
+ $result['amount'] = 0;
+ $result['sronly'] = Text::sprintf('COM_CACHE_QUICKICON_SRONLY_NOCACHE');
+ }
+
+ echo new JsonResponse($result);
+ }
+
+ /**
+ * Method to delete a list of cache groups.
+ *
+ * @return void
+ */
+ public function delete()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $cid = (array) $this->input->post->get('cid', array(), 'string');
+
+ if (empty($cid)) {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'warning');
+ } else {
+ $result = $this->getModel('cache')->cleanlist($cid);
+
+ if ($result !== array()) {
+ $this->app->enqueueMessage(Text::sprintf('COM_CACHE_EXPIRED_ITEMS_DELETE_ERROR', implode(', ', $result)), 'error');
+ } else {
+ $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_HAVE_BEEN_DELETED'), 'message');
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_cache');
+ }
+
+ /**
+ * Method to delete all cache groups.
+ *
+ * @return void
+ *
+ * @since 3.6.0
+ */
+ public function deleteAll()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Cache\Administrator\Model\CacheModel $model */
+ $model = $this->getModel('cache');
+ $allCleared = true;
+
+ $mCache = $model->getCache();
+
+ foreach ($mCache->getAll() as $cache) {
+ if ($mCache->clean($cache->group) === false) {
+ $this->app->enqueueMessage(
+ Text::sprintf(
+ 'COM_CACHE_EXPIRED_ITEMS_DELETE_ERROR',
+ Text::_('JADMINISTRATOR') . ' > ' . $cache->group
+ ),
+ 'error'
+ );
+ $allCleared = false;
+ }
+ }
+
+ if ($allCleared) {
+ $this->app->enqueueMessage(Text::_('COM_CACHE_MSG_ALL_CACHE_GROUPS_CLEARED'), 'message');
+ } else {
+ $this->app->enqueueMessage(Text::_('COM_CACHE_MSG_SOME_CACHE_GROUPS_CLEARED'), 'warning');
+ }
+
+ $this->app->triggerEvent('onAfterPurge', array());
+ $this->setRedirect('index.php?option=com_cache&view=cache');
+ }
+
+ /**
+ * Purge the cache.
+ *
+ * @return void
+ */
+ public function purge()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ if (!$this->getModel('cache')->purge()) {
+ $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_PURGING_ERROR'), 'error');
+ } else {
+ $this->app->enqueueMessage(Text::_('COM_CACHE_EXPIRED_ITEMS_HAVE_BEEN_PURGED'), 'message');
+ }
+
+ $this->setRedirect('index.php?option=com_cache&view=cache');
+ }
}
diff --git a/administrator/components/com_cache/src/Model/CacheModel.php b/administrator/components/com_cache/src/Model/CacheModel.php
index cdbcebf587dec..a6113be391f79 100644
--- a/administrator/components/com_cache/src/Model/CacheModel.php
+++ b/administrator/components/com_cache/src/Model/CacheModel.php
@@ -1,4 +1,5 @@
setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
-
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 3.5
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to get cache data
- *
- * @return array
- */
- public function getData()
- {
- if (empty($this->_data))
- {
- try
- {
- $cache = $this->getCache();
- $data = $cache->getAll();
-
- if ($data && \count($data) > 0)
- {
- // Process filter by search term.
- if ($search = $this->getState('filter.search'))
- {
- foreach ($data as $key => $cacheItem)
- {
- if (stripos($cacheItem->group, $search) === false)
- {
- unset($data[$key]);
- }
- }
- }
-
- // Process ordering.
- $listOrder = $this->getState('list.ordering', 'group');
- $listDirn = $this->getState('list.direction', 'ASC');
-
- $this->_data = ArrayHelper::sortObjects($data, $listOrder, strtolower($listDirn) === 'desc' ? -1 : 1, true, true);
-
- // Process pagination.
- $limit = (int) $this->getState('list.limit', 25);
-
- if ($limit !== 0)
- {
- $start = (int) $this->getState('list.start', 0);
-
- return \array_slice($this->_data, $start, $limit);
- }
- }
- else
- {
- $this->_data = array();
- }
- }
- catch (CacheConnectingException $exception)
- {
- $this->setError(Text::_('COM_CACHE_ERROR_CACHE_CONNECTION_FAILED'));
- $this->_data = array();
- }
- catch (UnsupportedCacheException $exception)
- {
- $this->setError(Text::_('COM_CACHE_ERROR_CACHE_DRIVER_UNSUPPORTED'));
- $this->_data = array();
- }
- }
-
- return $this->_data;
- }
-
- /**
- * Method to get cache instance.
- *
- * @return CacheController
- */
- public function getCache()
- {
- $app = Factory::getApplication();
-
- $options = array(
- 'defaultgroup' => '',
- 'storage' => $app->get('cache_handler', ''),
- 'caching' => true,
- 'cachebase' => $app->get('cache_path', JPATH_CACHE)
- );
-
- return Cache::getInstance('', $options);
- }
-
- /**
- * Get the number of current Cache Groups.
- *
- * @return integer
- */
- public function getTotal()
- {
- if (empty($this->_total))
- {
- $this->_total = count($this->getData());
- }
-
- return $this->_total;
- }
-
- /**
- * Method to get a pagination object for the cache.
- *
- * @return Pagination
- */
- public function getPagination()
- {
- if (empty($this->_pagination))
- {
- $this->_pagination = new Pagination($this->getTotal(), $this->getState('list.start'), $this->getState('list.limit'));
- }
-
- return $this->_pagination;
- }
-
- /**
- * Clean out a cache group as named by param.
- * If no param is passed clean all cache groups.
- *
- * @param string $group Cache group name.
- *
- * @return boolean True on success, false otherwise
- */
- public function clean($group = '')
- {
- try
- {
- $this->getCache()->clean($group);
- }
- catch (CacheConnectingException $exception)
- {
- return false;
- }
- catch (UnsupportedCacheException $exception)
- {
- return false;
- }
-
- Factory::getApplication()->triggerEvent('onAfterPurge', array($group));
-
- return true;
- }
-
- /**
- * Purge an array of cache groups.
- *
- * @param array $array Array of cache group names.
- *
- * @return array Array with errors, if they exist.
- */
- public function cleanlist($array)
- {
- $errors = array();
-
- foreach ($array as $group)
- {
- if (!$this->clean($group))
- {
- $errors[] = $group;
- }
- }
-
- return $errors;
- }
-
- /**
- * Purge all cache items.
- *
- * @return boolean True if successful; false otherwise.
- */
- public function purge()
- {
- try
- {
- Factory::getCache('')->gc();
- }
- catch (CacheConnectingException $exception)
- {
- return false;
- }
- catch (UnsupportedCacheException $exception)
- {
- return false;
- }
-
- Factory::getApplication()->triggerEvent('onAfterPurge', array());
-
- return true;
- }
+ /**
+ * An Array of CacheItems indexed by cache group ID
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Group total
+ *
+ * @var integer
+ */
+ protected $_total = null;
+
+ /**
+ * Pagination object
+ *
+ * @var object
+ */
+ protected $_pagination = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 3.5
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'group',
+ 'count',
+ 'size',
+ 'client_id',
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering Field for ordering.
+ * @param string $direction Direction of ordering.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'group', $direction = 'asc')
+ {
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 3.5
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to get cache data
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ if (empty($this->_data)) {
+ try {
+ $cache = $this->getCache();
+ $data = $cache->getAll();
+
+ if ($data && \count($data) > 0) {
+ // Process filter by search term.
+ if ($search = $this->getState('filter.search')) {
+ foreach ($data as $key => $cacheItem) {
+ if (stripos($cacheItem->group, $search) === false) {
+ unset($data[$key]);
+ }
+ }
+ }
+
+ // Process ordering.
+ $listOrder = $this->getState('list.ordering', 'group');
+ $listDirn = $this->getState('list.direction', 'ASC');
+
+ $this->_data = ArrayHelper::sortObjects($data, $listOrder, strtolower($listDirn) === 'desc' ? -1 : 1, true, true);
+
+ // Process pagination.
+ $limit = (int) $this->getState('list.limit', 25);
+
+ if ($limit !== 0) {
+ $start = (int) $this->getState('list.start', 0);
+
+ return \array_slice($this->_data, $start, $limit);
+ }
+ } else {
+ $this->_data = array();
+ }
+ } catch (CacheConnectingException $exception) {
+ $this->setError(Text::_('COM_CACHE_ERROR_CACHE_CONNECTION_FAILED'));
+ $this->_data = array();
+ } catch (UnsupportedCacheException $exception) {
+ $this->setError(Text::_('COM_CACHE_ERROR_CACHE_DRIVER_UNSUPPORTED'));
+ $this->_data = array();
+ }
+ }
+
+ return $this->_data;
+ }
+
+ /**
+ * Method to get cache instance.
+ *
+ * @return CacheController
+ */
+ public function getCache()
+ {
+ $app = Factory::getApplication();
+
+ $options = array(
+ 'defaultgroup' => '',
+ 'storage' => $app->get('cache_handler', ''),
+ 'caching' => true,
+ 'cachebase' => $app->get('cache_path', JPATH_CACHE)
+ );
+
+ return Cache::getInstance('', $options);
+ }
+
+ /**
+ * Get the number of current Cache Groups.
+ *
+ * @return integer
+ */
+ public function getTotal()
+ {
+ if (empty($this->_total)) {
+ $this->_total = count($this->getData());
+ }
+
+ return $this->_total;
+ }
+
+ /**
+ * Method to get a pagination object for the cache.
+ *
+ * @return Pagination
+ */
+ public function getPagination()
+ {
+ if (empty($this->_pagination)) {
+ $this->_pagination = new Pagination($this->getTotal(), $this->getState('list.start'), $this->getState('list.limit'));
+ }
+
+ return $this->_pagination;
+ }
+
+ /**
+ * Clean out a cache group as named by param.
+ * If no param is passed clean all cache groups.
+ *
+ * @param string $group Cache group name.
+ *
+ * @return boolean True on success, false otherwise
+ */
+ public function clean($group = '')
+ {
+ try {
+ $this->getCache()->clean($group);
+ } catch (CacheConnectingException $exception) {
+ return false;
+ } catch (UnsupportedCacheException $exception) {
+ return false;
+ }
+
+ Factory::getApplication()->triggerEvent('onAfterPurge', array($group));
+
+ return true;
+ }
+
+ /**
+ * Purge an array of cache groups.
+ *
+ * @param array $array Array of cache group names.
+ *
+ * @return array Array with errors, if they exist.
+ */
+ public function cleanlist($array)
+ {
+ $errors = array();
+
+ foreach ($array as $group) {
+ if (!$this->clean($group)) {
+ $errors[] = $group;
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Purge all cache items.
+ *
+ * @return boolean True if successful; false otherwise.
+ */
+ public function purge()
+ {
+ try {
+ Factory::getCache('')->gc();
+ } catch (CacheConnectingException $exception) {
+ return false;
+ } catch (UnsupportedCacheException $exception) {
+ return false;
+ }
+
+ Factory::getApplication()->triggerEvent('onAfterPurge', array());
+
+ return true;
+ }
}
diff --git a/administrator/components/com_categories/helpers/categories.php b/administrator/components/com_categories/helpers/categories.php
index b3caa9bf86cd5..a83cf756e457d 100644
--- a/administrator/components/com_categories/helpers/categories.php
+++ b/administrator/components/com_categories/helpers/categories.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Categories helper.
diff --git a/administrator/components/com_categories/layouts/joomla/form/field/categoryedit.php b/administrator/components/com_categories/layouts/joomla/form/field/categoryedit.php
index e4d3b0d7cded9..a94ab5edcd4bb 100644
--- a/administrator/components/com_categories/layouts/joomla/form/field/categoryedit.php
+++ b/administrator/components/com_categories/layouts/joomla/form/field/categoryedit.php
@@ -1,4 +1,5 @@
value as array
- if ($multiple && is_array($value))
- {
- if (!count($value))
- {
- $value[] = '';
- }
-
- foreach ($value as $val)
- {
- $html[] = ' ';
- }
- }
- else
- {
- $html[] = ' ';
- }
-}
-else
-{
- // Create a regular list.
- if (count($options) === 0)
- {
- // All Categories have been deleted, so we need a new category (This will create on save if selected).
- $options[0] = new \stdClass;
- $options[0]->value = 'Uncategorised';
- $options[0]->text = 'Uncategorised';
- $options[0]->level = '1';
- $options[0]->published = '1';
- $options[0]->lft = '1';
- }
-
- $html[] = HTMLHelper::_('select.genericlist', $options, $name, trim($attr), 'value', 'text', $value, $id);
+if ($readonly) {
+ $html[] = HTMLHelper::_('select.genericlist', $options, '', trim($attr), 'value', 'text', $value, $id);
+
+ // E.g. form field type tag sends $this->value as array
+ if ($multiple && is_array($value)) {
+ if (!count($value)) {
+ $value[] = '';
+ }
+
+ foreach ($value as $val) {
+ $html[] = ' ';
+ }
+ } else {
+ $html[] = ' ';
+ }
+} else {
+ // Create a regular list.
+ if (count($options) === 0) {
+ // All Categories have been deleted, so we need a new category (This will create on save if selected).
+ $options[0] = new \stdClass();
+ $options[0]->value = 'Uncategorised';
+ $options[0]->text = 'Uncategorised';
+ $options[0]->level = '1';
+ $options[0]->published = '1';
+ $options[0]->lft = '1';
+ }
+
+ $html[] = HTMLHelper::_('select.genericlist', $options, $name, trim($attr), 'value', 'text', $value, $id);
}
-if ($refreshPage === true)
-{
- $attr2 .= ' data-refresh-catid="' . $refreshCatId . '" data-refresh-section="' . $refreshSection . '"';
- $attr2 .= ' onchange="Joomla.categoryHasChanged(this)"';
+if ($refreshPage === true) {
+ $attr2 .= ' data-refresh-catid="' . $refreshCatId . '" data-refresh-section="' . $refreshSection . '"';
+ $attr2 .= ' onchange="Joomla.categoryHasChanged(this)"';
- Factory::getDocument()->getWebAssetManager()
- ->registerAndUseScript('field.category-change', 'layouts/joomla/form/field/category-change.min.js', [], ['defer' => true], ['core'])
- ->useScript('webcomponent.core-loader');
+ Factory::getDocument()->getWebAssetManager()
+ ->registerAndUseScript('field.category-change', 'layouts/joomla/form/field/category-change.min.js', [], ['defer' => true], ['core'])
+ ->useScript('webcomponent.core-loader');
- // Pass the element id to the javascript
- Factory::getDocument()->addScriptOptions('category-change', $id);
-}
-else
-{
- $attr2 .= $onchange ? ' onchange="' . $onchange . '"' : '';
+ // Pass the element id to the javascript
+ Factory::getDocument()->addScriptOptions('category-change', $id);
+} else {
+ $attr2 .= $onchange ? ' onchange="' . $onchange . '"' : '';
}
Text::script('JGLOBAL_SELECT_NO_RESULTS_MATCH');
Text::script('JGLOBAL_SELECT_PRESS_TO_SELECT');
Factory::getDocument()->getWebAssetManager()
- ->usePreset('choicesjs')
- ->useScript('webcomponent.field-fancy-select');
+ ->usePreset('choicesjs')
+ ->useScript('webcomponent.field-fancy-select');
?>
>
diff --git a/administrator/components/com_categories/services/provider.php b/administrator/components/com_categories/services/provider.php
index 2ef22e136fe12..043c4850d6425 100644
--- a/administrator/components/com_categories/services/provider.php
+++ b/administrator/components/com_categories/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Categories'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Categories'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Categories'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Categories'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new CategoriesComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new CategoriesComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_categories/src/Controller/AjaxController.php b/administrator/components/com_categories/src/Controller/AjaxController.php
index f0bdbe8e9b767..3a3398a0a0821 100644
--- a/administrator/components/com_categories/src/Controller/AjaxController.php
+++ b/administrator/components/com_categories/src/Controller/AjaxController.php
@@ -1,4 +1,5 @@
input->get('extension');
+ /**
+ * Method to fetch associations of a category
+ *
+ * The method assumes that the following http parameters are passed in an Ajax Get request:
+ * token: the form token
+ * assocId: the id of the category whose associations are to be returned
+ * excludeLang: the association for this language is to be excluded
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function fetchAssociations()
+ {
+ if (!Session::checkToken('get')) {
+ echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
+ } else {
+ $extension = $this->input->get('extension');
- $assocId = $this->input->getInt('assocId', 0);
+ $assocId = $this->input->getInt('assocId', 0);
- if ($assocId == 0)
- {
- echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
+ if ($assocId == 0) {
+ echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
- return;
- }
+ return;
+ }
- $excludeLang = $this->input->get('excludeLang', '', 'STRING');
+ $excludeLang = $this->input->get('excludeLang', '', 'STRING');
- $associations = Associations::getAssociations($extension, '#__categories', 'com_categories.item', (int) $assocId, 'id', 'alias', '');
+ $associations = Associations::getAssociations($extension, '#__categories', 'com_categories.item', (int) $assocId, 'id', 'alias', '');
- unset($associations[$excludeLang]);
+ unset($associations[$excludeLang]);
- // Add the title to each of the associated records
- Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_categories/tables');
- $categoryTable = Table::getInstance('Category', 'JTable');
+ // Add the title to each of the associated records
+ Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_categories/tables');
+ $categoryTable = Table::getInstance('Category', 'JTable');
- foreach ($associations as $lang => $association)
- {
- $categoryTable->load($association->id);
- $associations[$lang]->title = $categoryTable->title;
- }
+ foreach ($associations as $lang => $association) {
+ $categoryTable->load($association->id);
+ $associations[$lang]->title = $categoryTable->title;
+ }
- $countContentLanguages = \count(LanguageHelper::getContentLanguages(array(0, 1), false));
+ $countContentLanguages = \count(LanguageHelper::getContentLanguages(array(0, 1), false));
- if (\count($associations) == 0)
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
- }
- elseif ($countContentLanguages > \count($associations) + 2)
- {
- $tags = implode(', ', array_keys($associations));
- $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
- }
- else
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
- }
+ if (\count($associations) == 0) {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
+ } elseif ($countContentLanguages > \count($associations) + 2) {
+ $tags = implode(', ', array_keys($associations));
+ $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
+ } else {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
+ }
- echo new JsonResponse($associations, $message);
- }
- }
+ echo new JsonResponse($associations, $message);
+ }
+ }
}
diff --git a/administrator/components/com_categories/src/Controller/CategoriesController.php b/administrator/components/com_categories/src/Controller/CategoriesController.php
index ad46433211073..ca5c6ceab862e 100644
--- a/administrator/components/com_categories/src/Controller/CategoriesController.php
+++ b/administrator/components/com_categories/src/Controller/CategoriesController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Outputs the JSON-encoded amount of published content categories
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('Categories');
- $model->setState('filter.published', 1);
- $model->setState('filter.extension', 'com_content');
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_CATEGORIES_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_CATEGORIES_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
-
- /**
- * Rebuild the nested set tree.
- *
- * @return boolean False on failure or error, true on success.
- *
- * @since 1.6
- */
- public function rebuild()
- {
- $this->checkToken();
-
- $extension = $this->input->get('extension');
- $this->setRedirect(Route::_('index.php?option=com_categories&view=categories&extension=' . $extension, false));
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $model */
- $model = $this->getModel();
-
- if ($model->rebuild())
- {
- // Rebuild succeeded.
- $this->setMessage(Text::_('COM_CATEGORIES_REBUILD_SUCCESS'));
-
- return true;
- }
-
- // Rebuild failed.
- $this->setMessage(Text::_('COM_CATEGORIES_REBUILD_FAILURE'));
-
- return false;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $extension = $this->input->getCmd('extension', null);
-
- return '&extension=' . $extension;
- }
+ /**
+ * Proxy for getModel
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Category', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Outputs the JSON-encoded amount of published content categories
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Categories');
+ $model->setState('filter.published', 1);
+ $model->setState('filter.extension', 'com_content');
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_CATEGORIES_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_CATEGORIES_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
+
+ /**
+ * Rebuild the nested set tree.
+ *
+ * @return boolean False on failure or error, true on success.
+ *
+ * @since 1.6
+ */
+ public function rebuild()
+ {
+ $this->checkToken();
+
+ $extension = $this->input->get('extension');
+ $this->setRedirect(Route::_('index.php?option=com_categories&view=categories&extension=' . $extension, false));
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $model */
+ $model = $this->getModel();
+
+ if ($model->rebuild()) {
+ // Rebuild succeeded.
+ $this->setMessage(Text::_('COM_CATEGORIES_REBUILD_SUCCESS'));
+
+ return true;
+ }
+
+ // Rebuild failed.
+ $this->setMessage(Text::_('COM_CATEGORIES_REBUILD_FAILURE'));
+
+ return false;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $extension = $this->input->getCmd('extension', null);
+
+ return '&extension=' . $extension;
+ }
}
diff --git a/administrator/components/com_categories/src/Controller/CategoryController.php b/administrator/components/com_categories/src/Controller/CategoryController.php
index 0a7e746733da5..ab7fbe315e809 100644
--- a/administrator/components/com_categories/src/Controller/CategoryController.php
+++ b/administrator/components/com_categories/src/Controller/CategoryController.php
@@ -1,4 +1,5 @@
extension))
- {
- $this->extension = $this->input->get('extension', 'com_content');
- }
- }
-
- /**
- * Method to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowAdd($data = array())
- {
- $user = $this->app->getIdentity();
-
- return ($user->authorise('core.create', $this->extension) || \count($user->getAuthorisedCategories($this->extension, 'core.create')));
- }
-
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'parent_id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- // Check "edit" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->extension . '.category.' . $recordId))
- {
- return true;
- }
-
- // Check "edit own" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->extension . '.category.' . $recordId))
- {
- // Need to do a lookup from the model to get the owner
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record))
- {
- return false;
- }
-
- $ownerId = $record->created_user_id;
-
- // If the owner matches 'me' then do the test.
- if ($ownerId == $user->id)
- {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Override parent save method to store form data with right key as expected by edit category page
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 3.10.3
- */
- public function save($key = null, $urlVar = null)
- {
- $result = parent::save($key, $urlVar);
-
- $oldKey = $this->option . '.edit.category.data';
- $newKey = $this->option . '.edit.category.' . substr($this->extension, 4) . '.data';
- $this->app->setUserState($newKey, $this->app->getUserState($oldKey));
-
- return $result;
- }
-
- /**
- * Override cancel method to clear form data for a failed edit action
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return boolean True if access level checks pass, false otherwise.
- *
- * @since 3.10.3
- */
- public function cancel($key = null)
- {
- $result = parent::cancel($key);
-
- $newKey = $this->option . '.edit.category.' . substr($this->extension, 4) . '.data';
- $this->app->setUserState($newKey, null);
-
- return $result;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object|null $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 1.6
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $model */
- $model = $this->getModel('Category');
-
- // Preset the redirect
- $this->setRedirect('index.php?option=com_categories&view=categories&extension=' . $this->extension);
-
- return parent::batch($model);
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer|null $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 1.6
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
-
- // In case extension is not passed in the URL, get it directly from category instead of default to com_content
- if (!$this->input->exists('extension') && $recordId > 0)
- {
- $table = $this->getModel('Category')->getTable();
-
- if ($table->load($recordId))
- {
- $this->extension = $table->extension;
- }
- }
-
- $append .= '&extension=' . $this->extension;
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 1.6
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&extension=' . $this->extension;
-
- return $append;
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param \Joomla\CMS\MVC\Model\BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 3.1
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- $item = $model->getItem();
-
- if (isset($item->params) && \is_array($item->params))
- {
- $registry = new Registry($item->params);
- $item->params = (string) $registry;
- }
-
- if (isset($item->metadata) && \is_array($item->metadata))
- {
- $registry = new Registry($item->metadata);
- $item->metadata = (string) $registry;
- }
- }
+ use VersionableControllerTrait;
+
+ /**
+ * The extension for which the categories apply.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $extension;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface|null $factory The factory.
+ * @param CMSApplication|null $app The Application for the dispatcher
+ * @param Input|null $input Input
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, CMSApplication $app = null, Input $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ if (empty($this->extension)) {
+ $this->extension = $this->input->get('extension', 'com_content');
+ }
+ }
+
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $user = $this->app->getIdentity();
+
+ return ($user->authorise('core.create', $this->extension) || \count($user->getAuthorisedCategories($this->extension, 'core.create')));
+ }
+
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'parent_id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ // Check "edit" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->extension . '.category.' . $recordId)) {
+ return true;
+ }
+
+ // Check "edit own" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->extension . '.category.' . $recordId)) {
+ // Need to do a lookup from the model to get the owner
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record)) {
+ return false;
+ }
+
+ $ownerId = $record->created_user_id;
+
+ // If the owner matches 'me' then do the test.
+ if ($ownerId == $user->id) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Override parent save method to store form data with right key as expected by edit category page
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 3.10.3
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ $result = parent::save($key, $urlVar);
+
+ $oldKey = $this->option . '.edit.category.data';
+ $newKey = $this->option . '.edit.category.' . substr($this->extension, 4) . '.data';
+ $this->app->setUserState($newKey, $this->app->getUserState($oldKey));
+
+ return $result;
+ }
+
+ /**
+ * Override cancel method to clear form data for a failed edit action
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return boolean True if access level checks pass, false otherwise.
+ *
+ * @since 3.10.3
+ */
+ public function cancel($key = null)
+ {
+ $result = parent::cancel($key);
+
+ $newKey = $this->option . '.edit.category.' . substr($this->extension, 4) . '.data';
+ $this->app->setUserState($newKey, null);
+
+ return $result;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object|null $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 1.6
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $model */
+ $model = $this->getModel('Category');
+
+ // Preset the redirect
+ $this->setRedirect('index.php?option=com_categories&view=categories&extension=' . $this->extension);
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer|null $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 1.6
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+
+ // In case extension is not passed in the URL, get it directly from category instead of default to com_content
+ if (!$this->input->exists('extension') && $recordId > 0) {
+ $table = $this->getModel('Category')->getTable();
+
+ if ($table->load($recordId)) {
+ $this->extension = $table->extension;
+ }
+ }
+
+ $append .= '&extension=' . $this->extension;
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 1.6
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&extension=' . $this->extension;
+
+ return $append;
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param \Joomla\CMS\MVC\Model\BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ $item = $model->getItem();
+
+ if (isset($item->params) && \is_array($item->params)) {
+ $registry = new Registry($item->params);
+ $item->params = (string) $registry;
+ }
+
+ if (isset($item->metadata) && \is_array($item->metadata)) {
+ $registry = new Registry($item->metadata);
+ $item->metadata = (string) $registry;
+ }
+ }
}
diff --git a/administrator/components/com_categories/src/Controller/DisplayController.php b/administrator/components/com_categories/src/Controller/DisplayController.php
index 5f9ab4ae35db5..33f2bb00e4574 100644
--- a/administrator/components/com_categories/src/Controller/DisplayController.php
+++ b/administrator/components/com_categories/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
extension))
- {
- $this->extension = $this->input->get('extension', 'com_content');
- }
- }
-
- /**
- * Method to display a view.
- *
- * @param boolean $cachable If true, the view output will be cached
- * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
- *
- * @return static|boolean This object to support chaining.
- *
- * @since 1.5
- */
- public function display($cachable = false, $urlparams = array())
- {
- // Get the document object.
- $document = $this->app->getDocument();
-
- // Set the default view name and format from the Request.
- $vName = $this->input->get('view', 'categories');
- $vFormat = $document->getType();
- $lName = $this->input->get('layout', 'default', 'string');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if ($vName == 'category' && $lName == 'edit' && !$this->checkEditId('com_categories.edit.category', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_categories&view=categories&extension=' . $this->extension, false));
-
- return false;
- }
-
- // Get and render the view.
- if ($view = $this->getView($vName, $vFormat))
- {
- // Get the model for the view.
- $model = $this->getModel($vName, 'Administrator', array('name' => $vName . '.' . substr($this->extension, 4)));
-
- // Push the model into the view (as default).
- $view->setModel($model, true);
- $view->setLayout($lName);
-
- // Push document object into the view.
- $view->document = $document;
- $view->display();
- }
-
- return $this;
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'categories';
+
+ /**
+ * The extension for which the categories apply.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $extension;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface|null $factory The factory.
+ * @param CMSApplication|null $app The Application for the dispatcher
+ * @param Input|null $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Guess the Text message prefix. Defaults to the option.
+ if (empty($this->extension)) {
+ $this->extension = $this->input->get('extension', 'com_content');
+ }
+ }
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ // Get the document object.
+ $document = $this->app->getDocument();
+
+ // Set the default view name and format from the Request.
+ $vName = $this->input->get('view', 'categories');
+ $vFormat = $document->getType();
+ $lName = $this->input->get('layout', 'default', 'string');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if ($vName == 'category' && $lName == 'edit' && !$this->checkEditId('com_categories.edit.category', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_categories&view=categories&extension=' . $this->extension, false));
+
+ return false;
+ }
+
+ // Get and render the view.
+ if ($view = $this->getView($vName, $vFormat)) {
+ // Get the model for the view.
+ $model = $this->getModel($vName, 'Administrator', array('name' => $vName . '.' . substr($this->extension, 4)));
+
+ // Push the model into the view (as default).
+ $view->setModel($model, true);
+ $view->setLayout($lName);
+
+ // Push document object into the view.
+ $view->document = $document;
+ $view->display();
+ }
+
+ return $this;
+ }
}
diff --git a/administrator/components/com_categories/src/Dispatcher/Dispatcher.php b/administrator/components/com_categories/src/Dispatcher/Dispatcher.php
index d32c1c0ece757..4a7b59afa5969 100644
--- a/administrator/components/com_categories/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_categories/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
getApplication()->input->getCmd('extension');
+ /**
+ * Categories have to check for extension permission
+ *
+ * @return void
+ */
+ protected function checkAccess()
+ {
+ $extension = $this->getApplication()->input->getCmd('extension');
- $parts = explode('.', $extension);
+ $parts = explode('.', $extension);
- // Check the user has permission to access this component if in the backend
- if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage', $parts[0]))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ // Check the user has permission to access this component if in the backend
+ if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage', $parts[0])) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_categories/src/Extension/CategoriesComponent.php b/administrator/components/com_categories/src/Extension/CategoriesComponent.php
index 8b2eb72f2b9dd..cca327dc05da1 100644
--- a/administrator/components/com_categories/src/Extension/CategoriesComponent.php
+++ b/administrator/components/com_categories/src/Extension/CategoriesComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('categoriesadministrator', new AdministratorService);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('categoriesadministrator', new AdministratorService());
+ }
}
diff --git a/administrator/components/com_categories/src/Field/CategoryeditField.php b/administrator/components/com_categories/src/Field/CategoryeditField.php
index 4a8ccec8e8d22..b141f5e7eb4c1 100644
--- a/administrator/components/com_categories/src/Field/CategoryeditField.php
+++ b/administrator/components/com_categories/src/Field/CategoryeditField.php
@@ -1,4 +1,5 @@
tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string|null $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->allowAdd = isset($this->element['allowAdd']) ? (boolean) $this->element['allowAdd'] : false;
- $this->customPrefix = (string) $this->element['customPrefix'];
- }
-
- return $return;
- }
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 3.6
- */
- public function __get($name)
- {
- switch ($name)
- {
- case 'allowAdd':
- return (bool) $this->$name;
- case 'customPrefix':
- return $this->$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.6
- */
- public function __set($name, $value)
- {
- $value = (string) $value;
-
- switch ($name)
- {
- case 'allowAdd':
- $value = (string) $value;
- $this->$name = ($value === 'true' || $value === $name || $value === '1');
- break;
- case 'customPrefix':
- $this->$name = (string) $value;
- break;
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to get a list of categories that respects access controls and can be used for
- * either category assignment or parent category assignment in edit screens.
- * Use the parent element to indicate that the field will be used for assigning parent categories.
- *
- * @return array The field option objects.
- *
- * @since 1.6
- */
- protected function getOptions()
- {
- $options = array();
- $published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array(0, 1);
- $name = (string) $this->element['name'];
-
- // Let's get the id for the current item, either category or content item.
- $jinput = Factory::getApplication()->input;
-
- // Load the category options for a given extension.
-
- // For categories the old category is the category id or 0 for new category.
- if ($this->element['parent'] || $jinput->get('option') == 'com_categories')
- {
- $oldCat = $jinput->get('id', 0);
- $oldParent = $this->form->getValue($name, 0);
- $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $jinput->get('extension', 'com_content');
- }
- else
- // For items the old category is the category they are in when opened or 0 if new.
- {
- $oldCat = $this->form->getValue($name, 0);
- $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $jinput->get('option', 'com_content');
- }
-
- // Account for case that a submitted form has a multi-value category id field (e.g. a filtering form), just use the first category
- $oldCat = \is_array($oldCat)
- ? (int) reset($oldCat)
- : (int) $oldCat;
-
- $db = $this->getDatabase();
- $user = Factory::getUser();
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.id', 'value'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.level'),
- $db->quoteName('a.published'),
- $db->quoteName('a.lft'),
- $db->quoteName('a.language'),
- ]
- )
- ->from($db->quoteName('#__categories', 'a'));
-
- // Filter by the extension type
- if ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')
- {
- $query->where('(' . $db->quoteName('a.extension') . ' = :extension OR ' . $db->quoteName('a.parent_id') . ' = 0)')
- ->bind(':extension', $extension);
- }
- else
- {
- $query->where($db->quoteName('a.extension') . ' = :extension')
- ->bind(':extension', $extension);
- }
-
- // Filter language
- if (!empty($this->element['language']))
- {
- if (strpos($this->element['language'], ',') !== false)
- {
- $language = explode(',', $this->element['language']);
- }
- else
- {
- $language = $this->element['language'];
- }
-
- $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
- }
-
- // Filter on the published state
- $state = ArrayHelper::toInteger($published);
- $query->whereIn($db->quoteName('a.published'), $state);
-
- // Filter categories on User Access Level
- // Filter by access level on categories.
- if (!$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
-
- $query->order($db->quoteName('a.lft') . ' ASC');
-
- // If parent isn't explicitly stated but we are in com_categories assume we want parents
- if ($oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories'))
- {
- // Prevent parenting to children of this item.
- // To rearrange parents and children move the children up, not the parents down.
- $query->join(
- 'LEFT',
- $db->quoteName('#__categories', 'p'),
- $db->quoteName('p.id') . ' = :oldcat'
- )
- ->bind(':oldcat', $oldCat, ParameterType::INTEGER)
- ->where('NOT(' . $db->quoteName('a.lft') . ' >= ' . $db->quoteName('p.lft')
- . ' AND ' . $db->quoteName('a.rgt') . ' <= ' . $db->quoteName('p.rgt') . ')'
- );
- }
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- // Pad the option text with spaces using depth level as a multiplier.
- for ($i = 0, $n = \count($options); $i < $n; $i++)
- {
- // Translate ROOT
- if ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')
- {
- if ($options[$i]->level == 0)
- {
- $options[$i]->text = Text::_('JGLOBAL_ROOT_PARENT');
- }
- }
-
- if ($options[$i]->published == 1)
- {
- $options[$i]->text = str_repeat('- ', !$options[$i]->level ? 0 : $options[$i]->level - 1) . $options[$i]->text;
- }
- else
- {
- $options[$i]->text = str_repeat('- ', !$options[$i]->level ? 0 : $options[$i]->level - 1) . '[' . $options[$i]->text . ']';
- }
-
- // Displays language code if not set to All
- if ($options[$i]->language !== '*')
- {
- $options[$i]->text = $options[$i]->text . ' (' . $options[$i]->language . ')';
- }
- }
-
- // For new items we want a list of categories you are allowed to create in.
- if ($oldCat == 0)
- {
- foreach ($options as $i => $option)
- {
- /*
- * To take save or create in a category you need to have create rights for that category unless the item is already in that category.
- * Unset the option if the user isn't authorised for it. In this field assets are always categories.
- */
- if ($option->level != 0 && !$user->authorise('core.create', $extension . '.category.' . $option->value))
- {
- unset($options[$i]);
- }
- }
- }
- // If you have an existing category id things are more complex.
- else
- {
- /*
- * If you are only allowed to edit in this category but not edit.state, you should not get any
- * option to change the category parent for a category or the category for a content item,
- * but you should be able to save in that category.
- */
- foreach ($options as $i => $option)
- {
- $assetKey = $extension . '.category.' . $oldCat;
-
- if ($option->level != 0 && !isset($oldParent) && $option->value != $oldCat && !$user->authorise('core.edit.state', $assetKey))
- {
- unset($options[$i]);
- continue;
- }
-
- if ($option->level != 0 && isset($oldParent) && $option->value != $oldParent && !$user->authorise('core.edit.state', $assetKey))
- {
- unset($options[$i]);
- continue;
- }
-
- /*
- * However, if you can edit.state you can also move this to another category for which you have
- * create permission and you should also still be able to save in the current category.
- */
- $assetKey = $extension . '.category.' . $option->value;
-
- if ($option->level != 0 && !isset($oldParent) && $option->value != $oldCat && !$user->authorise('core.create', $assetKey))
- {
- unset($options[$i]);
- continue;
- }
-
- if ($option->level != 0 && isset($oldParent) && $option->value != $oldParent && !$user->authorise('core.create', $assetKey))
- {
- unset($options[$i]);
- }
- }
- }
-
- if ($oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')
- && !isset($options[0])
- && isset($this->element['show_root']))
- {
- $rowQuery = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.id', 'value'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.level'),
- $db->quoteName('a.parent_id'),
- ]
- )
- ->from($db->quoteName('#__categories', 'a'))
- ->where($db->quoteName('a.id') . ' = :aid')
- ->bind(':aid', $oldCat, ParameterType::INTEGER);
- $db->setQuery($rowQuery);
- $row = $db->loadObject();
-
- if ($row->parent_id == '1')
- {
- $parent = new \stdClass;
- $parent->text = Text::_('JGLOBAL_ROOT_PARENT');
- array_unshift($options, $parent);
- }
-
- array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('JGLOBAL_ROOT')));
- }
-
- // Merge any additional options in the XML definition.
- return array_merge(parent::getOptions(), $options);
- }
-
- /**
- * Method to get the field input markup for a generic list.
- * Use the multiple attribute to enable multiselect.
- *
- * @return string The field input markup.
- *
- * @since 3.6
- */
- protected function getInput()
- {
- $data = $this->getLayoutData();
-
- $data['options'] = $this->getOptions();
- $data['allowCustom'] = $this->allowAdd;
- $data['customPrefix'] = $this->customPrefix;
- $data['refreshPage'] = (boolean) $this->element['refresh-enabled'];
- $data['refreshCatId'] = (string) $this->element['refresh-cat-id'];
- $data['refreshSection'] = (string) $this->element['refresh-section'];
-
- $renderer = $this->getRenderer($this->layout);
- $renderer->setComponent('com_categories');
- $renderer->setClient(1);
-
- return $renderer->render($data);
- }
+ /**
+ * To allow creation of new categories.
+ *
+ * @var integer
+ * @since 3.6
+ */
+ protected $allowAdd;
+
+ /**
+ * Optional prefix for new categories.
+ *
+ * @var string
+ * @since 3.9.11
+ */
+ protected $customPrefix;
+
+ /**
+ * A flexible category list that respects access controls
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'CategoryEdit';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.categoryedit';
+
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string|null $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->allowAdd = isset($this->element['allowAdd']) ? (bool) $this->element['allowAdd'] : false;
+ $this->customPrefix = (string) $this->element['customPrefix'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.6
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'allowAdd':
+ return (bool) $this->$name;
+ case 'customPrefix':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.6
+ */
+ public function __set($name, $value)
+ {
+ $value = (string) $value;
+
+ switch ($name) {
+ case 'allowAdd':
+ $value = (string) $value;
+ $this->$name = ($value === 'true' || $value === $name || $value === '1');
+ break;
+ case 'customPrefix':
+ $this->$name = (string) $value;
+ break;
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to get a list of categories that respects access controls and can be used for
+ * either category assignment or parent category assignment in edit screens.
+ * Use the parent element to indicate that the field will be used for assigning parent categories.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.6
+ */
+ protected function getOptions()
+ {
+ $options = array();
+ $published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array(0, 1);
+ $name = (string) $this->element['name'];
+
+ // Let's get the id for the current item, either category or content item.
+ $jinput = Factory::getApplication()->input;
+
+ // Load the category options for a given extension.
+
+ // For categories the old category is the category id or 0 for new category.
+ if ($this->element['parent'] || $jinput->get('option') == 'com_categories') {
+ $oldCat = $jinput->get('id', 0);
+ $oldParent = $this->form->getValue($name, 0);
+ $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $jinput->get('extension', 'com_content');
+ } else // For items the old category is the category they are in when opened or 0 if new.
+ {
+ $oldCat = $this->form->getValue($name, 0);
+ $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $jinput->get('option', 'com_content');
+ }
+
+ // Account for case that a submitted form has a multi-value category id field (e.g. a filtering form), just use the first category
+ $oldCat = \is_array($oldCat)
+ ? (int) reset($oldCat)
+ : (int) $oldCat;
+
+ $db = $this->getDatabase();
+ $user = Factory::getUser();
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.published'),
+ $db->quoteName('a.lft'),
+ $db->quoteName('a.language'),
+ ]
+ )
+ ->from($db->quoteName('#__categories', 'a'));
+
+ // Filter by the extension type
+ if ($this->element['parent'] == true || $jinput->get('option') == 'com_categories') {
+ $query->where('(' . $db->quoteName('a.extension') . ' = :extension OR ' . $db->quoteName('a.parent_id') . ' = 0)')
+ ->bind(':extension', $extension);
+ } else {
+ $query->where($db->quoteName('a.extension') . ' = :extension')
+ ->bind(':extension', $extension);
+ }
+
+ // Filter language
+ if (!empty($this->element['language'])) {
+ if (strpos($this->element['language'], ',') !== false) {
+ $language = explode(',', $this->element['language']);
+ } else {
+ $language = $this->element['language'];
+ }
+
+ $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
+ }
+
+ // Filter on the published state
+ $state = ArrayHelper::toInteger($published);
+ $query->whereIn($db->quoteName('a.published'), $state);
+
+ // Filter categories on User Access Level
+ // Filter by access level on categories.
+ if (!$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+
+ $query->order($db->quoteName('a.lft') . ' ASC');
+
+ // If parent isn't explicitly stated but we are in com_categories assume we want parents
+ if ($oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')) {
+ // Prevent parenting to children of this item.
+ // To rearrange parents and children move the children up, not the parents down.
+ $query->join(
+ 'LEFT',
+ $db->quoteName('#__categories', 'p'),
+ $db->quoteName('p.id') . ' = :oldcat'
+ )
+ ->bind(':oldcat', $oldCat, ParameterType::INTEGER)
+ ->where('NOT(' . $db->quoteName('a.lft') . ' >= ' . $db->quoteName('p.lft')
+ . ' AND ' . $db->quoteName('a.rgt') . ' <= ' . $db->quoteName('p.rgt') . ')');
+ }
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ // Pad the option text with spaces using depth level as a multiplier.
+ for ($i = 0, $n = \count($options); $i < $n; $i++) {
+ // Translate ROOT
+ if ($this->element['parent'] == true || $jinput->get('option') == 'com_categories') {
+ if ($options[$i]->level == 0) {
+ $options[$i]->text = Text::_('JGLOBAL_ROOT_PARENT');
+ }
+ }
+
+ if ($options[$i]->published == 1) {
+ $options[$i]->text = str_repeat('- ', !$options[$i]->level ? 0 : $options[$i]->level - 1) . $options[$i]->text;
+ } else {
+ $options[$i]->text = str_repeat('- ', !$options[$i]->level ? 0 : $options[$i]->level - 1) . '[' . $options[$i]->text . ']';
+ }
+
+ // Displays language code if not set to All
+ if ($options[$i]->language !== '*') {
+ $options[$i]->text = $options[$i]->text . ' (' . $options[$i]->language . ')';
+ }
+ }
+
+ // For new items we want a list of categories you are allowed to create in.
+ if ($oldCat == 0) {
+ foreach ($options as $i => $option) {
+ /*
+ * To take save or create in a category you need to have create rights for that category unless the item is already in that category.
+ * Unset the option if the user isn't authorised for it. In this field assets are always categories.
+ */
+ if ($option->level != 0 && !$user->authorise('core.create', $extension . '.category.' . $option->value)) {
+ unset($options[$i]);
+ }
+ }
+ } else {
+ // If you have an existing category id things are more complex.
+ /*
+ * If you are only allowed to edit in this category but not edit.state, you should not get any
+ * option to change the category parent for a category or the category for a content item,
+ * but you should be able to save in that category.
+ */
+ foreach ($options as $i => $option) {
+ $assetKey = $extension . '.category.' . $oldCat;
+
+ if ($option->level != 0 && !isset($oldParent) && $option->value != $oldCat && !$user->authorise('core.edit.state', $assetKey)) {
+ unset($options[$i]);
+ continue;
+ }
+
+ if ($option->level != 0 && isset($oldParent) && $option->value != $oldParent && !$user->authorise('core.edit.state', $assetKey)) {
+ unset($options[$i]);
+ continue;
+ }
+
+ /*
+ * However, if you can edit.state you can also move this to another category for which you have
+ * create permission and you should also still be able to save in the current category.
+ */
+ $assetKey = $extension . '.category.' . $option->value;
+
+ if ($option->level != 0 && !isset($oldParent) && $option->value != $oldCat && !$user->authorise('core.create', $assetKey)) {
+ unset($options[$i]);
+ continue;
+ }
+
+ if ($option->level != 0 && isset($oldParent) && $option->value != $oldParent && !$user->authorise('core.create', $assetKey)) {
+ unset($options[$i]);
+ }
+ }
+ }
+
+ if (
+ $oldCat != 0 && ($this->element['parent'] == true || $jinput->get('option') == 'com_categories')
+ && !isset($options[0])
+ && isset($this->element['show_root'])
+ ) {
+ $rowQuery = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.parent_id'),
+ ]
+ )
+ ->from($db->quoteName('#__categories', 'a'))
+ ->where($db->quoteName('a.id') . ' = :aid')
+ ->bind(':aid', $oldCat, ParameterType::INTEGER);
+ $db->setQuery($rowQuery);
+ $row = $db->loadObject();
+
+ if ($row->parent_id == '1') {
+ $parent = new \stdClass();
+ $parent->text = Text::_('JGLOBAL_ROOT_PARENT');
+ array_unshift($options, $parent);
+ }
+
+ array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('JGLOBAL_ROOT')));
+ }
+
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getOptions(), $options);
+ }
+
+ /**
+ * Method to get the field input markup for a generic list.
+ * Use the multiple attribute to enable multiselect.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.6
+ */
+ protected function getInput()
+ {
+ $data = $this->getLayoutData();
+
+ $data['options'] = $this->getOptions();
+ $data['allowCustom'] = $this->allowAdd;
+ $data['customPrefix'] = $this->customPrefix;
+ $data['refreshPage'] = (bool) $this->element['refresh-enabled'];
+ $data['refreshCatId'] = (string) $this->element['refresh-cat-id'];
+ $data['refreshSection'] = (string) $this->element['refresh-section'];
+
+ $renderer = $this->getRenderer($this->layout);
+ $renderer->setComponent('com_categories');
+ $renderer->setClient(1);
+
+ return $renderer->render($data);
+ }
}
diff --git a/administrator/components/com_categories/src/Field/ComponentsCategoryField.php b/administrator/components/com_categories/src/Field/ComponentsCategoryField.php
index 8fd9838dd588a..3efedd4cd8d37 100644
--- a/administrator/components/com_categories/src/Field/ComponentsCategoryField.php
+++ b/administrator/components/com_categories/src/Field/ComponentsCategoryField.php
@@ -1,4 +1,5 @@
getDatabase();
- $options = array();
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ // Initialise variable.
+ $db = $this->getDatabase();
+ $options = array();
- $query = $db->getQuery(true);
- $query->select('DISTINCT ' . $db->quoteName('extension'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('extension') . ' != ' . $db->quote('system'));
+ $query = $db->getQuery(true);
+ $query->select('DISTINCT ' . $db->quoteName('extension'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('extension') . ' != ' . $db->quote('system'));
- $db->setQuery($query);
- $categoryTypes = $db->loadColumn();
+ $db->setQuery($query);
+ $categoryTypes = $db->loadColumn();
- foreach ($categoryTypes as $categoryType)
- {
- $option = new \stdClass;
- $option->value = $categoryType;
+ foreach ($categoryTypes as $categoryType) {
+ $option = new \stdClass();
+ $option->value = $categoryType;
- // Extract the component name and optional section name
- $parts = explode('.', $categoryType);
- $component = $parts[0];
- $section = (\count($parts) > 1) ? $parts[1] : null;
+ // Extract the component name and optional section name
+ $parts = explode('.', $categoryType);
+ $component = $parts[0];
+ $section = (\count($parts) > 1) ? $parts[1] : null;
- // Load component language files
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE)
- || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
+ // Load component language files
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE)
+ || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
- // If the component section string exists, let's use it
- if ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : ''))))
- {
- $option->text = Text::_($component_section_key);
- }
- else
- // Else use the component title
- {
- $option->text = Text::_(strtoupper($component));
- }
+ // If the component section string exists, let's use it
+ if ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : '')))) {
+ $option->text = Text::_($component_section_key);
+ } else // Else use the component title
+ {
+ $option->text = Text::_(strtoupper($component));
+ }
- $options[] = $option;
- }
+ $options[] = $option;
+ }
- // Sort by name
- $options = ArrayHelper::sortObjects($options, 'text', 1, true, true);
+ // Sort by name
+ $options = ArrayHelper::sortObjects($options, 'text', 1, true, true);
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_categories/src/Field/Modal/CategoryField.php b/administrator/components/com_categories/src/Field/Modal/CategoryField.php
index 70c1f5ff18237..0685b050afcc4 100644
--- a/administrator/components/com_categories/src/Field/Modal/CategoryField.php
+++ b/administrator/components/com_categories/src/Field/Modal/CategoryField.php
@@ -1,4 +1,5 @@
element['extension'])
- {
- $extension = (string) $this->element['extension'];
- }
- else
- {
- $extension = (string) Factory::getApplication()->input->get('extension', 'com_content');
- }
-
- $allowNew = ((string) $this->element['new'] == 'true');
- $allowEdit = ((string) $this->element['edit'] == 'true');
- $allowClear = ((string) $this->element['clear'] != 'false');
- $allowSelect = ((string) $this->element['select'] != 'false');
- $allowPropagate = ((string) $this->element['propagate'] == 'true');
-
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- // Load language.
- Factory::getLanguage()->load('com_categories', JPATH_ADMINISTRATOR);
-
- // The active category id field.
- $value = (int) $this->value ?: '';
-
- // Create the modal id.
- $modalId = 'Category_' . $this->id;
-
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
-
- // Add the modal field script to the document head.
- $wa->useScript('field.modal-fields');
-
- // Script to proxy the select modal function to the modal-fields.js file.
- if ($allowSelect)
- {
- static $scriptSelect = null;
-
- if (is_null($scriptSelect))
- {
- $scriptSelect = array();
- }
-
- if (!isset($scriptSelect[$this->id]))
- {
- $wa->addInlineScript("
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'Modal_Category';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ if ($this->element['extension']) {
+ $extension = (string) $this->element['extension'];
+ } else {
+ $extension = (string) Factory::getApplication()->input->get('extension', 'com_content');
+ }
+
+ $allowNew = ((string) $this->element['new'] == 'true');
+ $allowEdit = ((string) $this->element['edit'] == 'true');
+ $allowClear = ((string) $this->element['clear'] != 'false');
+ $allowSelect = ((string) $this->element['select'] != 'false');
+ $allowPropagate = ((string) $this->element['propagate'] == 'true');
+
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ // Load language.
+ Factory::getLanguage()->load('com_categories', JPATH_ADMINISTRATOR);
+
+ // The active category id field.
+ $value = (int) $this->value ?: '';
+
+ // Create the modal id.
+ $modalId = 'Category_' . $this->id;
+
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+
+ // Add the modal field script to the document head.
+ $wa->useScript('field.modal-fields');
+
+ // Script to proxy the select modal function to the modal-fields.js file.
+ if ($allowSelect) {
+ static $scriptSelect = null;
+
+ if (is_null($scriptSelect)) {
+ $scriptSelect = array();
+ }
+
+ if (!isset($scriptSelect[$this->id])) {
+ $wa->addInlineScript(
+ "
window.jSelectCategory_" . $this->id . " = function (id, title, object) {
window.processModalSelect('Category', '" . $this->id . "', id, title, '', object);
}",
- [],
- ['type' => 'module']
- );
-
- Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
-
- $scriptSelect[$this->id] = true;
- }
- }
-
- // Setup variables for display.
- $linkCategories = 'index.php?option=com_categories&view=categories&layout=modal&tmpl=component&' . Session::getFormToken() . '=1'
- . '&extension=' . $extension;
- $linkCategory = 'index.php?option=com_categories&view=category&layout=modal&tmpl=component&' . Session::getFormToken() . '=1'
- . '&extension=' . $extension;
- $modalTitle = Text::_('COM_CATEGORIES_SELECT_A_CATEGORY');
-
- if (isset($this->element['language']))
- {
- $linkCategories .= '&forcedLanguage=' . $this->element['language'];
- $linkCategory .= '&forcedLanguage=' . $this->element['language'];
- $modalTitle .= ' — ' . $this->element['label'];
- }
-
- $urlSelect = $linkCategories . '&function=jSelectCategory_' . $this->id;
- $urlEdit = $linkCategory . '&task=category.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
- $urlNew = $linkCategory . '&task=category.add';
-
- if ($value)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('id') . ' = :value')
- ->bind(':value', $value, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $title = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- $title = empty($title) ? Text::_('COM_CATEGORIES_SELECT_A_CATEGORY') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
-
- // The current category display field.
- $html = '';
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= '';
- }
-
- $html .= ' ';
-
- // Select category button.
- if ($allowSelect)
- {
- $html .= ''
- . ' ' . Text::_('JSELECT')
- . ' ';
- }
-
- // New category button.
- if ($allowNew)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_CREATE')
- . ' ';
- }
-
- // Edit category button.
- if ($allowEdit)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_EDIT')
- . ' ';
- }
-
- // Clear category button.
- if ($allowClear)
- {
- $html .= ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
- }
-
- // Propagate category button
- if ($allowPropagate && \count($languages) > 2)
- {
- // Strip off language tag at the end
- $tagLength = (int) \strlen($this->element['language']);
- $callbackFunctionStem = substr("jSelectCategory_" . $this->id, 0, -$tagLength);
-
- $html .= ''
- . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
- . ' ';
- }
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= ' ';
- }
-
- // Select category modal.
- if ($allowSelect)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalSelect' . $modalId,
- array(
- 'title' => $modalTitle,
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
- }
-
- // New category modal.
- if ($allowNew)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalNew' . $modalId,
- array(
- 'title' => Text::_('COM_CATEGORIES_NEW_CATEGORY'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlNew,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Edit category modal.
- if ($allowEdit)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalEdit' . $modalId,
- array(
- 'title' => Text::_('COM_CATEGORIES_EDIT_CATEGORY'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlEdit,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Note: class='required' for client side validation
- $class = $this->required ? ' class="required modal-value"' : '';
-
- $html .= ' ';
-
- return $html;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 3.7.0
- */
- protected function getLabel()
- {
- return str_replace($this->id, $this->id . '_name', parent::getLabel());
- }
+ [],
+ ['type' => 'module']
+ );
+
+ Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
+
+ $scriptSelect[$this->id] = true;
+ }
+ }
+
+ // Setup variables for display.
+ $linkCategories = 'index.php?option=com_categories&view=categories&layout=modal&tmpl=component&' . Session::getFormToken() . '=1'
+ . '&extension=' . $extension;
+ $linkCategory = 'index.php?option=com_categories&view=category&layout=modal&tmpl=component&' . Session::getFormToken() . '=1'
+ . '&extension=' . $extension;
+ $modalTitle = Text::_('COM_CATEGORIES_SELECT_A_CATEGORY');
+
+ if (isset($this->element['language'])) {
+ $linkCategories .= '&forcedLanguage=' . $this->element['language'];
+ $linkCategory .= '&forcedLanguage=' . $this->element['language'];
+ $modalTitle .= ' — ' . $this->element['label'];
+ }
+
+ $urlSelect = $linkCategories . '&function=jSelectCategory_' . $this->id;
+ $urlEdit = $linkCategory . '&task=category.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
+ $urlNew = $linkCategory . '&task=category.add';
+
+ if ($value) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('id') . ' = :value')
+ ->bind(':value', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $title = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $title = empty($title) ? Text::_('COM_CATEGORIES_SELECT_A_CATEGORY') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
+
+ // The current category display field.
+ $html = '';
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= '';
+ }
+
+ $html .= ' ';
+
+ // Select category button.
+ if ($allowSelect) {
+ $html .= ''
+ . ' ' . Text::_('JSELECT')
+ . ' ';
+ }
+
+ // New category button.
+ if ($allowNew) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_CREATE')
+ . ' ';
+ }
+
+ // Edit category button.
+ if ($allowEdit) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_EDIT')
+ . ' ';
+ }
+
+ // Clear category button.
+ if ($allowClear) {
+ $html .= ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+ }
+
+ // Propagate category button
+ if ($allowPropagate && \count($languages) > 2) {
+ // Strip off language tag at the end
+ $tagLength = (int) \strlen($this->element['language']);
+ $callbackFunctionStem = substr("jSelectCategory_" . $this->id, 0, -$tagLength);
+
+ $html .= ''
+ . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
+ . ' ';
+ }
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= ' ';
+ }
+
+ // Select category modal.
+ if ($allowSelect) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalSelect' . $modalId,
+ array(
+ 'title' => $modalTitle,
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+ }
+
+ // New category modal.
+ if ($allowNew) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalNew' . $modalId,
+ array(
+ 'title' => Text::_('COM_CATEGORIES_NEW_CATEGORY'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlNew,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Edit category modal.
+ if ($allowEdit) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalEdit' . $modalId,
+ array(
+ 'title' => Text::_('COM_CATEGORIES_EDIT_CATEGORY'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlEdit,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Note: class='required' for client side validation
+ $class = $this->required ? ' class="required modal-value"' : '';
+
+ $html .= ' ';
+
+ return $html;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getLabel()
+ {
+ return str_replace($this->id, $this->id . '_name', parent::getLabel());
+ }
}
diff --git a/administrator/components/com_categories/src/Helper/CategoriesHelper.php b/administrator/components/com_categories/src/Helper/CategoriesHelper.php
index dfaacc58ae81c..753432eb44fb0 100644
--- a/administrator/components/com_categories/src/Helper/CategoriesHelper.php
+++ b/administrator/components/com_categories/src/Helper/CategoriesHelper.php
@@ -1,4 +1,5 @@
getAuthorisedViewLevels();
-
- foreach ($langAssociations as $langAssociation)
- {
- // Include only published categories with user access
- $arrId = explode(':', $langAssociation->id);
- $assocId = (int) $arrId[0];
- $db = Factory::getDbo();
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('published'))
- ->from($db->quoteName('#__categories'))
- ->whereIn($db->quoteName('access'), $groups)
- ->where($db->quoteName('id') . ' = :associd')
- ->bind(':associd', $assocId, ParameterType::INTEGER);
-
- $result = (int) $db->setQuery($query)->loadResult();
-
- if ($result === 1)
- {
- $associations[$langAssociation->language] = $langAssociation->id;
- }
- }
-
- return $associations;
- }
-
- /**
- * Check if Category ID exists otherwise assign to ROOT category.
- *
- * @param mixed $catid Name or ID of category.
- * @param string $extension Extension that triggers this function
- *
- * @return integer $catid Category ID.
- */
- public static function validateCategoryId($catid, $extension)
- {
- $categoryTable = Table::getInstance('CategoryTable', '\\Joomla\\Component\\Categories\\Administrator\\Table\\');
-
- $data = array();
- $data['id'] = $catid;
- $data['extension'] = $extension;
-
- if (!$categoryTable->load($data))
- {
- $catid = 0;
- }
-
- return (int) $catid;
- }
-
- /**
- * Create new Category from within item view.
- *
- * @param array $data Array of data for new category.
- *
- * @return integer
- */
- public static function createCategory($data)
- {
- $categoryModel = Factory::getApplication()->bootComponent('com_categories')
- ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
- $categoryModel->save($data);
-
- $catid = $categoryModel->getState('category.id');
-
- return $catid;
- }
+ /**
+ * Gets a list of associations for a given item.
+ *
+ * @param integer $pk Content item key.
+ * @param string $extension Optional extension name.
+ *
+ * @return array of associations.
+ */
+ public static function getAssociations($pk, $extension = 'com_content')
+ {
+ $langAssociations = Associations::getAssociations($extension, '#__categories', 'com_categories.item', $pk, 'id', 'alias', '');
+ $associations = array();
+ $user = Factory::getUser();
+ $groups = $user->getAuthorisedViewLevels();
+
+ foreach ($langAssociations as $langAssociation) {
+ // Include only published categories with user access
+ $arrId = explode(':', $langAssociation->id);
+ $assocId = (int) $arrId[0];
+ $db = Factory::getDbo();
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('published'))
+ ->from($db->quoteName('#__categories'))
+ ->whereIn($db->quoteName('access'), $groups)
+ ->where($db->quoteName('id') . ' = :associd')
+ ->bind(':associd', $assocId, ParameterType::INTEGER);
+
+ $result = (int) $db->setQuery($query)->loadResult();
+
+ if ($result === 1) {
+ $associations[$langAssociation->language] = $langAssociation->id;
+ }
+ }
+
+ return $associations;
+ }
+
+ /**
+ * Check if Category ID exists otherwise assign to ROOT category.
+ *
+ * @param mixed $catid Name or ID of category.
+ * @param string $extension Extension that triggers this function
+ *
+ * @return integer $catid Category ID.
+ */
+ public static function validateCategoryId($catid, $extension)
+ {
+ $categoryTable = Table::getInstance('CategoryTable', '\\Joomla\\Component\\Categories\\Administrator\\Table\\');
+
+ $data = array();
+ $data['id'] = $catid;
+ $data['extension'] = $extension;
+
+ if (!$categoryTable->load($data)) {
+ $catid = 0;
+ }
+
+ return (int) $catid;
+ }
+
+ /**
+ * Create new Category from within item view.
+ *
+ * @param array $data Array of data for new category.
+ *
+ * @return integer
+ */
+ public static function createCategory($data)
+ {
+ $categoryModel = Factory::getApplication()->bootComponent('com_categories')
+ ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
+ $categoryModel->save($data);
+
+ $catid = $categoryModel->getState('category.id');
+
+ return $catid;
+ }
}
diff --git a/administrator/components/com_categories/src/Helper/CategoryAssociationHelper.php b/administrator/components/com_categories/src/Helper/CategoryAssociationHelper.php
index 17ada8cf21648..b8357e0b910f4 100644
--- a/administrator/components/com_categories/src/Helper/CategoryAssociationHelper.php
+++ b/administrator/components/com_categories/src/Helper/CategoryAssociationHelper.php
@@ -1,4 +1,5 @@
$item)
- {
- if (class_exists($helperClassname) && \is_callable(array($helperClassname, 'getCategoryRoute')))
- {
- $return[$tag] = $helperClassname::getCategoryRoute($item, $tag, $layout);
- }
- else
- {
- $viewLayout = $layout ? '&layout=' . $layout : '';
-
- $return[$tag] = 'index.php?option=' . $extension . '&view=category&id=' . $item . $viewLayout;
- }
- }
- }
-
- return $return;
- }
+ /**
+ * Flag if associations are present for categories
+ *
+ * @var boolean
+ * @since 3.0
+ */
+ public static $category_association = true;
+
+ /**
+ * Method to get the associations for a given category
+ *
+ * @param integer $id Id of the item
+ * @param string $extension Name of the component
+ * @param string|null $layout Category layout
+ *
+ * @return array Array of associations for the component categories
+ *
+ * @since 3.0
+ */
+ public static function getCategoryAssociations($id = 0, $extension = 'com_content', $layout = null)
+ {
+ $return = array();
+
+ if ($id) {
+ $helperClassname = ucfirst(substr($extension, 4)) . 'HelperRoute';
+
+ $associations = CategoriesHelper::getAssociations($id, $extension);
+
+ foreach ($associations as $tag => $item) {
+ if (class_exists($helperClassname) && \is_callable(array($helperClassname, 'getCategoryRoute'))) {
+ $return[$tag] = $helperClassname::getCategoryRoute($item, $tag, $layout);
+ } else {
+ $viewLayout = $layout ? '&layout=' . $layout : '';
+
+ $return[$tag] = 'index.php?option=' . $extension . '&view=category&id=' . $item . $viewLayout;
+ }
+ }
+ }
+
+ return $return;
+ }
}
diff --git a/administrator/components/com_categories/src/Model/CategoriesModel.php b/administrator/components/com_categories/src/Model/CategoriesModel.php
index 5637b90d2446a..f0d1b84126eaa 100644
--- a/administrator/components/com_categories/src/Model/CategoriesModel.php
+++ b/administrator/components/com_categories/src/Model/CategoriesModel.php
@@ -1,4 +1,5 @@
input->get('forcedLanguage', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', 'com_content', 'cmd');
-
- $this->setState('filter.extension', $extension);
- $parts = explode('.', $extension);
-
- // Extract the component name
- $this->setState('filter.component', $parts[0]);
-
- // Extract the optional section name
- $this->setState('filter.section', (\count($parts) > 1) ? $parts[1] : null);
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language.
- if (!empty($forcedLanguage))
- {
- $this->setState('filter.language', $forcedLanguage);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.extension');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . $this->getState('filter.level');
- $id .= ':' . serialize($this->getState('filter.tag'));
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to get a database query to list categories.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.id, a.title, a.alias, a.note, a.published, a.access' .
- ', a.checked_out, a.checked_out_time, a.created_user_id' .
- ', a.path, a.parent_id, a.level, a.lft, a.rgt' .
- ', a.language'
- )
- );
- $query->from($db->quoteName('#__categories', 'a'));
-
- // Join over the language
- $query->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- ]
- )
- ->join(
- 'LEFT',
- $db->quoteName('#__languages', 'l'),
- $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')
- );
-
- // Join over the users for the checked out user.
- $query->select($db->quoteName('uc.name', 'editor'))
- ->join(
- 'LEFT',
- $db->quoteName('#__users', 'uc'),
- $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')
- );
-
- // Join over the asset groups.
- $query->select($db->quoteName('ag.title', 'access_level'))
- ->join(
- 'LEFT',
- $db->quoteName('#__viewlevels', 'ag'),
- $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')
- );
-
- // Join over the users for the author.
- $query->select($db->quoteName('ua.name', 'author_name'))
- ->join(
- 'LEFT',
- $db->quoteName('#__users', 'ua'),
- $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id')
- );
-
- // Join over the associations.
- $assoc = $this->getAssoc();
-
- if ($assoc)
- {
- $query->select('COUNT(asso2.id)>1 as association')
- ->join(
- 'LEFT',
- $db->quoteName('#__associations', 'asso'),
- $db->quoteName('asso.id') . ' = ' . $db->quoteName('a.id')
- . ' AND ' . $db->quoteName('asso.context') . ' = ' . $db->quote('com_categories.item')
- )
- ->join(
- 'LEFT',
- $db->quoteName('#__associations', 'asso2'),
- $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
- )
- ->group('a.id, l.title, uc.name, ag.title, ua.name');
- }
-
- // Filter by extension
- if ($extension = $this->getState('filter.extension'))
- {
- $query->where($db->quoteName('a.extension') . ' = :extension')
- ->bind(':extension', $extension);
- }
-
- // Filter on the level.
- if ($level = (int) $this->getState('filter.level'))
- {
- $query->where($db->quoteName('a.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter by access level.
- if ($access = (int) $this->getState('filter.access'))
- {
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
-
- // Filter by published state
- $published = (string) $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.published') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->whereIn($db->quoteName('a.published'), [0, 1]);
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.title') . ' LIKE :title',
- $db->quoteName('a.alias') . ' LIKE :alias',
- $db->quoteName('a.note') . ' LIKE :note',
- ],
- 'OR'
- )
- ->bind(':title', $search)
- ->bind(':alias', $search)
- ->bind(':note', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
-
- // Filter by a single or group of tags.
- $tag = $this->getState('filter.tag');
- $typeAlias = $extension . '.category';
-
- // Run simplified query when filtering by one tag.
- if (\is_array($tag) && \count($tag) === 1)
- {
- $tag = $tag[0];
- }
-
- if ($tag && \is_array($tag))
- {
- $tag = ArrayHelper::toInteger($tag);
-
- $subQuery = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('content_item_id'))
- ->from($db->quoteName('#__contentitem_tag_map'))
- ->where(
- [
- $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
- $db->quoteName('type_alias') . ' = :typeAlias',
- ]
- );
-
- $query->join(
- 'INNER',
- '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- )
- ->bind(':typeAlias', $typeAlias);
- }
- elseif ($tag = (int) $tag)
- {
- $query->join(
- 'INNER',
- $db->quoteName('#__contentitem_tag_map', 'tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- )
- ->where(
- [
- $db->quoteName('tagmap.tag_id') . ' = :tag',
- $db->quoteName('tagmap.type_alias') . ' = :typeAlias',
- ]
- )
- ->bind(':tag', $tag, ParameterType::INTEGER)
- ->bind(':typeAlias', $typeAlias);
- }
-
- // Add the list ordering clause
- $listOrdering = $this->getState('list.ordering', 'a.lft');
- $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
-
- if ($listOrdering == 'a.access')
- {
- $query->order('a.access ' . $listDirn . ', a.lft ' . $listDirn);
- }
- else
- {
- $query->order($db->escape($listOrdering) . ' ' . $listDirn);
- }
-
- // Group by on Categories for \JOIN with component tables to count items
- $query->group('a.id,
+ /**
+ * Does an association exist? Caches the result of getAssoc().
+ *
+ * @var boolean|null
+ * @since 4.0.5
+ */
+ private $hasAssociation;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface|null $factory The factory.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'alias', 'a.alias',
+ 'published', 'a.published',
+ 'access', 'a.access', 'access_level',
+ 'language', 'a.language', 'language_title',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'created_time', 'a.created_time',
+ 'created_user_id', 'a.created_user_id',
+ 'lft', 'a.lft',
+ 'rgt', 'a.rgt',
+ 'level', 'a.level',
+ 'path', 'a.path',
+ 'tag',
+ );
+ }
+
+ if (Associations::isEnabled()) {
+ $config['filter_fields'][] = 'association';
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', 'com_content', 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ $parts = explode('.', $extension);
+
+ // Extract the component name
+ $this->setState('filter.component', $parts[0]);
+
+ // Extract the optional section name
+ $this->setState('filter.section', (\count($parts) > 1) ? $parts[1] : null);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language.
+ if (!empty($forcedLanguage)) {
+ $this->setState('filter.language', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.extension');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.level');
+ $id .= ':' . serialize($this->getState('filter.tag'));
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to get a database query to list categories.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.title, a.alias, a.note, a.published, a.access' .
+ ', a.checked_out, a.checked_out_time, a.created_user_id' .
+ ', a.path, a.parent_id, a.level, a.lft, a.rgt' .
+ ', a.language'
+ )
+ );
+ $query->from($db->quoteName('#__categories', 'a'));
+
+ // Join over the language
+ $query->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ ]
+ )
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__languages', 'l'),
+ $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')
+ );
+
+ // Join over the users for the checked out user.
+ $query->select($db->quoteName('uc.name', 'editor'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'uc'),
+ $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')
+ );
+
+ // Join over the asset groups.
+ $query->select($db->quoteName('ag.title', 'access_level'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__viewlevels', 'ag'),
+ $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')
+ );
+
+ // Join over the users for the author.
+ $query->select($db->quoteName('ua.name', 'author_name'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'ua'),
+ $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id')
+ );
+
+ // Join over the associations.
+ $assoc = $this->getAssoc();
+
+ if ($assoc) {
+ $query->select('COUNT(asso2.id)>1 as association')
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'asso'),
+ $db->quoteName('asso.id') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('asso.context') . ' = ' . $db->quote('com_categories.item')
+ )
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__associations', 'asso2'),
+ $db->quoteName('asso2.key') . ' = ' . $db->quoteName('asso.key')
+ )
+ ->group('a.id, l.title, uc.name, ag.title, ua.name');
+ }
+
+ // Filter by extension
+ if ($extension = $this->getState('filter.extension')) {
+ $query->where($db->quoteName('a.extension') . ' = :extension')
+ ->bind(':extension', $extension);
+ }
+
+ // Filter on the level.
+ if ($level = (int) $this->getState('filter.level')) {
+ $query->where($db->quoteName('a.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter by access level.
+ if ($access = (int) $this->getState('filter.access')) {
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+
+ // Filter by published state
+ $published = (string) $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.published') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->whereIn($db->quoteName('a.published'), [0, 1]);
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.title') . ' LIKE :title',
+ $db->quoteName('a.alias') . ' LIKE :alias',
+ $db->quoteName('a.note') . ' LIKE :note',
+ ],
+ 'OR'
+ )
+ ->bind(':title', $search)
+ ->bind(':alias', $search)
+ ->bind(':note', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Filter by a single or group of tags.
+ $tag = $this->getState('filter.tag');
+ $typeAlias = $extension . '.category';
+
+ // Run simplified query when filtering by one tag.
+ if (\is_array($tag) && \count($tag) === 1) {
+ $tag = $tag[0];
+ }
+
+ if ($tag && \is_array($tag)) {
+ $tag = ArrayHelper::toInteger($tag);
+
+ $subQuery = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('content_item_id'))
+ ->from($db->quoteName('#__contentitem_tag_map'))
+ ->where(
+ [
+ $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
+ $db->quoteName('type_alias') . ' = :typeAlias',
+ ]
+ );
+
+ $query->join(
+ 'INNER',
+ '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ )
+ ->bind(':typeAlias', $typeAlias);
+ } elseif ($tag = (int) $tag) {
+ $query->join(
+ 'INNER',
+ $db->quoteName('#__contentitem_tag_map', 'tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ )
+ ->where(
+ [
+ $db->quoteName('tagmap.tag_id') . ' = :tag',
+ $db->quoteName('tagmap.type_alias') . ' = :typeAlias',
+ ]
+ )
+ ->bind(':tag', $tag, ParameterType::INTEGER)
+ ->bind(':typeAlias', $typeAlias);
+ }
+
+ // Add the list ordering clause
+ $listOrdering = $this->getState('list.ordering', 'a.lft');
+ $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
+
+ if ($listOrdering == 'a.access') {
+ $query->order('a.access ' . $listDirn . ', a.lft ' . $listDirn);
+ } else {
+ $query->order($db->escape($listOrdering) . ' ' . $listDirn);
+ }
+
+ // Group by on Categories for \JOIN with component tables to count items
+ $query->group('a.id,
a.title,
a.alias,
a.note,
@@ -395,125 +369,118 @@ protected function getListQuery()
l.image,
uc.name,
ag.title,
- ua.name'
- );
+ ua.name');
- return $query;
- }
+ return $query;
+ }
- /**
- * Method to determine if an association exists
- *
- * @return boolean True if the association exists
- *
- * @since 3.0
- */
- public function getAssoc()
- {
- if (!\is_null($this->hasAssociation))
- {
- return $this->hasAssociation;
- }
+ /**
+ * Method to determine if an association exists
+ *
+ * @return boolean True if the association exists
+ *
+ * @since 3.0
+ */
+ public function getAssoc()
+ {
+ if (!\is_null($this->hasAssociation)) {
+ return $this->hasAssociation;
+ }
- $extension = $this->getState('filter.extension');
+ $extension = $this->getState('filter.extension');
- $this->hasAssociation = Associations::isEnabled();
- $extension = explode('.', $extension);
- $component = array_shift($extension);
- $cname = str_replace('com_', '', $component);
+ $this->hasAssociation = Associations::isEnabled();
+ $extension = explode('.', $extension);
+ $component = array_shift($extension);
+ $cname = str_replace('com_', '', $component);
- if (!$this->hasAssociation || !$component || !$cname)
- {
- $this->hasAssociation = false;
+ if (!$this->hasAssociation || !$component || !$cname) {
+ $this->hasAssociation = false;
- return $this->hasAssociation;
- }
+ return $this->hasAssociation;
+ }
- $componentObject = $this->bootComponent($component);
+ $componentObject = $this->bootComponent($component);
- if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface)
- {
- $this->hasAssociation = true;
+ if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface) {
+ $this->hasAssociation = true;
- return $this->hasAssociation;
- }
+ return $this->hasAssociation;
+ }
- $hname = $cname . 'HelperAssociation';
- \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php');
+ $hname = $cname . 'HelperAssociation';
+ \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php');
/* @codingStandardsIgnoreStart */
$this->hasAssociation = class_exists($hname) && !empty($hname::$category_association);
/* @codingStandardsIgnoreEnd */
- return $this->hasAssociation;
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 3.0.1
- */
- public function getItems()
- {
- $items = parent::getItems();
-
- if ($items != false)
- {
- $extension = $this->getState('filter.extension');
-
- $this->countItems($items, $extension);
- }
-
- return $items;
- }
-
- /**
- * Method to load the countItems method from the extensions
- *
- * @param \stdClass[] $items The category items
- * @param string $extension The category extension
- *
- * @return void
- *
- * @since 3.5
- */
- public function countItems(&$items, $extension)
- {
- $parts = explode('.', $extension, 2);
- $section = '';
-
- if (\count($parts) > 1)
- {
- $section = $parts[1];
- }
-
- $component = Factory::getApplication()->bootComponent($parts[0]);
-
- if ($component instanceof CategoryServiceInterface)
- {
- $component->countItems($items, $section);
- }
- }
-
- /**
- * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
- *
- * @return DatabaseQuery
- *
- * @since 4.0.0
- */
- protected function getEmptyStateQuery()
- {
- $query = parent::getEmptyStateQuery();
-
- // Get the extension from the filter
- $extension = $this->getState('filter.extension');
-
- $query->where($this->getDatabase()->quoteName('extension') . ' = :extension')
- ->bind(':extension', $extension);
-
- return $query;
- }
+ return $this->hasAssociation;
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 3.0.1
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ if ($items != false) {
+ $extension = $this->getState('filter.extension');
+
+ $this->countItems($items, $extension);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Method to load the countItems method from the extensions
+ *
+ * @param \stdClass[] $items The category items
+ * @param string $extension The category extension
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public function countItems(&$items, $extension)
+ {
+ $parts = explode('.', $extension, 2);
+ $section = '';
+
+ if (\count($parts) > 1) {
+ $section = $parts[1];
+ }
+
+ $component = Factory::getApplication()->bootComponent($parts[0]);
+
+ if ($component instanceof CategoryServiceInterface) {
+ $component->countItems($items, $section);
+ }
+ }
+
+ /**
+ * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 4.0.0
+ */
+ protected function getEmptyStateQuery()
+ {
+ $query = parent::getEmptyStateQuery();
+
+ // Get the extension from the filter
+ $extension = $this->getState('filter.extension');
+
+ $query->where($this->getDatabase()->quoteName('extension') . ' = :extension')
+ ->bind(':extension', $extension);
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_categories/src/Model/CategoryModel.php b/administrator/components/com_categories/src/Model/CategoryModel.php
index 3cd6e49238f94..784440f71c07d 100644
--- a/administrator/components/com_categories/src/Model/CategoryModel.php
+++ b/administrator/components/com_categories/src/Model/CategoryModel.php
@@ -1,4 +1,5 @@
input->get('extension', 'com_content');
- $this->typeAlias = $extension . '.category';
-
- // Add a new batch command
- $this->batch_commands['flip_ordering'] = 'batchFlipordering';
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.delete', $record->extension . '.category.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
-
- // Check for existing category.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->id);
- }
-
- // New category, so check against the parent.
- if (!empty($record->parent_id))
- {
- return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->parent_id);
- }
-
- // Default to component settings if neither category nor parent known.
- return $user->authorise('core.edit.state', $record->extension);
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\Table\Table A Table object
- *
- * @since 1.6
- */
- public function getTable($type = 'Category', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
-
- /**
- * Auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- $parentId = $app->input->getInt('parent_id');
- $this->setState('category.parent_id', $parentId);
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState($this->getName() . '.id', $pk);
-
- $extension = $app->input->get('extension', 'com_content');
- $this->setState('category.extension', $extension);
- $parts = explode('.', $extension);
-
- // Extract the component name
- $this->setState('category.component', $parts[0]);
-
- // Extract the optional section name
- $this->setState('category.section', (\count($parts) > 1) ? $parts[1] : null);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_categories');
- $this->setState('params', $params);
- }
-
- /**
- * Method to get a category.
- *
- * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
- *
- * @return mixed Category data object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- if ($result = parent::getItem($pk))
- {
- // Prime required properties.
- if (empty($result->id))
- {
- $result->parent_id = $this->getState('category.parent_id');
- $result->extension = $this->getState('category.extension');
- }
-
- // Convert the metadata field to an array.
- $registry = new Registry($result->metadata);
- $result->metadata = $registry->toArray();
-
- if (!empty($result->id))
- {
- $result->tags = new TagsHelper;
- $result->tags->getTagIds($result->id, $result->extension . '.category');
- }
- }
-
- $assoc = $this->getAssoc();
-
- if ($assoc)
- {
- if ($result->id != null)
- {
- $result->associations = ArrayHelper::toInteger(CategoriesHelper::getAssociations($result->id, $result->extension));
- }
- else
- {
- $result->associations = array();
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get the row form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A JForm object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- $extension = $this->getState('category.extension');
- $jinput = Factory::getApplication()->input;
-
- // A workaround to get the extension into the model for save requests.
- if (empty($extension) && isset($data['extension']))
- {
- $extension = $data['extension'];
- $parts = explode('.', $extension);
-
- $this->setState('category.extension', $extension);
- $this->setState('category.component', $parts[0]);
- $this->setState('category.section', @$parts[1]);
- }
-
- // Get the form.
- $form = $this->loadForm('com_categories.category' . $extension, 'category', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on Edit State access controls.
- if (empty($data['extension']))
- {
- $data['extension'] = $extension;
- }
-
- $categoryId = $jinput->get('id');
- $parts = explode('.', $extension);
- $assetKey = $categoryId ? $extension . '.category.' . $categoryId : $parts[0];
-
- if (!Factory::getUser()->authorise('core.edit.state', $assetKey))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- // Don't allow to change the created_user_id user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_user_id', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * A protected method to get the where clause for the reorder
- * This ensures that the row will be moved relative to a row with the same extension
- *
- * @param Category $table Current table instance
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('extension') . ' = ' . $db->quote($table->extension),
- ];
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $app = Factory::getApplication();
- $data = $app->getUserState('com_categories.edit.' . $this->getName() . '.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Category Manager
- if (!$data->id)
- {
- // Check for which extension the Category Manager is used and get selected fields
- $extension = substr($app->getUserState('com_categories.categories.filter.extension', ''), 4);
- $filters = (array) $app->getUserState('com_categories.categories.' . $extension . '.filter');
-
- $data->set(
- 'published',
- $app->input->getInt(
- 'published',
- ((isset($filters['published']) && $filters['published'] !== '') ? $filters['published'] : null)
- )
- );
- $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
- $data->set(
- 'access',
- $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
- );
- }
- }
-
- $this->preprocessData('com_categories.category', $data);
-
- return $data;
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see JFormRule
- * @see JFilterInput
- * @since 3.9.23
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', $data['extension']))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import.
- *
- * @return mixed
- *
- * @since 1.6
- *
- * @throws \Exception if there is an error in the form event.
- *
- * @see \Joomla\CMS\Form\FormField
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $lang = Factory::getLanguage();
- $component = $this->getState('category.component');
- $section = $this->getState('category.section');
- $extension = Factory::getApplication()->input->get('extension', null);
-
- // Get the component form if it exists
- $name = 'category' . ($section ? ('.' . $section) : '');
-
- // Looking first in the component forms folder
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml");
-
- // Looking in the component models/forms folder (J! 3)
- if (!file_exists($path))
- {
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");
- }
-
- // Old way: looking in the component folder
- if (!file_exists($path))
- {
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/$name.xml");
- }
-
- if (file_exists($path))
- {
- $lang->load($component, JPATH_BASE);
- $lang->load($component, JPATH_BASE . '/components/' . $component);
-
- if (!$form->loadFile($path, false))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
-
- $componentInterface = Factory::getApplication()->bootComponent($component);
-
- if ($componentInterface instanceof CategoryServiceInterface)
- {
- $componentInterface->prepareForm($form, $data);
- }
- else
- {
- // Try to find the component helper.
- $eName = str_replace('com_', '', $component);
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/helpers/category.php");
-
- if (file_exists($path))
- {
- $cName = ucfirst($eName) . ucfirst($section) . 'HelperCategory';
-
- \JLoader::register($cName, $path);
-
- if (class_exists($cName) && \is_callable(array($cName, 'onPrepareForm')))
- {
- $lang->load($component, JPATH_BASE, null, false, false)
- || $lang->load($component, JPATH_BASE . '/components/' . $component, null, false, false)
- || $lang->load($component, JPATH_BASE, $lang->getDefault(), false, false)
- || $lang->load($component, JPATH_BASE . '/components/' . $component, $lang->getDefault(), false, false);
- \call_user_func_array(array($cName, 'onPrepareForm'), array(&$form));
-
- // Check for an error.
- if ($form instanceof \Exception)
- {
- $this->setError($form->getMessage());
-
- return false;
- }
- }
- }
- }
-
- // Set the access control rules field component value.
- $form->setFieldAttribute('rules', 'component', $component);
- $form->setFieldAttribute('rules', 'section', $name);
-
- // Association category items
- if ($this->getAssoc())
- {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (\count($languages) > 1)
- {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language)
- {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_category');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('extension', $extension);
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $table = $this->getTable();
- $input = Factory::getApplication()->input;
- $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id');
- $isNew = true;
- $context = $this->option . '.' . $this->name;
-
- if (!empty($data['tags']) && $data['tags'][0] != '')
- {
- $table->newTags = $data['tags'];
- }
-
- // Include the plugins for the save events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Load the row if saving an existing category.
- if ($pk > 0)
- {
- $table->load($pk);
- $isNew = false;
- }
-
- // Set the new parent id if parent id not matched OR while New/Save as Copy .
- if ($table->parent_id != $data['parent_id'] || $data['id'] == 0)
- {
- $table->setLocation($data['parent_id'], 'last-child');
- }
-
- // Alter the title for save as copy
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['title'] == $origTable->title)
- {
- [$title, $alias] = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']);
- $data['title'] = $title;
- $data['alias'] = $alias;
- }
- else
- {
- if ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
- }
-
- $data['published'] = 0;
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Bind the rules.
- if (isset($data['rules']))
- {
- $rules = new Rules($data['rules']);
- $table->setRules($rules);
- }
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data));
-
- if (\in_array(false, $result, true))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Store the data.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $assoc = $this->getAssoc();
-
- if ($assoc)
- {
- // Adding self to the association
- $associations = $data['associations'] ?? array();
-
- // Unset any invalid associations
- $associations = ArrayHelper::toInteger($associations);
-
- foreach ($associations as $tag => $id)
- {
- if (!$id)
- {
- unset($associations[$tag]);
- }
- }
-
- // Detecting all item menus
- $allLanguage = $table->language == '*';
-
- if ($allLanguage && !empty($associations))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice');
- }
-
- // Get associationskey for edited item
- $db = $this->getDatabase();
- $id = (int) $table->id;
- $query = $db->getQuery(true)
- ->select($db->quoteName('key'))
- ->from($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :associationscontext')
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':associationscontext', $this->associationsContext)
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $oldKey = $db->loadResult();
-
- if ($associations || $oldKey !== null)
- {
- $where = [];
-
- // Deleting old associations for the associated items
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :associationscontext')
- ->bind(':associationscontext', $this->associationsContext);
-
- if ($associations)
- {
- $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
- }
-
- if ($oldKey !== null)
- {
- $where[] = $db->quoteName('key') . ' = :oldKey';
- $query->bind(':oldKey', $oldKey);
- }
-
- $query->extendWhere('AND', $where, 'OR');
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Adding self to the association
- if (!$allLanguage)
- {
- $associations[$table->language] = (int) $table->id;
- }
-
- if (\count($associations) > 1)
- {
- // Adding new association for these items
- $key = md5(json_encode($associations));
- $query->clear()
- ->insert($db->quoteName('#__associations'))
- ->columns(
- [
- $db->quoteName('id'),
- $db->quoteName('context'),
- $db->quoteName('key'),
- ]
- );
-
- foreach ($associations as $id)
- {
- $id = (int) $id;
-
- $query->values(
- implode(
- ',',
- $query->bindArray(
- [$id, $this->associationsContext, $key],
- [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
- )
- )
- );
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew, $data));
-
- // Rebuild the path for the category:
- if (!$table->rebuildPath($table->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Rebuild the paths of the category's children:
- if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $this->setState($this->getName() . '.id', $table->id);
-
- if (Factory::getApplication()->input->get('task') == 'editAssociations')
- {
- return $this->redirectToAssociations($data);
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array $pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- */
- public function publish(&$pks, $value = 1)
- {
- if (parent::publish($pks, $value))
- {
- $extension = Factory::getApplication()->input->get('extension');
-
- // Include the content plugins for the change of category state event.
- PluginHelper::importPlugin('content');
-
- // Trigger the onCategoryChangeState event.
- Factory::getApplication()->triggerEvent('onCategoryChangeState', array($extension, $pks, $value));
-
- return true;
- }
- }
-
- /**
- * Method rebuild the entire nested set tree.
- *
- * @return boolean False on failure or error, true otherwise.
- *
- * @since 1.6
- */
- public function rebuild()
- {
- // Get an instance of the table object.
- $table = $this->getTable();
-
- if (!$table->rebuild())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to save the reordered nested set tree.
- * First we save the new order values in the lft values of the changed ids.
- * Then we invoke the table rebuild to implement the new ordering.
- *
- * @param array $idArray An array of primary key ids.
- * @param integer $lftArray The lft value
- *
- * @return boolean False on failure or error, True otherwise
- *
- * @since 1.6
- */
- public function saveorder($idArray = null, $lftArray = null)
- {
- // Get an instance of the table object.
- $table = $this->getTable();
-
- if (!$table->saveorder($idArray, $lftArray))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Batch flip category ordering.
- *
- * @param integer $value The new category.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return mixed An array of new IDs on success, boolean false on failure.
- *
- * @since 3.6.3
- */
- protected function batchFlipordering($value, $pks, $contexts)
- {
- $successful = array();
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- /**
- * For each category get the max ordering value
- * Re-order with max - ordering
- */
- foreach ($pks as $id)
- {
- $query->select('MAX(' . $db->quoteName('ordering') . ')')
- ->from($db->quoteName('#__content'))
- ->where($db->quoteName('catid') . ' = :catid')
- ->bind(':catid', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $max = (int) $db->loadResult();
- $max++;
-
- $query->clear();
-
- $query->update($db->quoteName('#__content'))
- ->set($db->quoteName('ordering') . ' = :max - ' . $db->quoteName('ordering'))
- ->where($db->quoteName('catid') . ' = :catid')
- ->bind(':max', $max, ParameterType::INTEGER)
- ->bind(':catid', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- if ($db->execute())
- {
- $successful[] = $id;
- }
- }
-
- return empty($successful) ? false : $successful;
- }
-
- /**
- * Batch copy categories to a new category.
- *
- * @param integer $value The new category.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return mixed An array of new IDs on success, boolean false on failure.
- *
- * @since 1.6
- */
- protected function batchCopy($value, $pks, $contexts)
- {
- $type = new UCMType;
- $this->type = $type->getTypeByAlias($this->typeAlias);
-
- // $value comes as {parent_id}.{extension}
- $parts = explode('.', $value);
- $parentId = (int) ArrayHelper::getValue($parts, 0, 1);
-
- $db = $this->getDatabase();
- $extension = Factory::getApplication()->input->get('extension', '', 'word');
- $newIds = array();
-
- // Check that the parent exists
- if ($parentId)
- {
- if (!$this->table->load($parentId))
- {
- if ($error = $this->table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Non-fatal error
- $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
- $parentId = 0;
- }
- }
-
- // Check that user has create permission for parent category
- if ($parentId == $this->table->getRootId())
- {
- $canCreate = $this->user->authorise('core.create', $extension);
- }
- else
- {
- $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId);
- }
-
- if (!$canCreate)
- {
- // Error since user cannot create in parent category
- $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
-
- return false;
- }
- }
-
- // If the parent is 0, set it to the ID of the root item in the tree
- if (empty($parentId))
- {
- if (!$parentId = $this->table->getRootId())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
- // Make sure we can create in root
- elseif (!$this->user->authorise('core.create', $extension))
- {
- $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
-
- return false;
- }
- }
-
- // We need to log the parent ID
- $parents = array();
-
- // Calculate the emergency stop count as a precaution against a runaway loop bug
- $query = $db->getQuery(true)
- ->select('COUNT(' . $db->quoteName('id') . ')')
- ->from($db->quoteName('#__categories'));
- $db->setQuery($query);
-
- try
- {
- $count = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Parent exists so let's proceed
- while (!empty($pks) && $count > 0)
- {
- // Pop the first id off the stack
- $pk = array_shift($pks);
-
- $this->table->reset();
-
- // Check that the row actually exists
- if (!$this->table->load($pk))
- {
- if ($error = $this->table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Not fatal error
- $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
- continue;
- }
- }
-
- // Copy is a bit tricky, because we also need to copy the children
- $lft = (int) $this->table->lft;
- $rgt = (int) $this->table->rgt;
- $query->clear()
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('lft') . ' > :lft')
- ->where($db->quoteName('rgt') . ' < :rgt')
- ->bind(':lft', $lft, ParameterType::INTEGER)
- ->bind(':rgt', $rgt, ParameterType::INTEGER);
- $db->setQuery($query);
- $childIds = $db->loadColumn();
-
- // Add child ID's to the array only if they aren't already there.
- foreach ($childIds as $childId)
- {
- if (!\in_array($childId, $pks))
- {
- $pks[] = $childId;
- }
- }
-
- // Make a copy of the old ID, Parent ID and Asset ID
- $oldId = $this->table->id;
- $oldParentId = $this->table->parent_id;
- $oldAssetId = $this->table->asset_id;
-
- // Reset the id because we are making a copy.
- $this->table->id = 0;
-
- // If we a copying children, the Old ID will turn up in the parents list
- // otherwise it's a new top level item
- $this->table->parent_id = $parents[$oldParentId] ?? $parentId;
-
- // Set the new location in the tree for the node.
- $this->table->setLocation($this->table->parent_id, 'last-child');
-
- // @TODO: Deal with ordering?
- // $this->table->ordering = 1;
- $this->table->level = null;
- $this->table->asset_id = null;
- $this->table->lft = null;
- $this->table->rgt = null;
-
- // Alter the title & alias
- [$title, $alias] = $this->generateNewTitle($this->table->parent_id, $this->table->alias, $this->table->title);
- $this->table->title = $title;
- $this->table->alias = $alias;
-
- // Unpublish because we are making a copy
- $this->table->published = 0;
-
- // Store the row.
- if (!$this->table->store())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- // Get the new item ID
- $newId = $this->table->get('id');
-
- // Add the new ID to the array
- $newIds[$pk] = $newId;
-
- // Copy rules
- $query->clear()
- ->update($db->quoteName('#__assets', 't'))
- ->join('INNER',
- $db->quoteName('#__assets', 's'),
- $db->quoteName('s.id') . ' = :oldid'
- )
- ->bind(':oldid', $oldAssetId, ParameterType::INTEGER)
- ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules'))
- ->where($db->quoteName('t.id') . ' = :assetid')
- ->bind(':assetid', $this->table->asset_id, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
-
- // Now we log the old 'parent' to the new 'parent'
- $parents[$oldId] = $this->table->id;
- $count--;
- }
-
- // Rebuild the hierarchy.
- if (!$this->table->rebuild())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- // Rebuild the tree path.
- if (!$this->table->rebuildPath($this->table->id))
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- return $newIds;
- }
-
- /**
- * Batch move categories to a new category.
- *
- * @param integer $value The new category ID.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- protected function batchMove($value, $pks, $contexts)
- {
- $parentId = (int) $value;
- $type = new UCMType;
- $this->type = $type->getTypeByAlias($this->typeAlias);
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $extension = Factory::getApplication()->input->get('extension', '', 'word');
-
- // Check that the parent exists.
- if ($parentId)
- {
- if (!$this->table->load($parentId))
- {
- if ($error = $this->table->getError())
- {
- // Fatal error.
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Non-fatal error.
- $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
- $parentId = 0;
- }
- }
-
- // Check that user has create permission for parent category.
- if ($parentId == $this->table->getRootId())
- {
- $canCreate = $this->user->authorise('core.create', $extension);
- }
- else
- {
- $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId);
- }
-
- if (!$canCreate)
- {
- // Error since user cannot create in parent category
- $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
-
- return false;
- }
-
- // Check that user has edit permission for every category being moved
- // Note that the entire batch operation fails if any category lacks edit permission
- foreach ($pks as $pk)
- {
- if (!$this->user->authorise('core.edit', $extension . '.category.' . $pk))
- {
- // Error since user cannot edit this category
- $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_EDIT'));
-
- return false;
- }
- }
- }
-
- // We are going to store all the children and just move the category
- $children = array();
-
- // Parent exists so let's proceed
- foreach ($pks as $pk)
- {
- // Check that the row actually exists
- if (!$this->table->load($pk))
- {
- if ($error = $this->table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Not fatal error
- $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
- continue;
- }
- }
-
- // Set the new location in the tree for the node.
- $this->table->setLocation($parentId, 'last-child');
-
- // Check if we are moving to a different parent
- if ($parentId != $this->table->parent_id)
- {
- $lft = (int) $this->table->lft;
- $rgt = (int) $this->table->rgt;
-
- // Add the child node ids to the children array.
- $query->clear()
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__categories'))
- ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt')
- ->bind(':lft', $lft, ParameterType::INTEGER)
- ->bind(':rgt', $rgt, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $children = array_merge($children, (array) $db->loadColumn());
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- // Store the row.
- if (!$this->table->store())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- // Rebuild the tree path.
- if (!$this->table->rebuildPath())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
- }
-
- // Process the child rows
- if (!empty($children))
- {
- // Remove any duplicates and sanitize ids.
- $children = array_unique($children);
- $children = ArrayHelper::toInteger($children);
- }
-
- return true;
- }
-
- /**
- * Custom clean the cache of com_content and content modules
- *
- * @param string $group Cache group name.
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- $extension = Factory::getApplication()->input->get('extension');
-
- switch ($extension)
- {
- case 'com_content':
- parent::cleanCache('com_content');
- parent::cleanCache('mod_articles_archive');
- parent::cleanCache('mod_articles_categories');
- parent::cleanCache('mod_articles_category');
- parent::cleanCache('mod_articles_latest');
- parent::cleanCache('mod_articles_news');
- parent::cleanCache('mod_articles_popular');
- break;
- default:
- parent::cleanCache($extension);
- break;
- }
- }
-
- /**
- * Method to change the title & alias.
- *
- * @param integer $parentId The id of the parent.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 1.7
- */
- protected function generateNewTitle($parentId, $alias, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('alias' => $alias, 'parent_id' => $parentId)))
- {
- $title = StringHelper::increment($title);
- $alias = StringHelper::increment($alias, 'dash');
- }
-
- return array($title, $alias);
- }
-
- /**
- * Method to determine if a category association is available.
- *
- * @return boolean True if a category association is available; false otherwise.
- */
- public function getAssoc()
- {
- if (!\is_null($this->hasAssociation))
- {
- return $this->hasAssociation;
- }
-
- $extension = $this->getState('category.extension', '');
-
- $this->hasAssociation = Associations::isEnabled();
- $extension = explode('.', $extension);
- $component = array_shift($extension);
- $cname = str_replace('com_', '', $component);
-
- if (!$this->hasAssociation || !$component || !$cname)
- {
- $this->hasAssociation = false;
-
- return $this->hasAssociation;
- }
-
- $componentObject = $this->bootComponent($component);
-
- if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface)
- {
- $this->hasAssociation = true;
-
- return $this->hasAssociation;
- }
-
- $hname = $cname . 'HelperAssociation';
- \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php');
-
- $this->hasAssociation = class_exists($hname) && !empty($hname::$category_association);
-
- return $this->hasAssociation;
- }
+ use VersionableModelTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_CATEGORIES';
+
+ /**
+ * The type alias for this content type. Used for content version history.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = null;
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_categories.item';
+
+ /**
+ * Does an association exist? Caches the result of getAssoc().
+ *
+ * @var boolean|null
+ * @since 3.10.4
+ */
+ private $hasAssociation;
+
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface|null $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ $extension = Factory::getApplication()->input->get('extension', 'com_content');
+ $this->typeAlias = $extension . '.category';
+
+ // Add a new batch command
+ $this->batch_commands['flip_ordering'] = 'batchFlipordering';
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.delete', $record->extension . '.category.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+
+ // Check for existing category.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->id);
+ }
+
+ // New category, so check against the parent.
+ if (!empty($record->parent_id)) {
+ return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->parent_id);
+ }
+
+ // Default to component settings if neither category nor parent known.
+ return $user->authorise('core.edit.state', $record->extension);
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\Table\Table A Table object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'Category', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ $parentId = $app->input->getInt('parent_id');
+ $this->setState('category.parent_id', $parentId);
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState($this->getName() . '.id', $pk);
+
+ $extension = $app->input->get('extension', 'com_content');
+ $this->setState('category.extension', $extension);
+ $parts = explode('.', $extension);
+
+ // Extract the component name
+ $this->setState('category.component', $parts[0]);
+
+ // Extract the optional section name
+ $this->setState('category.section', (\count($parts) > 1) ? $parts[1] : null);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_categories');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Method to get a category.
+ *
+ * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
+ *
+ * @return mixed Category data object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ if ($result = parent::getItem($pk)) {
+ // Prime required properties.
+ if (empty($result->id)) {
+ $result->parent_id = $this->getState('category.parent_id');
+ $result->extension = $this->getState('category.extension');
+ }
+
+ // Convert the metadata field to an array.
+ $registry = new Registry($result->metadata);
+ $result->metadata = $registry->toArray();
+
+ if (!empty($result->id)) {
+ $result->tags = new TagsHelper();
+ $result->tags->getTagIds($result->id, $result->extension . '.category');
+ }
+ }
+
+ $assoc = $this->getAssoc();
+
+ if ($assoc) {
+ if ($result->id != null) {
+ $result->associations = ArrayHelper::toInteger(CategoriesHelper::getAssociations($result->id, $result->extension));
+ } else {
+ $result->associations = array();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the row form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A JForm object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $extension = $this->getState('category.extension');
+ $jinput = Factory::getApplication()->input;
+
+ // A workaround to get the extension into the model for save requests.
+ if (empty($extension) && isset($data['extension'])) {
+ $extension = $data['extension'];
+ $parts = explode('.', $extension);
+
+ $this->setState('category.extension', $extension);
+ $this->setState('category.component', $parts[0]);
+ $this->setState('category.section', @$parts[1]);
+ }
+
+ // Get the form.
+ $form = $this->loadForm('com_categories.category' . $extension, 'category', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on Edit State access controls.
+ if (empty($data['extension'])) {
+ $data['extension'] = $extension;
+ }
+
+ $categoryId = $jinput->get('id');
+ $parts = explode('.', $extension);
+ $assetKey = $categoryId ? $extension . '.category.' . $categoryId : $parts[0];
+
+ if (!Factory::getUser()->authorise('core.edit.state', $assetKey)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_user_id user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_user_id', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * A protected method to get the where clause for the reorder
+ * This ensures that the row will be moved relative to a row with the same extension
+ *
+ * @param Category $table Current table instance
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('extension') . ' = ' . $db->quote($table->extension),
+ ];
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $app = Factory::getApplication();
+ $data = $app->getUserState('com_categories.edit.' . $this->getName() . '.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Category Manager
+ if (!$data->id) {
+ // Check for which extension the Category Manager is used and get selected fields
+ $extension = substr($app->getUserState('com_categories.categories.filter.extension', ''), 4);
+ $filters = (array) $app->getUserState('com_categories.categories.' . $extension . '.filter');
+
+ $data->set(
+ 'published',
+ $app->input->getInt(
+ 'published',
+ ((isset($filters['published']) && $filters['published'] !== '') ? $filters['published'] : null)
+ )
+ );
+ $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
+ $data->set(
+ 'access',
+ $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
+ );
+ }
+ }
+
+ $this->preprocessData('com_categories.category', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see JFormRule
+ * @see JFilterInput
+ * @since 3.9.23
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', $data['extension'])) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import.
+ *
+ * @return mixed
+ *
+ * @since 1.6
+ *
+ * @throws \Exception if there is an error in the form event.
+ *
+ * @see \Joomla\CMS\Form\FormField
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $lang = Factory::getLanguage();
+ $component = $this->getState('category.component');
+ $section = $this->getState('category.section');
+ $extension = Factory::getApplication()->input->get('extension', null);
+
+ // Get the component form if it exists
+ $name = 'category' . ($section ? ('.' . $section) : '');
+
+ // Looking first in the component forms folder
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml");
+
+ // Looking in the component models/forms folder (J! 3)
+ if (!file_exists($path)) {
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");
+ }
+
+ // Old way: looking in the component folder
+ if (!file_exists($path)) {
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/$name.xml");
+ }
+
+ if (file_exists($path)) {
+ $lang->load($component, JPATH_BASE);
+ $lang->load($component, JPATH_BASE . '/components/' . $component);
+
+ if (!$form->loadFile($path, false)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+
+ $componentInterface = Factory::getApplication()->bootComponent($component);
+
+ if ($componentInterface instanceof CategoryServiceInterface) {
+ $componentInterface->prepareForm($form, $data);
+ } else {
+ // Try to find the component helper.
+ $eName = str_replace('com_', '', $component);
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/helpers/category.php");
+
+ if (file_exists($path)) {
+ $cName = ucfirst($eName) . ucfirst($section) . 'HelperCategory';
+
+ \JLoader::register($cName, $path);
+
+ if (class_exists($cName) && \is_callable(array($cName, 'onPrepareForm'))) {
+ $lang->load($component, JPATH_BASE, null, false, false)
+ || $lang->load($component, JPATH_BASE . '/components/' . $component, null, false, false)
+ || $lang->load($component, JPATH_BASE, $lang->getDefault(), false, false)
+ || $lang->load($component, JPATH_BASE . '/components/' . $component, $lang->getDefault(), false, false);
+ \call_user_func_array(array($cName, 'onPrepareForm'), array(&$form));
+
+ // Check for an error.
+ if ($form instanceof \Exception) {
+ $this->setError($form->getMessage());
+
+ return false;
+ }
+ }
+ }
+ }
+
+ // Set the access control rules field component value.
+ $form->setFieldAttribute('rules', 'component', $component);
+ $form->setFieldAttribute('rules', 'section', $name);
+
+ // Association category items
+ if ($this->getAssoc()) {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (\count($languages) > 1) {
+ $addform = new \SimpleXMLElement('');
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('type', 'modal_category');
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('extension', $extension);
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+ }
+
+ $form->load($addform, false);
+ }
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+ $input = Factory::getApplication()->input;
+ $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id');
+ $isNew = true;
+ $context = $this->option . '.' . $this->name;
+
+ if (!empty($data['tags']) && $data['tags'][0] != '') {
+ $table->newTags = $data['tags'];
+ }
+
+ // Include the plugins for the save events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Load the row if saving an existing category.
+ if ($pk > 0) {
+ $table->load($pk);
+ $isNew = false;
+ }
+
+ // Set the new parent id if parent id not matched OR while New/Save as Copy .
+ if ($table->parent_id != $data['parent_id'] || $data['id'] == 0) {
+ $table->setLocation($data['parent_id'], 'last-child');
+ }
+
+ // Alter the title for save as copy
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['title'] == $origTable->title) {
+ [$title, $alias] = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']);
+ $data['title'] = $title;
+ $data['alias'] = $alias;
+ } else {
+ if ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+ }
+
+ $data['published'] = 0;
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Bind the rules.
+ if (isset($data['rules'])) {
+ $rules = new Rules($data['rules']);
+ $table->setRules($rules);
+ }
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data));
+
+ if (\in_array(false, $result, true)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Store the data.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $assoc = $this->getAssoc();
+
+ if ($assoc) {
+ // Adding self to the association
+ $associations = $data['associations'] ?? array();
+
+ // Unset any invalid associations
+ $associations = ArrayHelper::toInteger($associations);
+
+ foreach ($associations as $tag => $id) {
+ if (!$id) {
+ unset($associations[$tag]);
+ }
+ }
+
+ // Detecting all item menus
+ $allLanguage = $table->language == '*';
+
+ if ($allLanguage && !empty($associations)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice');
+ }
+
+ // Get associationskey for edited item
+ $db = $this->getDatabase();
+ $id = (int) $table->id;
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('key'))
+ ->from($db->quoteName('#__associations'))
+ ->where($db->quoteName('context') . ' = :associationscontext')
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':associationscontext', $this->associationsContext)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $oldKey = $db->loadResult();
+
+ if ($associations || $oldKey !== null) {
+ $where = [];
+
+ // Deleting old associations for the associated items
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__associations'))
+ ->where($db->quoteName('context') . ' = :associationscontext')
+ ->bind(':associationscontext', $this->associationsContext);
+
+ if ($associations) {
+ $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
+ }
+
+ if ($oldKey !== null) {
+ $where[] = $db->quoteName('key') . ' = :oldKey';
+ $query->bind(':oldKey', $oldKey);
+ }
+
+ $query->extendWhere('AND', $where, 'OR');
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Adding self to the association
+ if (!$allLanguage) {
+ $associations[$table->language] = (int) $table->id;
+ }
+
+ if (\count($associations) > 1) {
+ // Adding new association for these items
+ $key = md5(json_encode($associations));
+ $query->clear()
+ ->insert($db->quoteName('#__associations'))
+ ->columns(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('context'),
+ $db->quoteName('key'),
+ ]
+ );
+
+ foreach ($associations as $id) {
+ $id = (int) $id;
+
+ $query->values(
+ implode(
+ ',',
+ $query->bindArray(
+ [$id, $this->associationsContext, $key],
+ [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
+ )
+ )
+ );
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew, $data));
+
+ // Rebuild the path for the category:
+ if (!$table->rebuildPath($table->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Rebuild the paths of the category's children:
+ if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $this->setState($this->getName() . '.id', $table->id);
+
+ if (Factory::getApplication()->input->get('task') == 'editAssociations') {
+ return $this->redirectToAssociations($data);
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array $pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ if (parent::publish($pks, $value)) {
+ $extension = Factory::getApplication()->input->get('extension');
+
+ // Include the content plugins for the change of category state event.
+ PluginHelper::importPlugin('content');
+
+ // Trigger the onCategoryChangeState event.
+ Factory::getApplication()->triggerEvent('onCategoryChangeState', array($extension, $pks, $value));
+
+ return true;
+ }
+ }
+
+ /**
+ * Method rebuild the entire nested set tree.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since 1.6
+ */
+ public function rebuild()
+ {
+ // Get an instance of the table object.
+ $table = $this->getTable();
+
+ if (!$table->rebuild()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to save the reordered nested set tree.
+ * First we save the new order values in the lft values of the changed ids.
+ * Then we invoke the table rebuild to implement the new ordering.
+ *
+ * @param array $idArray An array of primary key ids.
+ * @param integer $lftArray The lft value
+ *
+ * @return boolean False on failure or error, True otherwise
+ *
+ * @since 1.6
+ */
+ public function saveorder($idArray = null, $lftArray = null)
+ {
+ // Get an instance of the table object.
+ $table = $this->getTable();
+
+ if (!$table->saveorder($idArray, $lftArray)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Batch flip category ordering.
+ *
+ * @param integer $value The new category.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return mixed An array of new IDs on success, boolean false on failure.
+ *
+ * @since 3.6.3
+ */
+ protected function batchFlipordering($value, $pks, $contexts)
+ {
+ $successful = array();
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ /**
+ * For each category get the max ordering value
+ * Re-order with max - ordering
+ */
+ foreach ($pks as $id) {
+ $query->select('MAX(' . $db->quoteName('ordering') . ')')
+ ->from($db->quoteName('#__content'))
+ ->where($db->quoteName('catid') . ' = :catid')
+ ->bind(':catid', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $max = (int) $db->loadResult();
+ $max++;
+
+ $query->clear();
+
+ $query->update($db->quoteName('#__content'))
+ ->set($db->quoteName('ordering') . ' = :max - ' . $db->quoteName('ordering'))
+ ->where($db->quoteName('catid') . ' = :catid')
+ ->bind(':max', $max, ParameterType::INTEGER)
+ ->bind(':catid', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ if ($db->execute()) {
+ $successful[] = $id;
+ }
+ }
+
+ return empty($successful) ? false : $successful;
+ }
+
+ /**
+ * Batch copy categories to a new category.
+ *
+ * @param integer $value The new category.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return mixed An array of new IDs on success, boolean false on failure.
+ *
+ * @since 1.6
+ */
+ protected function batchCopy($value, $pks, $contexts)
+ {
+ $type = new UCMType();
+ $this->type = $type->getTypeByAlias($this->typeAlias);
+
+ // $value comes as {parent_id}.{extension}
+ $parts = explode('.', $value);
+ $parentId = (int) ArrayHelper::getValue($parts, 0, 1);
+
+ $db = $this->getDatabase();
+ $extension = Factory::getApplication()->input->get('extension', '', 'word');
+ $newIds = array();
+
+ // Check that the parent exists
+ if ($parentId) {
+ if (!$this->table->load($parentId)) {
+ if ($error = $this->table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Non-fatal error
+ $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
+ $parentId = 0;
+ }
+ }
+
+ // Check that user has create permission for parent category
+ if ($parentId == $this->table->getRootId()) {
+ $canCreate = $this->user->authorise('core.create', $extension);
+ } else {
+ $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId);
+ }
+
+ if (!$canCreate) {
+ // Error since user cannot create in parent category
+ $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
+
+ return false;
+ }
+ }
+
+ // If the parent is 0, set it to the ID of the root item in the tree
+ if (empty($parentId)) {
+ if (!$parentId = $this->table->getRootId()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ } elseif (!$this->user->authorise('core.create', $extension)) {
+ // Make sure we can create in root
+ $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
+
+ return false;
+ }
+ }
+
+ // We need to log the parent ID
+ $parents = array();
+
+ // Calculate the emergency stop count as a precaution against a runaway loop bug
+ $query = $db->getQuery(true)
+ ->select('COUNT(' . $db->quoteName('id') . ')')
+ ->from($db->quoteName('#__categories'));
+ $db->setQuery($query);
+
+ try {
+ $count = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Parent exists so let's proceed
+ while (!empty($pks) && $count > 0) {
+ // Pop the first id off the stack
+ $pk = array_shift($pks);
+
+ $this->table->reset();
+
+ // Check that the row actually exists
+ if (!$this->table->load($pk)) {
+ if ($error = $this->table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Not fatal error
+ $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ continue;
+ }
+ }
+
+ // Copy is a bit tricky, because we also need to copy the children
+ $lft = (int) $this->table->lft;
+ $rgt = (int) $this->table->rgt;
+ $query->clear()
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('lft') . ' > :lft')
+ ->where($db->quoteName('rgt') . ' < :rgt')
+ ->bind(':lft', $lft, ParameterType::INTEGER)
+ ->bind(':rgt', $rgt, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $childIds = $db->loadColumn();
+
+ // Add child ID's to the array only if they aren't already there.
+ foreach ($childIds as $childId) {
+ if (!\in_array($childId, $pks)) {
+ $pks[] = $childId;
+ }
+ }
+
+ // Make a copy of the old ID, Parent ID and Asset ID
+ $oldId = $this->table->id;
+ $oldParentId = $this->table->parent_id;
+ $oldAssetId = $this->table->asset_id;
+
+ // Reset the id because we are making a copy.
+ $this->table->id = 0;
+
+ // If we a copying children, the Old ID will turn up in the parents list
+ // otherwise it's a new top level item
+ $this->table->parent_id = $parents[$oldParentId] ?? $parentId;
+
+ // Set the new location in the tree for the node.
+ $this->table->setLocation($this->table->parent_id, 'last-child');
+
+ // @TODO: Deal with ordering?
+ // $this->table->ordering = 1;
+ $this->table->level = null;
+ $this->table->asset_id = null;
+ $this->table->lft = null;
+ $this->table->rgt = null;
+
+ // Alter the title & alias
+ [$title, $alias] = $this->generateNewTitle($this->table->parent_id, $this->table->alias, $this->table->title);
+ $this->table->title = $title;
+ $this->table->alias = $alias;
+
+ // Unpublish because we are making a copy
+ $this->table->published = 0;
+
+ // Store the row.
+ if (!$this->table->store()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ // Get the new item ID
+ $newId = $this->table->get('id');
+
+ // Add the new ID to the array
+ $newIds[$pk] = $newId;
+
+ // Copy rules
+ $query->clear()
+ ->update($db->quoteName('#__assets', 't'))
+ ->join(
+ 'INNER',
+ $db->quoteName('#__assets', 's'),
+ $db->quoteName('s.id') . ' = :oldid'
+ )
+ ->bind(':oldid', $oldAssetId, ParameterType::INTEGER)
+ ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules'))
+ ->where($db->quoteName('t.id') . ' = :assetid')
+ ->bind(':assetid', $this->table->asset_id, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+
+ // Now we log the old 'parent' to the new 'parent'
+ $parents[$oldId] = $this->table->id;
+ $count--;
+ }
+
+ // Rebuild the hierarchy.
+ if (!$this->table->rebuild()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ // Rebuild the tree path.
+ if (!$this->table->rebuildPath($this->table->id)) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ return $newIds;
+ }
+
+ /**
+ * Batch move categories to a new category.
+ *
+ * @param integer $value The new category ID.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ protected function batchMove($value, $pks, $contexts)
+ {
+ $parentId = (int) $value;
+ $type = new UCMType();
+ $this->type = $type->getTypeByAlias($this->typeAlias);
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $extension = Factory::getApplication()->input->get('extension', '', 'word');
+
+ // Check that the parent exists.
+ if ($parentId) {
+ if (!$this->table->load($parentId)) {
+ if ($error = $this->table->getError()) {
+ // Fatal error.
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Non-fatal error.
+ $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
+ $parentId = 0;
+ }
+ }
+
+ // Check that user has create permission for parent category.
+ if ($parentId == $this->table->getRootId()) {
+ $canCreate = $this->user->authorise('core.create', $extension);
+ } else {
+ $canCreate = $this->user->authorise('core.create', $extension . '.category.' . $parentId);
+ }
+
+ if (!$canCreate) {
+ // Error since user cannot create in parent category
+ $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
+
+ return false;
+ }
+
+ // Check that user has edit permission for every category being moved
+ // Note that the entire batch operation fails if any category lacks edit permission
+ foreach ($pks as $pk) {
+ if (!$this->user->authorise('core.edit', $extension . '.category.' . $pk)) {
+ // Error since user cannot edit this category
+ $this->setError(Text::_('COM_CATEGORIES_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+ }
+ }
+
+ // We are going to store all the children and just move the category
+ $children = array();
+
+ // Parent exists so let's proceed
+ foreach ($pks as $pk) {
+ // Check that the row actually exists
+ if (!$this->table->load($pk)) {
+ if ($error = $this->table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Not fatal error
+ $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ continue;
+ }
+ }
+
+ // Set the new location in the tree for the node.
+ $this->table->setLocation($parentId, 'last-child');
+
+ // Check if we are moving to a different parent
+ if ($parentId != $this->table->parent_id) {
+ $lft = (int) $this->table->lft;
+ $rgt = (int) $this->table->rgt;
+
+ // Add the child node ids to the children array.
+ $query->clear()
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__categories'))
+ ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt')
+ ->bind(':lft', $lft, ParameterType::INTEGER)
+ ->bind(':rgt', $rgt, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $children = array_merge($children, (array) $db->loadColumn());
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ // Store the row.
+ if (!$this->table->store()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ // Rebuild the tree path.
+ if (!$this->table->rebuildPath()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+ }
+
+ // Process the child rows
+ if (!empty($children)) {
+ // Remove any duplicates and sanitize ids.
+ $children = array_unique($children);
+ $children = ArrayHelper::toInteger($children);
+ }
+
+ return true;
+ }
+
+ /**
+ * Custom clean the cache of com_content and content modules
+ *
+ * @param string $group Cache group name.
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ $extension = Factory::getApplication()->input->get('extension');
+
+ switch ($extension) {
+ case 'com_content':
+ parent::cleanCache('com_content');
+ parent::cleanCache('mod_articles_archive');
+ parent::cleanCache('mod_articles_categories');
+ parent::cleanCache('mod_articles_category');
+ parent::cleanCache('mod_articles_latest');
+ parent::cleanCache('mod_articles_news');
+ parent::cleanCache('mod_articles_popular');
+ break;
+ default:
+ parent::cleanCache($extension);
+ break;
+ }
+ }
+
+ /**
+ * Method to change the title & alias.
+ *
+ * @param integer $parentId The id of the parent.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 1.7
+ */
+ protected function generateNewTitle($parentId, $alias, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('alias' => $alias, 'parent_id' => $parentId))) {
+ $title = StringHelper::increment($title);
+ $alias = StringHelper::increment($alias, 'dash');
+ }
+
+ return array($title, $alias);
+ }
+
+ /**
+ * Method to determine if a category association is available.
+ *
+ * @return boolean True if a category association is available; false otherwise.
+ */
+ public function getAssoc()
+ {
+ if (!\is_null($this->hasAssociation)) {
+ return $this->hasAssociation;
+ }
+
+ $extension = $this->getState('category.extension', '');
+
+ $this->hasAssociation = Associations::isEnabled();
+ $extension = explode('.', $extension);
+ $component = array_shift($extension);
+ $cname = str_replace('com_', '', $component);
+
+ if (!$this->hasAssociation || !$component || !$cname) {
+ $this->hasAssociation = false;
+
+ return $this->hasAssociation;
+ }
+
+ $componentObject = $this->bootComponent($component);
+
+ if ($componentObject instanceof AssociationServiceInterface && $componentObject instanceof CategoryServiceInterface) {
+ $this->hasAssociation = true;
+
+ return $this->hasAssociation;
+ }
+
+ $hname = $cname . 'HelperAssociation';
+ \JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php');
+
+ $this->hasAssociation = class_exists($hname) && !empty($hname::$category_association);
+
+ return $this->hasAssociation;
+ }
}
diff --git a/administrator/components/com_categories/src/Service/HTML/AdministratorService.php b/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
index 0a48842e8485e..c5c58226fc199 100644
--- a/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_categories/src/Service/HTML/AdministratorService.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select(
- [
- $db->quoteName('c.id'),
- $db->quoteName('c.title'),
- $db->quoteName('l.sef', 'lang_sef'),
- $db->quoteName('l.lang_code'),
- $db->quoteName('l.image'),
- $db->quoteName('l.title', 'language_title'),
- ]
- )
- ->from($db->quoteName('#__categories', 'c'))
- ->whereIn($db->quoteName('c.id'), array_values($associations))
- ->where($db->quoteName('c.id') . ' != :catid')
- ->bind(':catid', $catid, ParameterType::INTEGER)
- ->join(
- 'LEFT',
- $db->quoteName('#__languages', 'l'),
- $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')
- );
- $db->setQuery($query);
+ // Get the associated categories
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('c.id'),
+ $db->quoteName('c.title'),
+ $db->quoteName('l.sef', 'lang_sef'),
+ $db->quoteName('l.lang_code'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.title', 'language_title'),
+ ]
+ )
+ ->from($db->quoteName('#__categories', 'c'))
+ ->whereIn($db->quoteName('c.id'), array_values($associations))
+ ->where($db->quoteName('c.id') . ' != :catid')
+ ->bind(':catid', $catid, ParameterType::INTEGER)
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__languages', 'l'),
+ $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code')
+ );
+ $db->setQuery($query);
- try
- {
- $items = $db->loadObjectList('id');
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500, $e);
- }
+ try {
+ $items = $db->loadObjectList('id');
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500, $e);
+ }
- if ($items)
- {
- $languages = LanguageHelper::getContentLanguages(array(0, 1));
- $content_languages = array_column($languages, 'lang_code');
+ if ($items) {
+ $languages = LanguageHelper::getContentLanguages(array(0, 1));
+ $content_languages = array_column($languages, 'lang_code');
- foreach ($items as &$item)
- {
- if (in_array($item->lang_code, $content_languages))
- {
- $text = $item->lang_code;
- $url = Route::_('index.php?option=com_categories&task=category.edit&id=' . (int) $item->id . '&extension=' . $extension);
- $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8');
- $classes = 'badge bg-secondary';
+ foreach ($items as &$item) {
+ if (in_array($item->lang_code, $content_languages)) {
+ $text = $item->lang_code;
+ $url = Route::_('index.php?option=com_categories&task=category.edit&id=' . (int) $item->id . '&extension=' . $extension);
+ $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8');
+ $classes = 'badge bg-secondary';
- $item->link = '' . $text . ' '
- . '' . $tooltip . '
';
- }
- else
- {
- // Display warning if Content Language is trashed or deleted
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
- }
- }
- }
+ $item->link = '' . $text . ' '
+ . '' . $tooltip . '
';
+ } else {
+ // Display warning if Content Language is trashed or deleted
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
+ }
+ }
+ }
- $html = LayoutHelper::render('joomla.content.associations', $items);
- }
+ $html = LayoutHelper::render('joomla.content.associations', $items);
+ }
- return $html;
- }
+ return $html;
+ }
}
diff --git a/administrator/components/com_categories/src/Table/CategoryTable.php b/administrator/components/com_categories/src/Table/CategoryTable.php
index dc63e189a81da..6c48bef1ed572 100644
--- a/administrator/components/com_categories/src/Table/CategoryTable.php
+++ b/administrator/components/com_categories/src/Table/CategoryTable.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->assoc = $this->get('Assoc');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Written this way because we only want to call IsEmptyState if no items, to prevent always calling it when not needed.
- if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Preprocess the list of items to find ordering divisions.
- foreach ($this->items as &$item)
- {
- $this->ordering[$item->parent_id][] = $item->id;
- }
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- else
- {
- // In article associations modal we need to remove language filter if forcing a language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
- $languageXml = new \SimpleXMLElement(' ');
- $this->filterForm->setField($languageXml, 'filter', true);
-
- // Also, unset the active language filter so the search tools is not open by default with this filter.
- unset($this->activeFilters['language']);
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @throws \Exception
- * @since 1.6
- */
- protected function addToolbar()
- {
- $categoryId = $this->state->get('filter.category_id');
- $component = $this->state->get('filter.component');
- $section = $this->state->get('filter.section');
- $canDo = ContentHelper::getActions($component, 'category', $categoryId);
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- // Avoid nonsense situation.
- if ($component == 'com_categories')
- {
- return;
- }
-
- // Need to load the menu language file as mod_menu hasn't been loaded yet.
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE)
- || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
-
- // If a component categories title string is present, let's use it.
- if ($lang->hasKey($component_title_key = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_TITLE'))
- {
- $title = Text::_($component_title_key);
- }
- elseif ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : ''))))
- // Else if the component section string exists, let's use it.
- {
- $title = Text::sprintf('COM_CATEGORIES_CATEGORIES_TITLE', $this->escape(Text::_($component_section_key)));
- }
- else
- // Else use the base title
- {
- $title = Text::_('COM_CATEGORIES_CATEGORIES_BASE_TITLE');
- }
-
- // Load specific css component
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = $this->document->getWebAssetManager();
- $wa->getRegistry()->addExtensionRegistryFile($component);
-
- if ($wa->assetExists('style', $component . '.admin-categories'))
- {
- $wa->useStyle($component . '.admin-categories');
- }
- else
- {
- $wa->registerAndUseStyle($component . '.admin-categories', $component . '/administrator/categories.css');
- }
-
- // Prepare the toolbar.
- ToolbarHelper::title($title, 'folder categories ' . substr($component, 4) . ($section ? "-$section" : '') . '-categories');
-
- if ($canDo->get('core.create') || count($user->getAuthorisedCategories($component, 'core.create')) > 0)
- {
- $toolbar->addNew('category.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('categories.publish')->listCheck(true);
-
- $childBar->unpublish('categories.unpublish')->listCheck(true);
-
- $childBar->archive('categories.archive')->listCheck(true);
- }
-
- if ($user->authorise('core.admin'))
- {
- $childBar->checkin('categories.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2)
- {
- $childBar->trash('categories.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($canDo->get('core.create')
- && $canDo->get('core.edit')
- && $canDo->get('core.edit.state'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $canDo->get('core.admin'))
- {
- $toolbar->standardButton('refresh')
- ->text('JTOOLBAR_REBUILD')
- ->task('categories.rebuild');
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete', $component))
- {
- $toolbar->delete('categories.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences($component);
- }
-
- // Get the component form if it exists for the help key/url
- $name = 'category' . ($section ? ('.' . $section) : '');
-
- // Looking first in the component forms folder
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml");
-
- // Looking in the component models/forms folder (J! 3)
- if (!file_exists($path))
- {
- $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");
- }
-
- $ref_key = '';
- $url = '';
-
- // Look first in form for help key and url
- if (file_exists($path))
- {
- if (!$xml = simplexml_load_file($path))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- $ref_key = (string) $xml->listhelp['key'];
- $url = (string) $xml->listhelp['url'];
- }
-
- if (!$ref_key)
- {
- // Compute the ref_key if it does exist in the component
- $languageKey = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_HELP_KEY';
-
- if ($lang->hasKey($languageKey))
- {
- $ref_key = $languageKey;
- }
- else
- {
- $languageKey = 'JHELP_COMPONENTS_' . strtoupper(substr($component, 4) . ($section ? "_$section" : '')) . '_CATEGORIES';
-
- if ($lang->hasKey($languageKey))
- {
- $ref_key = $languageKey;
- }
- }
- }
-
- /*
- * Get help for the categories view for the component by
- * -remotely searching in a URL defined in the category form
- * -remotely searching in a language defined dedicated URL: *component*_HELP_URL
- * -locally searching in a component help file if helpURL param exists in the component and is set to ''
- * -remotely searching in a component URL if helpURL param exists in the component and is NOT set to ''
- */
- if (!$url)
- {
- if ($lang->hasKey($lang_help_url = strtoupper($component) . '_HELP_URL'))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($lang_help_url);
- $lang->setDebug($debug);
- }
- }
-
- ToolbarHelper::help($ref_key, ComponentHelper::getParams($component)->exists('helpURL'), $url);
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var object
+ */
+ protected $state;
+
+ /**
+ * Flag if an association exists
+ *
+ * @var boolean
+ */
+ protected $assoc;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string|null $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @throws GenericDataException
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->assoc = $this->get('Assoc');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Written this way because we only want to call IsEmptyState if no items, to prevent always calling it when not needed.
+ if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Preprocess the list of items to find ordering divisions.
+ foreach ($this->items as &$item) {
+ $this->ordering[$item->parent_id][] = $item->id;
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // In article associations modal we need to remove language filter if forcing a language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement(' ');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $categoryId = $this->state->get('filter.category_id');
+ $component = $this->state->get('filter.component');
+ $section = $this->state->get('filter.section');
+ $canDo = ContentHelper::getActions($component, 'category', $categoryId);
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ // Avoid nonsense situation.
+ if ($component == 'com_categories') {
+ return;
+ }
+
+ // Need to load the menu language file as mod_menu hasn't been loaded yet.
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE)
+ || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
+
+ // If a component categories title string is present, let's use it.
+ if ($lang->hasKey($component_title_key = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_TITLE')) {
+ $title = Text::_($component_title_key);
+ } elseif ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : '')))) {
+ // Else if the component section string exists, let's use it.
+ $title = Text::sprintf('COM_CATEGORIES_CATEGORIES_TITLE', $this->escape(Text::_($component_section_key)));
+ } else // Else use the base title
+ {
+ $title = Text::_('COM_CATEGORIES_CATEGORIES_BASE_TITLE');
+ }
+
+ // Load specific css component
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = $this->document->getWebAssetManager();
+ $wa->getRegistry()->addExtensionRegistryFile($component);
+
+ if ($wa->assetExists('style', $component . '.admin-categories')) {
+ $wa->useStyle($component . '.admin-categories');
+ } else {
+ $wa->registerAndUseStyle($component . '.admin-categories', $component . '/administrator/categories.css');
+ }
+
+ // Prepare the toolbar.
+ ToolbarHelper::title($title, 'folder categories ' . substr($component, 4) . ($section ? "-$section" : '') . '-categories');
+
+ if ($canDo->get('core.create') || count($user->getAuthorisedCategories($component, 'core.create')) > 0) {
+ $toolbar->addNew('category.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('categories.publish')->listCheck(true);
+
+ $childBar->unpublish('categories.unpublish')->listCheck(true);
+
+ $childBar->archive('categories.archive')->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin')) {
+ $childBar->checkin('categories.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2) {
+ $childBar->trash('categories.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if (
+ $canDo->get('core.create')
+ && $canDo->get('core.edit')
+ && $canDo->get('core.edit.state')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $canDo->get('core.admin')) {
+ $toolbar->standardButton('refresh')
+ ->text('JTOOLBAR_REBUILD')
+ ->task('categories.rebuild');
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete', $component)) {
+ $toolbar->delete('categories.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences($component);
+ }
+
+ // Get the component form if it exists for the help key/url
+ $name = 'category' . ($section ? ('.' . $section) : '');
+
+ // Looking first in the component forms folder
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/forms/$name.xml");
+
+ // Looking in the component models/forms folder (J! 3)
+ if (!file_exists($path)) {
+ $path = Path::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");
+ }
+
+ $ref_key = '';
+ $url = '';
+
+ // Look first in form for help key and url
+ if (file_exists($path)) {
+ if (!$xml = simplexml_load_file($path)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ $ref_key = (string) $xml->listhelp['key'];
+ $url = (string) $xml->listhelp['url'];
+ }
+
+ if (!$ref_key) {
+ // Compute the ref_key if it does exist in the component
+ $languageKey = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_HELP_KEY';
+
+ if ($lang->hasKey($languageKey)) {
+ $ref_key = $languageKey;
+ } else {
+ $languageKey = 'JHELP_COMPONENTS_' . strtoupper(substr($component, 4) . ($section ? "_$section" : '')) . '_CATEGORIES';
+
+ if ($lang->hasKey($languageKey)) {
+ $ref_key = $languageKey;
+ }
+ }
+ }
+
+ /*
+ * Get help for the categories view for the component by
+ * -remotely searching in a URL defined in the category form
+ * -remotely searching in a language defined dedicated URL: *component*_HELP_URL
+ * -locally searching in a component help file if helpURL param exists in the component and is set to ''
+ * -remotely searching in a component URL if helpURL param exists in the component and is NOT set to ''
+ */
+ if (!$url) {
+ if ($lang->hasKey($lang_help_url = strtoupper($component) . '_HELP_URL')) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($lang_help_url);
+ $lang->setDebug($debug);
+ }
+ }
+
+ ToolbarHelper::help($ref_key, ComponentHelper::getParams($component)->exists('helpURL'), $url);
+ }
}
diff --git a/administrator/components/com_categories/src/View/Category/HtmlView.php b/administrator/components/com_categories/src/View/Category/HtmlView.php
index d0a31e2b49143..a9cee9e400b4a 100644
--- a/administrator/components/com_categories/src/View/Category/HtmlView.php
+++ b/administrator/components/com_categories/src/View/Category/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
- $section = $this->state->get('category.section') ? $this->state->get('category.section') . '.' : '';
- $this->canDo = ContentHelper::getActions($this->state->get('category.component'), $section . 'category', $this->item->id);
- $this->assoc = $this->get('Assoc');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Check if we have a content type for this alias
- if (!empty(TagsHelper::getTypes('objectList', array($this->state->get('category.extension') . '.category'), true)))
- {
- $this->checkTags = true;
- }
-
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- // If we are forcing a language in modal (used for associations).
- if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
- {
- // Set the language field to the forcedLanguage and disable changing it.
- $this->form->setValue('language', null, $forcedLanguage);
- $this->form->setFieldAttribute('language', 'readonly', 'true');
-
- // Only allow to select categories with All language or with the forced language.
- $this->form->setFieldAttribute('parent_id', 'language', '*,' . $forcedLanguage);
-
- // Only allow to select tags with All language or with the forced language.
- $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $extension = Factory::getApplication()->input->get('extension');
- $user = $this->getCurrentUser();
- $userId = $user->id;
-
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Avoid nonsense situation.
- if ($extension == 'com_categories')
- {
- return;
- }
-
- // The extension can be in the form com_foo.section
- $parts = explode('.', $extension);
- $component = $parts[0];
- $section = (count($parts) > 1) ? $parts[1] : null;
- $componentParams = ComponentHelper::getParams($component);
-
- // Need to load the menu language file as mod_menu hasn't been loaded yet.
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE)
- || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
-
- // Get the results for each action.
- $canDo = $this->canDo;
-
- // If a component categories title string is present, let's use it.
- if ($lang->hasKey($component_title_key = $component . ($section ? "_$section" : '') . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE'))
- {
- $title = Text::_($component_title_key);
- }
- // Else if the component section string exists, let's use it.
- elseif ($lang->hasKey($component_section_key = $component . ($section ? "_$section" : '')))
- {
- $title = Text::sprintf('COM_CATEGORIES_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT')
- . '_TITLE', $this->escape(Text::_($component_section_key))
- );
- }
- // Else use the base title
- else
- {
- $title = Text::_('COM_CATEGORIES_CATEGORY_BASE_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE');
- }
-
- // Load specific css component
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = $this->document->getWebAssetManager();
- $wa->getRegistry()->addExtensionRegistryFile($component);
-
- if ($wa->assetExists('style', $component . '.admin-categories'))
- {
- $wa->useStyle($component . '.admin-categories');
- }
- else
- {
- $wa->registerAndUseStyle($component . '.admin-categories', $component . '/administrator/categories.css');
- }
-
- // Prepare the toolbar.
- ToolbarHelper::title(
- $title,
- 'folder category-' . ($isNew ? 'add' : 'edit')
- . ' ' . substr($component, 4) . ($section ? "-$section" : '') . '-category-' . ($isNew ? 'add' : 'edit')
- );
-
- // For new records, check the create permission.
- if ($isNew && (count($user->getAuthorisedCategories($component, 'core.create')) > 0))
- {
- ToolbarHelper::apply('category.apply');
- ToolbarHelper::saveGroup(
- [
- ['save', 'category.save'],
- ['save2new', 'category.save2new']
- ],
- 'btn-success'
- );
-
- ToolbarHelper::cancel('category.cancel');
- }
-
- // If not checked out, can save the item.
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_user_id == $userId);
-
- $toolbarButtons = [];
-
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- ToolbarHelper::apply('category.apply');
-
- $toolbarButtons[] = ['save', 'category.save'];
-
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'category.save2new'];
- }
- }
-
- // If an existing item, can save to a copy.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'category.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel('category.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $componentParams->get('save_history', 0) && $itemEditable)
- {
- $typeAlias = $extension . '.category';
- ToolbarHelper::versions($typeAlias, $this->item->id);
- }
-
- if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations'))
- {
- ToolbarHelper::custom('category.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
- }
- }
-
- ToolbarHelper::divider();
-
- // Look first in form for help key
- $ref_key = (string) $this->form->getXml()->help['key'];
-
- // Try with a language string
- if (!$ref_key)
- {
- // Compute the ref_key if it does exist in the component
- $languageKey = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT') . '_HELP_KEY';
-
- if ($lang->hasKey($languageKey))
- {
- $ref_key = $languageKey;
- }
- else
- {
- $languageKey = 'JHELP_COMPONENTS_'
- . strtoupper(substr($component, 4) . ($section ? "_$section" : ''))
- . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT');
-
- if ($lang->hasKey($languageKey))
- {
- $ref_key = $languageKey;
- }
- }
- }
-
- /*
- * Get help for the category/section view for the component by
- * -remotely searching in a URL defined in the category form
- * -remotely searching in a language defined dedicated URL: *component*_HELP_URL
- * -locally searching in a component help file if helpURL param exists in the component and is set to ''
- * -remotely searching in a component URL if helpURL param exists in the component and is NOT set to ''
- */
- $url = (string) $this->form->getXml()->help['url'];
-
- if (!$url)
- {
- if ($lang->hasKey($lang_help_url = strtoupper($component) . '_HELP_URL'))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($lang_help_url);
- $lang->setDebug($debug);
- }
- }
-
- ToolbarHelper::help($ref_key, $componentParams->exists('helpURL'), $url, $component);
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * Flag if an association exists
+ *
+ * @var boolean
+ */
+ protected $assoc;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ */
+ protected $canDo;
+
+ /**
+ * Is there a content type associated with this category alias
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $checkTags = false;
+
+ /**
+ * Display the view.
+ *
+ * @param string|null $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $section = $this->state->get('category.section') ? $this->state->get('category.section') . '.' : '';
+ $this->canDo = ContentHelper::getActions($this->state->get('category.component'), $section . 'category', $this->item->id);
+ $this->assoc = $this->get('Assoc');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Check if we have a content type for this alias
+ if (!empty(TagsHelper::getTypes('objectList', array($this->state->get('category.extension') . '.category'), true))) {
+ $this->checkTags = true;
+ }
+
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('parent_id', 'language', '*,' . $forcedLanguage);
+
+ // Only allow to select tags with All language or with the forced language.
+ $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $extension = Factory::getApplication()->input->get('extension');
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Avoid nonsense situation.
+ if ($extension == 'com_categories') {
+ return;
+ }
+
+ // The extension can be in the form com_foo.section
+ $parts = explode('.', $extension);
+ $component = $parts[0];
+ $section = (count($parts) > 1) ? $parts[1] : null;
+ $componentParams = ComponentHelper::getParams($component);
+
+ // Need to load the menu language file as mod_menu hasn't been loaded yet.
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE)
+ || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
+
+ // Get the results for each action.
+ $canDo = $this->canDo;
+
+ // If a component categories title string is present, let's use it.
+ if ($lang->hasKey($component_title_key = $component . ($section ? "_$section" : '') . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE')) {
+ $title = Text::_($component_title_key);
+ } elseif ($lang->hasKey($component_section_key = $component . ($section ? "_$section" : ''))) {
+ // Else if the component section string exists, let's use it.
+ $title = Text::sprintf('COM_CATEGORIES_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT')
+ . '_TITLE', $this->escape(Text::_($component_section_key)));
+ } else {
+ // Else use the base title
+ $title = Text::_('COM_CATEGORIES_CATEGORY_BASE_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE');
+ }
+
+ // Load specific css component
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = $this->document->getWebAssetManager();
+ $wa->getRegistry()->addExtensionRegistryFile($component);
+
+ if ($wa->assetExists('style', $component . '.admin-categories')) {
+ $wa->useStyle($component . '.admin-categories');
+ } else {
+ $wa->registerAndUseStyle($component . '.admin-categories', $component . '/administrator/categories.css');
+ }
+
+ // Prepare the toolbar.
+ ToolbarHelper::title(
+ $title,
+ 'folder category-' . ($isNew ? 'add' : 'edit')
+ . ' ' . substr($component, 4) . ($section ? "-$section" : '') . '-category-' . ($isNew ? 'add' : 'edit')
+ );
+
+ // For new records, check the create permission.
+ if ($isNew && (count($user->getAuthorisedCategories($component, 'core.create')) > 0)) {
+ ToolbarHelper::apply('category.apply');
+ ToolbarHelper::saveGroup(
+ [
+ ['save', 'category.save'],
+ ['save2new', 'category.save2new']
+ ],
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('category.cancel');
+ } else {
+ // If not checked out, can save the item.
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_user_id == $userId);
+
+ $toolbarButtons = [];
+
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ ToolbarHelper::apply('category.apply');
+
+ $toolbarButtons[] = ['save', 'category.save'];
+
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'category.save2new'];
+ }
+ }
+
+ // If an existing item, can save to a copy.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'category.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('category.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $componentParams->get('save_history', 0) && $itemEditable) {
+ $typeAlias = $extension . '.category';
+ ToolbarHelper::versions($typeAlias, $this->item->id);
+ }
+
+ if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations')) {
+ ToolbarHelper::custom('category.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
+ }
+ }
+
+ ToolbarHelper::divider();
+
+ // Look first in form for help key
+ $ref_key = (string) $this->form->getXml()->help['key'];
+
+ // Try with a language string
+ if (!$ref_key) {
+ // Compute the ref_key if it does exist in the component
+ $languageKey = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT') . '_HELP_KEY';
+
+ if ($lang->hasKey($languageKey)) {
+ $ref_key = $languageKey;
+ } else {
+ $languageKey = 'JHELP_COMPONENTS_'
+ . strtoupper(substr($component, 4) . ($section ? "_$section" : ''))
+ . '_CATEGORY_' . ($isNew ? 'ADD' : 'EDIT');
+
+ if ($lang->hasKey($languageKey)) {
+ $ref_key = $languageKey;
+ }
+ }
+ }
+
+ /*
+ * Get help for the category/section view for the component by
+ * -remotely searching in a URL defined in the category form
+ * -remotely searching in a language defined dedicated URL: *component*_HELP_URL
+ * -locally searching in a component help file if helpURL param exists in the component and is set to ''
+ * -remotely searching in a component URL if helpURL param exists in the component and is NOT set to ''
+ */
+ $url = (string) $this->form->getXml()->help['url'];
+
+ if (!$url) {
+ if ($lang->hasKey($lang_help_url = strtoupper($component) . '_HELP_URL')) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($lang_help_url);
+ $lang->setDebug($debug);
+ }
+ }
+
+ ToolbarHelper::help($ref_key, $componentParams->exists('helpURL'), $url, $component);
+ }
}
diff --git a/administrator/components/com_categories/tmpl/categories/default.php b/administrator/components/com_categories/tmpl/categories/default.php
index e4ad8677615e6..9a05036baf514 100644
--- a/administrator/components/com_categories/tmpl/categories/default.php
+++ b/administrator/components/com_categories/tmpl/categories/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$userId = $user->get('id');
@@ -33,281 +34,273 @@
$component = $parts[0];
$section = null;
-if (count($parts) > 1)
-{
- $section = $parts[1];
+if (count($parts) > 1) {
+ $section = $parts[1];
- $inflector = Inflector::getInstance();
+ $inflector = Inflector::getInstance();
- if (!$inflector->isPlural($section))
- {
- $section = $inflector->toPlural($section);
- }
+ if (!$inflector->isPlural($section)) {
+ $section = $inflector->toPlural($section);
+ }
}
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_categories&task=categories.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_categories&task=categories.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_categories/tmpl/categories/default_batch_body.php b/administrator/components/com_categories/tmpl/categories/default_batch_body.php
index 700addd6f6756..aa432d63f01c5 100644
--- a/administrator/components/com_categories/tmpl/categories/default_batch_body.php
+++ b/administrator/components/com_categories/tmpl/categories/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
@@ -19,46 +21,46 @@
?>
diff --git a/administrator/components/com_categories/tmpl/categories/default_batch_footer.php b/administrator/components/com_categories/tmpl/categories/default_batch_footer.php
index 5f37efc761a37..3f29dd80ef2ed 100644
--- a/administrator/components/com_categories/tmpl/categories/default_batch_footer.php
+++ b/administrator/components/com_categories/tmpl/categories/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_categories/tmpl/categories/emptystate.php b/administrator/components/com_categories/tmpl/categories/emptystate.php
index 03b15bdbece0b..df600d521a58e 100644
--- a/administrator/components/com_categories/tmpl/categories/emptystate.php
+++ b/administrator/components/com_categories/tmpl/categories/emptystate.php
@@ -1,4 +1,5 @@
load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
// If a component categories title string is present, let's use it.
-if ($lang->hasKey($component_title_key = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_TITLE'))
-{
- $title = Text::_($component_title_key);
-}
-// Else if the component section string exists, let's use it
-elseif ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : ''))))
+if ($lang->hasKey($component_title_key = strtoupper($component . ($section ? "_$section" : '')) . '_CATEGORIES_TITLE')) {
+ $title = Text::_($component_title_key);
+} elseif ($lang->hasKey($component_section_key = strtoupper($component . ($section ? "_$section" : '')))) {
+ // Else if the component section string exists, let's use it
+ $title = Text::sprintf('COM_CATEGORIES_CATEGORIES_TITLE', $this->escape(Text::_($component_section_key)));
+} else // Else use the base title
{
- $title = Text::sprintf('COM_CATEGORIES_CATEGORIES_TITLE', $this->escape(Text::_($component_section_key)));
-}
-else // Else use the base title
-{
- $title = Text::_('COM_CATEGORIES_CATEGORIES_BASE_TITLE');
+ $title = Text::_('COM_CATEGORIES_CATEGORIES_BASE_TITLE');
}
$displayData = [
- 'textPrefix' => 'COM_CATEGORIES',
- 'formURL' => 'index.php?option=com_categories&extension=' . $extension,
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Category',
- 'title' => $title,
- 'icon' => 'icon-folder categories content-categories',
+ 'textPrefix' => 'COM_CATEGORIES',
+ 'formURL' => 'index.php?option=com_categories&extension=' . $extension,
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Category',
+ 'title' => $title,
+ 'icon' => 'icon-folder categories content-categories',
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', $extension))
-{
- $displayData['createURL'] = 'index.php?option=com_categories&extension=' . $extension . '&task=category.add';
+if (Factory::getApplication()->getIdentity()->authorise('core.create', $extension)) {
+ $displayData['createURL'] = 'index.php?option=com_categories&extension=' . $extension . '&task=category.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_categories/tmpl/categories/modal.php b/administrator/components/com_categories/tmpl/categories/modal.php
index 2500c0b44a004..819957f58ddc7 100644
--- a/administrator/components/com_categories/tmpl/categories/modal.php
+++ b/administrator/components/com_categories/tmpl/categories/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if ($app->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
HTMLHelper::_('behavior.core');
@@ -34,114 +34,106 @@
?>
diff --git a/administrator/components/com_categories/tmpl/category/edit.php b/administrator/components/com_categories/tmpl/category/edit.php
index 448b4a18d5832..b20338fdbb7e4 100644
--- a/administrator/components/com_categories/tmpl/category/edit.php
+++ b/administrator/components/com_categories/tmpl/category/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$input = $app->input;
@@ -34,14 +35,12 @@
$c = Factory::getApplication()->bootComponent($this->state->get('category.extension'));
-if ($c instanceof WorkflowServiceInterface)
-{
- $wcontext = $c->getCategoryWorkflowContext($this->state->get('category.section'));
+if ($c instanceof WorkflowServiceInterface) {
+ $wcontext = $c->getCategoryWorkflowContext($this->state->get('category.section'));
- if (!$c->isWorkflowActive($wcontext))
- {
- $this->ignore_fieldsets[] = 'workflow';
- }
+ if (!$c->isWorkflowActive($wcontext)) {
+ $this->ignore_fieldsets[] = 'workflow';
+ }
}
$this->useCoreUI = true;
@@ -54,78 +53,77 @@
diff --git a/administrator/components/com_categories/tmpl/category/modal.php b/administrator/components/com_categories/tmpl/category/modal.php
index 2adebfb9bcee7..87526bec61712 100644
--- a/administrator/components/com_categories/tmpl/category/modal.php
+++ b/administrator/components/com_categories/tmpl/category/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_checkin/services/provider.php b/administrator/components/com_checkin/services/provider.php
index e4ad52d3d4754..66b8f0fec1811 100644
--- a/administrator/components/com_checkin/services/provider.php
+++ b/administrator/components/com_checkin/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Checkin'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Checkin'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Checkin'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Checkin'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_checkin/src/Controller/DisplayController.php b/administrator/components/com_checkin/src/Controller/DisplayController.php
index 34c5512726681..0cffe3cfe2133 100644
--- a/administrator/components/com_checkin/src/Controller/DisplayController.php
+++ b/administrator/components/com_checkin/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'string');
-
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'), 'warning');
- }
- else
- {
- // Get the model.
- /** @var \Joomla\Component\Checkin\Administrator\Model\CheckinModel $model */
- $model = $this->getModel('Checkin');
-
- // Checked in the items.
- $this->setMessage(Text::plural('COM_CHECKIN_N_ITEMS_CHECKED_IN', $model->checkin($ids)));
- }
-
- $this->setRedirect('index.php?option=com_checkin');
- }
-
- /**
- * Provide the data for a badge in a menu item via JSON
- *
- * @return void
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function getMenuBadgeData()
- {
- if (!$this->app->getIdentity()->authorise('core.manage', 'com_checkin'))
- {
- throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
- }
-
- $model = $this->getModel('Checkin');
-
- $amount = (int) count($model->getItems());
-
- echo new JsonResponse($amount);
- }
-
- /**
- * Method to get the number of locked icons
- *
- * @return void
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function getQuickiconContent()
- {
- if (!$this->app->getIdentity()->authorise('core.manage', 'com_checkin'))
- {
- throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
- }
-
- $model = $this->getModel('Checkin');
-
- $amount = (int) count($model->getItems());
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_CHECKIN_N_QUICKICON_SRONLY', $amount);
-
- echo new JsonResponse($result);
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'checkin';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ return parent::display();
+ }
+
+ /**
+ * Check in a list of items.
+ *
+ * @return void
+ */
+ public function checkin()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'string');
+
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'), 'warning');
+ } else {
+ // Get the model.
+ /** @var \Joomla\Component\Checkin\Administrator\Model\CheckinModel $model */
+ $model = $this->getModel('Checkin');
+
+ // Checked in the items.
+ $this->setMessage(Text::plural('COM_CHECKIN_N_ITEMS_CHECKED_IN', $model->checkin($ids)));
+ }
+
+ $this->setRedirect('index.php?option=com_checkin');
+ }
+
+ /**
+ * Provide the data for a badge in a menu item via JSON
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function getMenuBadgeData()
+ {
+ if (!$this->app->getIdentity()->authorise('core.manage', 'com_checkin')) {
+ throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
+ }
+
+ $model = $this->getModel('Checkin');
+
+ $amount = (int) count($model->getItems());
+
+ echo new JsonResponse($amount);
+ }
+
+ /**
+ * Method to get the number of locked icons
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function getQuickiconContent()
+ {
+ if (!$this->app->getIdentity()->authorise('core.manage', 'com_checkin')) {
+ throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
+ }
+
+ $model = $this->getModel('Checkin');
+
+ $amount = (int) count($model->getItems());
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_CHECKIN_N_QUICKICON_SRONLY', $amount);
+
+ echo new JsonResponse($result);
+ }
}
diff --git a/administrator/components/com_checkin/src/Model/CheckinModel.php b/administrator/components/com_checkin/src/Model/CheckinModel.php
index efd485d2a8911..f3f5b92fb4c41 100644
--- a/administrator/components/com_checkin/src/Model/CheckinModel.php
+++ b/administrator/components/com_checkin/src/Model/CheckinModel.php
@@ -1,4 +1,5 @@
getDatabase();
-
- if (!is_array($ids))
- {
- return 0;
- }
-
- // This int will hold the checked item count.
- $results = 0;
-
- $app = Factory::getApplication();
-
- foreach ($ids as $tn)
- {
- // Make sure we get the right tables based on prefix.
- if (stripos($tn, $app->get('dbprefix')) !== 0)
- {
- continue;
- }
-
- $fields = $db->getTableColumns($tn, false);
-
- if (!(isset($fields['checked_out']) && isset($fields['checked_out_time'])))
- {
- continue;
- }
-
- $query = $db->getQuery(true)
- ->update($db->quoteName($tn))
- ->set($db->quoteName('checked_out') . ' = DEFAULT');
-
- if ($fields['checked_out_time']->Null === 'YES')
- {
- $query->set($db->quoteName('checked_out_time') . ' = NULL');
- }
- else
- {
- $nullDate = $db->getNullDate();
-
- $query->set($db->quoteName('checked_out_time') . ' = :checkouttime')
- ->bind(':checkouttime', $nullDate);
- }
-
- if ($fields['checked_out']->Null === 'YES')
- {
- $query->where($db->quoteName('checked_out') . ' IS NOT NULL');
- }
- else
- {
- $query->where($db->quoteName('checked_out') . ' > 0');
- }
-
- $db->setQuery($query);
-
- if ($db->execute())
- {
- $results = $results + $db->getAffectedRows();
- $app->triggerEvent('onAfterCheckin', array($tn));
- }
- }
-
- return $results;
- }
-
- /**
- * Get total of tables
- *
- * @return integer Total to check-in tables
- *
- * @since 1.6
- */
- public function getTotal()
- {
- if (!isset($this->total))
- {
- $this->getItems();
- }
-
- return $this->total;
- }
-
- /**
- * Get tables
- *
- * @return array Checked in table names as keys and checked in item count as values.
- *
- * @since 1.6
- */
- public function getItems()
- {
- if (!isset($this->items))
- {
- $db = $this->getDatabase();
- $tables = $db->getTableList();
- $prefix = Factory::getApplication()->get('dbprefix');
-
- // This array will hold table name as key and checked in item count as value.
- $results = array();
-
- foreach ($tables as $tn)
- {
- // Make sure we get the right tables based on prefix.
- if (stripos($tn, $prefix) !== 0)
- {
- continue;
- }
-
- if ($this->getState('filter.search') && stripos($tn, $this->getState('filter.search')) === false)
- {
- continue;
- }
-
- $fields = $db->getTableColumns($tn, false);
-
- if (!(isset($fields['checked_out']) && isset($fields['checked_out_time'])))
- {
- continue;
- }
-
- $query = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName($tn));
-
- if ($fields['checked_out']->Null === 'YES')
- {
- $query->where($db->quoteName('checked_out') . ' IS NOT NULL');
- }
- else
- {
- $query->where($db->quoteName('checked_out') . ' > 0');
- }
-
- $db->setQuery($query);
- $count = $db->loadResult();
-
- if ($count)
- {
- $results[$tn] = $count;
- }
- }
-
- $this->total = count($results);
-
- // Order items by table
- if ($this->getState('list.ordering') == 'table')
- {
- if (strtolower($this->getState('list.direction')) == 'asc')
- {
- ksort($results);
- }
- else
- {
- krsort($results);
- }
- }
- // Order items by number of items
- else
- {
- if (strtolower($this->getState('list.direction')) == 'asc')
- {
- asort($results);
- }
- else
- {
- arsort($results);
- }
- }
-
- // Pagination
- $limit = (int) $this->getState('list.limit');
-
- if ($limit !== 0)
- {
- $this->items = array_slice($results, $this->getState('list.start'), $limit);
- }
- else
- {
- $this->items = $results;
- }
- }
-
- return $this->items;
- }
+ /**
+ * Count of the total items checked out
+ *
+ * @var integer
+ */
+ protected $total;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'table',
+ 'count',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note: Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'table', $direction = 'asc')
+ {
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Checks in requested tables
+ *
+ * @param array $ids An array of table names. Optional.
+ *
+ * @return mixed The database results or 0
+ *
+ * @since 1.6
+ */
+ public function checkin($ids = array())
+ {
+ $db = $this->getDatabase();
+
+ if (!is_array($ids)) {
+ return 0;
+ }
+
+ // This int will hold the checked item count.
+ $results = 0;
+
+ $app = Factory::getApplication();
+
+ foreach ($ids as $tn) {
+ // Make sure we get the right tables based on prefix.
+ if (stripos($tn, $app->get('dbprefix')) !== 0) {
+ continue;
+ }
+
+ $fields = $db->getTableColumns($tn, false);
+
+ if (!(isset($fields['checked_out']) && isset($fields['checked_out_time']))) {
+ continue;
+ }
+
+ $query = $db->getQuery(true)
+ ->update($db->quoteName($tn))
+ ->set($db->quoteName('checked_out') . ' = DEFAULT');
+
+ if ($fields['checked_out_time']->Null === 'YES') {
+ $query->set($db->quoteName('checked_out_time') . ' = NULL');
+ } else {
+ $nullDate = $db->getNullDate();
+
+ $query->set($db->quoteName('checked_out_time') . ' = :checkouttime')
+ ->bind(':checkouttime', $nullDate);
+ }
+
+ if ($fields['checked_out']->Null === 'YES') {
+ $query->where($db->quoteName('checked_out') . ' IS NOT NULL');
+ } else {
+ $query->where($db->quoteName('checked_out') . ' > 0');
+ }
+
+ $db->setQuery($query);
+
+ if ($db->execute()) {
+ $results = $results + $db->getAffectedRows();
+ $app->triggerEvent('onAfterCheckin', array($tn));
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get total of tables
+ *
+ * @return integer Total to check-in tables
+ *
+ * @since 1.6
+ */
+ public function getTotal()
+ {
+ if (!isset($this->total)) {
+ $this->getItems();
+ }
+
+ return $this->total;
+ }
+
+ /**
+ * Get tables
+ *
+ * @return array Checked in table names as keys and checked in item count as values.
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ if (!isset($this->items)) {
+ $db = $this->getDatabase();
+ $tables = $db->getTableList();
+ $prefix = Factory::getApplication()->get('dbprefix');
+
+ // This array will hold table name as key and checked in item count as value.
+ $results = array();
+
+ foreach ($tables as $tn) {
+ // Make sure we get the right tables based on prefix.
+ if (stripos($tn, $prefix) !== 0) {
+ continue;
+ }
+
+ if ($this->getState('filter.search') && stripos($tn, $this->getState('filter.search')) === false) {
+ continue;
+ }
+
+ $fields = $db->getTableColumns($tn, false);
+
+ if (!(isset($fields['checked_out']) && isset($fields['checked_out_time']))) {
+ continue;
+ }
+
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName($tn));
+
+ if ($fields['checked_out']->Null === 'YES') {
+ $query->where($db->quoteName('checked_out') . ' IS NOT NULL');
+ } else {
+ $query->where($db->quoteName('checked_out') . ' > 0');
+ }
+
+ $db->setQuery($query);
+ $count = $db->loadResult();
+
+ if ($count) {
+ $results[$tn] = $count;
+ }
+ }
+
+ $this->total = count($results);
+
+ // Order items by table
+ if ($this->getState('list.ordering') == 'table') {
+ if (strtolower($this->getState('list.direction')) == 'asc') {
+ ksort($results);
+ } else {
+ krsort($results);
+ }
+ } else {
+ // Order items by number of items
+ if (strtolower($this->getState('list.direction')) == 'asc') {
+ asort($results);
+ } else {
+ arsort($results);
+ }
+ }
+
+ // Pagination
+ $limit = (int) $this->getState('list.limit');
+
+ if ($limit !== 0) {
+ $this->items = array_slice($results, $this->getState('list.start'), $limit);
+ } else {
+ $this->items = $results;
+ }
+ }
+
+ return $this->items;
+ }
}
diff --git a/administrator/components/com_checkin/src/View/Checkin/HtmlView.php b/administrator/components/com_checkin/src/View/Checkin/HtmlView.php
index 7bb8218f1611d..6c08abab488f2 100644
--- a/administrator/components/com_checkin/src/View/Checkin/HtmlView.php
+++ b/administrator/components/com_checkin/src/View/Checkin/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->total = $this->get('Total');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items))
- {
- $this->isEmptyState = true;
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_('COM_CHECKIN_GLOBAL_CHECK_IN'), 'check-square');
-
- if (!$this->isEmptyState)
- {
- ToolbarHelper::custom('checkin', 'checkin', '', 'JTOOLBAR_CHECKIN', true);
- }
-
- if (Factory::getApplication()->getIdentity()->authorise('core.admin', 'com_checkin'))
- {
- ToolbarHelper::divider();
- ToolbarHelper::preferences('com_checkin');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Maintenance:_Global_Check-in');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->total = $this->get('Total');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items)) {
+ $this->isEmptyState = true;
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_CHECKIN_GLOBAL_CHECK_IN'), 'check-square');
+
+ if (!$this->isEmptyState) {
+ ToolbarHelper::custom('checkin', 'checkin', '', 'JTOOLBAR_CHECKIN', true);
+ }
+
+ if (Factory::getApplication()->getIdentity()->authorise('core.admin', 'com_checkin')) {
+ ToolbarHelper::divider();
+ ToolbarHelper::preferences('com_checkin');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Maintenance:_Global_Check-in');
+ }
}
diff --git a/administrator/components/com_checkin/tmpl/checkin/default.php b/administrator/components/com_checkin/tmpl/checkin/default.php
index 930cec729630d..1718632ffaa34 100644
--- a/administrator/components/com_checkin/tmpl/checkin/default.php
+++ b/administrator/components/com_checkin/tmpl/checkin/default.php
@@ -1,4 +1,5 @@
escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_checkin/tmpl/checkin/emptystate.php b/administrator/components/com_checkin/tmpl/checkin/emptystate.php
index 9a85834893264..640e87e608e7b 100644
--- a/administrator/components/com_checkin/tmpl/checkin/emptystate.php
+++ b/administrator/components/com_checkin/tmpl/checkin/emptystate.php
@@ -1,4 +1,5 @@
'COM_CHECKIN',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Maintenance:_Global_Check-in',
- 'icon' => 'icon-check-square',
- 'title' => Text::_('COM_CHECKIN_GLOBAL_CHECK_IN'),
+ 'textPrefix' => 'COM_CHECKIN',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Maintenance:_Global_Check-in',
+ 'icon' => 'icon-check-square',
+ 'title' => Text::_('COM_CHECKIN_GLOBAL_CHECK_IN'),
];
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_config/services/provider.php b/administrator/components/com_config/services/provider.php
index d5a8df8a3d035..f0b39f879ae5f 100644
--- a/administrator/components/com_config/services/provider.php
+++ b/administrator/components/com_config/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Config'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Config'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Config'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Config'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Config'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Config'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new ConfigComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new ConfigComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_config/src/Controller/ApplicationController.php b/administrator/components/com_config/src/Controller/ApplicationController.php
index 9fc6687c48cab..7cc6227cc033b 100644
--- a/administrator/components/com_config/src/Controller/ApplicationController.php
+++ b/administrator/components/com_config/src/Controller/ApplicationController.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- }
-
- /**
- * Cancel operation.
- *
- * @return void
- *
- * @since 3.0.0
- */
- public function cancel()
- {
- $this->setRedirect(Route::_('index.php?option=com_cpanel'));
- }
-
- /**
- * Saves the form
- *
- * @return void|boolean Void on success. Boolean false on fail.
- *
- * @since 4.0.0
- */
- public function save()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- // Check if the user is authorized to do this.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'), 'error');
-
- return false;
- }
-
- $this->app->setUserState('com_config.config.global.data', null);
-
- /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
- $model = $this->getModel('Application', 'Administrator');
-
- $data = $this->input->post->get('jform', array(), 'array');
-
- // Complete data array if needed
- $oldData = $model->getData();
- $data = array_replace($oldData, $data);
-
- // Get request type
- $saveFormat = $this->app->getDocument()->getType();
-
- // Handle service requests
- if ($saveFormat == 'json')
- {
- $form = $model->getForm();
- $return = $model->validate($form, $data);
-
- if ($return === false)
- {
- $this->app->setHeader('Status', 422, true);
-
- return false;
- }
-
- return $model->save($return);
- }
-
- // Must load after serving service-requests
- $form = $model->getForm();
-
- // Validate the posted data.
- $return = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($return === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $this->app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Save the posted data in the session.
- $this->app->setUserState('com_config.config.global.data', $data);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_config', false));
-
- return false;
- }
-
- // Validate database connection data.
- $data = $return;
- $return = $model->validateDbConnection($data);
-
- // Check for validation errors.
- if ($return === false)
- {
- /*
- * The validateDbConnection method enqueued all messages for us.
- */
-
- // Save the posted data in the session.
- $this->app->setUserState('com_config.config.global.data', $data);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_config', false));
-
- return false;
- }
-
- // Save the validated data in the session.
- $this->app->setUserState('com_config.config.global.data', $return);
-
- // Attempt to save the configuration.
- $data = $return;
- $return = $model->save($data);
-
- // Check the return value.
- if ($return === false)
- {
- /*
- * The save method enqueued all messages for us, so we just need to redirect back.
- */
-
- // Save failed, go back to the screen and display a notice.
- $this->setRedirect(Route::_('index.php?option=com_config', false));
-
- return false;
- }
-
- // Set the success message.
- $this->app->enqueueMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'), 'message');
-
- // Set the redirect based on the task.
- switch ($this->input->getCmd('task'))
- {
- case 'apply':
- $this->setRedirect(Route::_('index.php?option=com_config', false));
- break;
-
- case 'save':
- default:
- $this->setRedirect(Route::_('index.php', false));
- break;
- }
- }
-
- /**
- * Method to remove root in global configuration.
- *
- * @return boolean
- *
- * @since 3.2
- */
- public function removeroot()
- {
- // Check for request forgeries.
- if (!Session::checkToken('get'))
- {
- $this->setRedirect('index.php', Text::_('JINVALID_TOKEN'), 'error');
-
- return false;
- }
-
- // Check if the user is authorized to do this.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'), 'error');
-
- return false;
- }
-
- // Initialise model.
-
- /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
- $model = $this->getModel('Application', 'Administrator');
-
- // Attempt to save the configuration and remove root.
- try
- {
- $model->removeroot();
- }
- catch (\RuntimeException $e)
- {
- // Save failed, go back to the screen and display a notice.
- $this->setRedirect('index.php', Text::_('JERROR_SAVE_FAILED', $e->getMessage()), 'error');
-
- return false;
- }
-
- // Set the redirect based on the task.
- $this->setRedirect(Route::_('index.php'), Text::_('COM_CONFIG_SAVE_SUCCESS'));
-
- return true;
- }
-
- /**
- * Method to send the test mail.
- *
- * @return void
- *
- * @since 3.5
- */
- public function sendtestmail()
- {
- // Send json mime type.
- $this->app->mimeType = 'application/json';
- $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
- $this->app->sendHeaders();
-
- // Check if user token is valid.
- if (!Session::checkToken())
- {
- $this->app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
- echo new JsonResponse;
- $this->app->close();
- }
-
- // Check if the user is authorized to do this.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
- echo new JsonResponse;
- $this->app->close();
- }
-
- /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
- $model = $this->getModel('Application', 'Administrator');
-
- echo new JsonResponse($model->sendTestMail());
-
- $this->app->close();
- }
-
- /**
- * Method to GET permission value and give it to the model for storing in the database.
- *
- * @return void
- *
- * @since 3.5
- */
- public function store()
- {
- // Send json mime type.
- $this->app->mimeType = 'application/json';
- $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
- $this->app->sendHeaders();
-
- // Check if user token is valid.
- if (!Session::checkToken('get'))
- {
- $this->app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
- echo new JsonResponse;
- $this->app->close();
- }
-
- /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
- $model = $this->getModel('Application', 'Administrator');
- echo new JsonResponse($model->storePermissions());
- $this->app->close();
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Map the apply task to the save method.
+ $this->registerTask('apply', 'save');
+ }
+
+ /**
+ * Cancel operation.
+ *
+ * @return void
+ *
+ * @since 3.0.0
+ */
+ public function cancel()
+ {
+ $this->setRedirect(Route::_('index.php?option=com_cpanel'));
+ }
+
+ /**
+ * Saves the form
+ *
+ * @return void|boolean Void on success. Boolean false on fail.
+ *
+ * @since 4.0.0
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ // Check if the user is authorized to do this.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+
+ return false;
+ }
+
+ $this->app->setUserState('com_config.config.global.data', null);
+
+ /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
+ $model = $this->getModel('Application', 'Administrator');
+
+ $data = $this->input->post->get('jform', array(), 'array');
+
+ // Complete data array if needed
+ $oldData = $model->getData();
+ $data = array_replace($oldData, $data);
+
+ // Get request type
+ $saveFormat = $this->app->getDocument()->getType();
+
+ // Handle service requests
+ if ($saveFormat == 'json') {
+ $form = $model->getForm();
+ $return = $model->validate($form, $data);
+
+ if ($return === false) {
+ $this->app->setHeader('Status', 422, true);
+
+ return false;
+ }
+
+ return $model->save($return);
+ }
+
+ // Must load after serving service-requests
+ $form = $model->getForm();
+
+ // Validate the posted data.
+ $return = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($return === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $this->app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Save the posted data in the session.
+ $this->app->setUserState('com_config.config.global.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_config', false));
+
+ return false;
+ }
+
+ // Validate database connection data.
+ $data = $return;
+ $return = $model->validateDbConnection($data);
+
+ // Check for validation errors.
+ if ($return === false) {
+ /*
+ * The validateDbConnection method enqueued all messages for us.
+ */
+
+ // Save the posted data in the session.
+ $this->app->setUserState('com_config.config.global.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_config', false));
+
+ return false;
+ }
+
+ // Save the validated data in the session.
+ $this->app->setUserState('com_config.config.global.data', $return);
+
+ // Attempt to save the configuration.
+ $data = $return;
+ $return = $model->save($data);
+
+ // Check the return value.
+ if ($return === false) {
+ /*
+ * The save method enqueued all messages for us, so we just need to redirect back.
+ */
+
+ // Save failed, go back to the screen and display a notice.
+ $this->setRedirect(Route::_('index.php?option=com_config', false));
+
+ return false;
+ }
+
+ // Set the success message.
+ $this->app->enqueueMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'), 'message');
+
+ // Set the redirect based on the task.
+ switch ($this->input->getCmd('task')) {
+ case 'apply':
+ $this->setRedirect(Route::_('index.php?option=com_config', false));
+ break;
+
+ case 'save':
+ default:
+ $this->setRedirect(Route::_('index.php', false));
+ break;
+ }
+ }
+
+ /**
+ * Method to remove root in global configuration.
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ public function removeroot()
+ {
+ // Check for request forgeries.
+ if (!Session::checkToken('get')) {
+ $this->setRedirect('index.php', Text::_('JINVALID_TOKEN'), 'error');
+
+ return false;
+ }
+
+ // Check if the user is authorized to do this.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+
+ return false;
+ }
+
+ // Initialise model.
+
+ /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
+ $model = $this->getModel('Application', 'Administrator');
+
+ // Attempt to save the configuration and remove root.
+ try {
+ $model->removeroot();
+ } catch (\RuntimeException $e) {
+ // Save failed, go back to the screen and display a notice.
+ $this->setRedirect('index.php', Text::_('JERROR_SAVE_FAILED', $e->getMessage()), 'error');
+
+ return false;
+ }
+
+ // Set the redirect based on the task.
+ $this->setRedirect(Route::_('index.php'), Text::_('COM_CONFIG_SAVE_SUCCESS'));
+
+ return true;
+ }
+
+ /**
+ * Method to send the test mail.
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public function sendtestmail()
+ {
+ // Send json mime type.
+ $this->app->mimeType = 'application/json';
+ $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
+ $this->app->sendHeaders();
+
+ // Check if user token is valid.
+ if (!Session::checkToken()) {
+ $this->app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
+ echo new JsonResponse();
+ $this->app->close();
+ }
+
+ // Check if the user is authorized to do this.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+ echo new JsonResponse();
+ $this->app->close();
+ }
+
+ /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
+ $model = $this->getModel('Application', 'Administrator');
+
+ echo new JsonResponse($model->sendTestMail());
+
+ $this->app->close();
+ }
+
+ /**
+ * Method to GET permission value and give it to the model for storing in the database.
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public function store()
+ {
+ // Send json mime type.
+ $this->app->mimeType = 'application/json';
+ $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet);
+ $this->app->sendHeaders();
+
+ // Check if user token is valid.
+ if (!Session::checkToken('get')) {
+ $this->app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
+ echo new JsonResponse();
+ $this->app->close();
+ }
+
+ /** @var \Joomla\Component\Config\Administrator\Model\ApplicationModel $model */
+ $model = $this->getModel('Application', 'Administrator');
+ echo new JsonResponse($model->storePermissions());
+ $this->app->close();
+ }
}
diff --git a/administrator/components/com_config/src/Controller/ComponentController.php b/administrator/components/com_config/src/Controller/ComponentController.php
index a7718e366b545..791c1a1ef66ca 100644
--- a/administrator/components/com_config/src/Controller/ComponentController.php
+++ b/administrator/components/com_config/src/Controller/ComponentController.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- }
-
- /**
- * Method to save component configuration.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean
- *
- * @since 3.2
- */
- public function save($key = null, $urlVar = null)
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $data = $this->input->get('jform', [], 'ARRAY');
- $id = $this->input->get('id', null, 'INT');
- $option = $this->input->get('component');
- $user = $this->app->getIdentity();
- $context = "$this->option.edit.$this->context.$option";
-
- /** @var \Joomla\Component\Config\Administrator\Model\ComponentModel $model */
- $model = $this->getModel('Component', 'Administrator');
- $model->setState('component.option', $option);
- $form = $model->getForm();
-
- // Make sure com_joomlaupdate and com_privacy can only be accessed by SuperUser
- if (\in_array(strtolower($option), ['com_joomlaupdate', 'com_privacy'], true) && !$user->authorise('core.admin'))
- {
- $this->setRedirect(Route::_('index.php', false), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
- }
-
- // Check if the user is authorised to do this.
- if (!$user->authorise('core.admin', $option) && !$user->authorise('core.options', $option))
- {
- $this->setRedirect(Route::_('index.php', false), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
- }
-
- // Remove the permissions rules data if user isn't allowed to edit them.
- if (!$user->authorise('core.admin', $option) && isset($data['params']) && isset($data['params']['rules']))
- {
- unset($data['params']['rules']);
- }
-
- $returnUri = $this->input->post->get('return', null, 'base64');
-
- $redirect = '';
-
- if (!empty($returnUri))
- {
- $redirect = '&return=' . urlencode($returnUri);
- }
-
- // Validate the posted data.
- $return = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($return === false)
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $data);
-
- // Redirect back to the edit screen.
- $this->setRedirect(
- Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
- $model->getError(),
- 'error'
- );
-
- return false;
- }
-
- // Attempt to save the configuration.
- $data = [
- 'params' => $return,
- 'id' => $id,
- 'option' => $option,
- ];
-
- try
- {
- $model->save($data);
- }
- catch (\RuntimeException $e)
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $data);
-
- // Save failed, go back to the screen and display a notice.
- $this->setRedirect(
- Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
- Text::_('JERROR_SAVE_FAILED', $e->getMessage()),
- 'error'
- );
-
- return false;
- }
-
- // Clear session data.
- $this->app->setUserState($context . '.data', null);
-
- // Set the redirect based on the task.
- switch ($this->input->get('task'))
- {
- case 'apply':
- $this->setRedirect(
- Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
- Text::_('COM_CONFIG_SAVE_SUCCESS'),
- 'message'
- );
-
- break;
-
- case 'save':
- $this->setMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'), 'message');
- default:
- $redirect = 'index.php?option=' . $option;
-
- if (!empty($returnUri))
- {
- $redirect = base64_decode($returnUri);
- }
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($redirect))
- {
- $redirect = Uri::base();
- }
-
- $this->setRedirect(Route::_($redirect, false));
- }
-
- return true;
- }
-
- /**
- * Method to cancel global configuration component.
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return boolean
- *
- * @since 3.2
- */
- public function cancel($key = null)
- {
- $component = $this->input->get('component');
-
- // Clear session data.
- $this->app->setUserState("$this->option.edit.$this->context.$component.data", null);
-
- // Calculate redirect URL
- $returnUri = $this->input->post->get('return', null, 'base64');
-
- $redirect = 'index.php?option=' . $component;
-
- if (!empty($returnUri))
- {
- $redirect = base64_decode($returnUri);
- }
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($redirect))
- {
- $redirect = Uri::base();
- }
-
- $this->setRedirect(Route::_($redirect, false));
-
- return true;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Map the apply task to the save method.
+ $this->registerTask('apply', 'save');
+ }
+
+ /**
+ * Method to save component configuration.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $data = $this->input->get('jform', [], 'ARRAY');
+ $id = $this->input->get('id', null, 'INT');
+ $option = $this->input->get('component');
+ $user = $this->app->getIdentity();
+ $context = "$this->option.edit.$this->context.$option";
+
+ /** @var \Joomla\Component\Config\Administrator\Model\ComponentModel $model */
+ $model = $this->getModel('Component', 'Administrator');
+ $model->setState('component.option', $option);
+ $form = $model->getForm();
+
+ // Make sure com_joomlaupdate and com_privacy can only be accessed by SuperUser
+ if (\in_array(strtolower($option), ['com_joomlaupdate', 'com_privacy'], true) && !$user->authorise('core.admin')) {
+ $this->setRedirect(Route::_('index.php', false), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+ }
+
+ // Check if the user is authorised to do this.
+ if (!$user->authorise('core.admin', $option) && !$user->authorise('core.options', $option)) {
+ $this->setRedirect(Route::_('index.php', false), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+ }
+
+ // Remove the permissions rules data if user isn't allowed to edit them.
+ if (!$user->authorise('core.admin', $option) && isset($data['params']) && isset($data['params']['rules'])) {
+ unset($data['params']['rules']);
+ }
+
+ $returnUri = $this->input->post->get('return', null, 'base64');
+
+ $redirect = '';
+
+ if (!empty($returnUri)) {
+ $redirect = '&return=' . urlencode($returnUri);
+ }
+
+ // Validate the posted data.
+ $return = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($return === false) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(
+ Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
+ $model->getError(),
+ 'error'
+ );
+
+ return false;
+ }
+
+ // Attempt to save the configuration.
+ $data = [
+ 'params' => $return,
+ 'id' => $id,
+ 'option' => $option,
+ ];
+
+ try {
+ $model->save($data);
+ } catch (\RuntimeException $e) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $data);
+
+ // Save failed, go back to the screen and display a notice.
+ $this->setRedirect(
+ Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
+ Text::_('JERROR_SAVE_FAILED', $e->getMessage()),
+ 'error'
+ );
+
+ return false;
+ }
+
+ // Clear session data.
+ $this->app->setUserState($context . '.data', null);
+
+ // Set the redirect based on the task.
+ switch ($this->input->get('task')) {
+ case 'apply':
+ $this->setRedirect(
+ Route::_('index.php?option=com_config&view=component&component=' . $option . $redirect, false),
+ Text::_('COM_CONFIG_SAVE_SUCCESS'),
+ 'message'
+ );
+
+ break;
+
+ case 'save':
+ $this->setMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'), 'message');
+
+ // No break
+
+ default:
+ $redirect = 'index.php?option=' . $option;
+
+ if (!empty($returnUri)) {
+ $redirect = base64_decode($returnUri);
+ }
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($redirect)) {
+ $redirect = Uri::base();
+ }
+
+ $this->setRedirect(Route::_($redirect, false));
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to cancel global configuration component.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ public function cancel($key = null)
+ {
+ $component = $this->input->get('component');
+
+ // Clear session data.
+ $this->app->setUserState("$this->option.edit.$this->context.$component.data", null);
+
+ // Calculate redirect URL
+ $returnUri = $this->input->post->get('return', null, 'base64');
+
+ $redirect = 'index.php?option=' . $component;
+
+ if (!empty($returnUri)) {
+ $redirect = base64_decode($returnUri);
+ }
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($redirect)) {
+ $redirect = Uri::base();
+ }
+
+ $this->setRedirect(Route::_($redirect, false));
+
+ return true;
+ }
}
diff --git a/administrator/components/com_config/src/Controller/DisplayController.php b/administrator/components/com_config/src/Controller/DisplayController.php
index 2ed6a6f030c14..5a93d0515676d 100644
--- a/administrator/components/com_config/src/Controller/DisplayController.php
+++ b/administrator/components/com_config/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('component', '');
-
- // Make sure com_joomlaupdate and com_privacy can only be accessed by SuperUser
- if (in_array(strtolower($component), array('com_joomlaupdate', 'com_privacy'))
- && !$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->setRedirect(Route::_('index.php'), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
- }
-
- return parent::display($cachable, $urlparams);
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'application';
+
+
+ /**
+ * Typical view method for MVC based architecture
+ *
+ * This function is provide as a default implementation, in most cases
+ * you will need to override it in your own controllers.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see {@link InputFilter::clean()}.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 3.0
+ * @throws \Exception
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $component = $this->input->get('component', '');
+
+ // Make sure com_joomlaupdate and com_privacy can only be accessed by SuperUser
+ if (
+ in_array(strtolower($component), array('com_joomlaupdate', 'com_privacy'))
+ && !$this->app->getIdentity()->authorise('core.admin')
+ ) {
+ $this->setRedirect(Route::_('index.php'), Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+ }
+
+ return parent::display($cachable, $urlparams);
+ }
}
diff --git a/administrator/components/com_config/src/Controller/RequestController.php b/administrator/components/com_config/src/Controller/RequestController.php
index f3121774503e9..4b4c17f167ff3 100644
--- a/administrator/components/com_config/src/Controller/RequestController.php
+++ b/administrator/components/com_config/src/Controller/RequestController.php
@@ -1,4 +1,5 @@
input->getWord('option', 'com_config');
-
- if ($this->app->isClient('administrator'))
- {
- $viewName = $this->input->getWord('view', 'application');
- }
- else
- {
- $viewName = $this->input->getWord('view', 'config');
- }
-
- // Register the layout paths for the view
- $paths = new \SplPriorityQueue;
-
- if ($this->app->isClient('administrator'))
- {
- $paths->insert(JPATH_ADMINISTRATOR . '/components/' . $componentFolder . '/view/' . $viewName . '/tmpl', 1);
- }
- else
- {
- $paths->insert(JPATH_BASE . '/components/' . $componentFolder . '/view/' . $viewName . '/tmpl', 1);
- }
-
- $model = new \Joomla\Component\Config\Administrator\Model\ApplicationModel;
- $component = $model->getState()->get('component.option');
-
- // Access check.
- if (!$this->app->getIdentity()->authorise('core.admin', $component)
- && !$this->app->getIdentity()->authorise('core.options', $component))
- {
- $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
-
- return false;
- }
-
- try
- {
- $data = $model->getData();
- $user = $this->app->getIdentity();
- }
- catch (\Exception $e)
- {
- $this->app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- $this->userIsSuperAdmin = $user->authorise('core.admin');
-
- // Required data
- $requiredData = array(
- 'sitename' => null,
- 'offline' => null,
- 'access' => null,
- 'list_limit' => null,
- 'MetaDesc' => null,
- 'MetaRights' => null,
- 'sef' => null,
- 'sitename_pagetitles' => null,
- 'debug' => null,
- 'debug_lang' => null,
- 'error_reporting' => null,
- 'mailfrom' => null,
- 'fromname' => null
- );
-
- $data = array_intersect_key($data, $requiredData);
-
- return json_encode($data);
- }
+ /**
+ * Execute the controller.
+ *
+ * @return mixed A rendered view or false
+ *
+ * @since 3.2
+ */
+ public function getJson()
+ {
+ $componentFolder = $this->input->getWord('option', 'com_config');
+
+ if ($this->app->isClient('administrator')) {
+ $viewName = $this->input->getWord('view', 'application');
+ } else {
+ $viewName = $this->input->getWord('view', 'config');
+ }
+
+ // Register the layout paths for the view
+ $paths = new \SplPriorityQueue();
+
+ if ($this->app->isClient('administrator')) {
+ $paths->insert(JPATH_ADMINISTRATOR . '/components/' . $componentFolder . '/view/' . $viewName . '/tmpl', 1);
+ } else {
+ $paths->insert(JPATH_BASE . '/components/' . $componentFolder . '/view/' . $viewName . '/tmpl', 1);
+ }
+
+ $model = new \Joomla\Component\Config\Administrator\Model\ApplicationModel();
+ $component = $model->getState()->get('component.option');
+
+ // Access check.
+ if (
+ !$this->app->getIdentity()->authorise('core.admin', $component)
+ && !$this->app->getIdentity()->authorise('core.options', $component)
+ ) {
+ $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+
+ return false;
+ }
+
+ try {
+ $data = $model->getData();
+ $user = $this->app->getIdentity();
+ } catch (\Exception $e) {
+ $this->app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ $this->userIsSuperAdmin = $user->authorise('core.admin');
+
+ // Required data
+ $requiredData = array(
+ 'sitename' => null,
+ 'offline' => null,
+ 'access' => null,
+ 'list_limit' => null,
+ 'MetaDesc' => null,
+ 'MetaRights' => null,
+ 'sef' => null,
+ 'sitename_pagetitles' => null,
+ 'debug' => null,
+ 'debug_lang' => null,
+ 'error_reporting' => null,
+ 'mailfrom' => null,
+ 'fromname' => null
+ );
+
+ $data = array_intersect_key($data, $requiredData);
+
+ return json_encode($data);
+ }
}
diff --git a/administrator/components/com_config/src/Extension/ConfigComponent.php b/administrator/components/com_config/src/Extension/ConfigComponent.php
index f275a9d63321c..6a94cf65712a9 100644
--- a/administrator/components/com_config/src/Extension/ConfigComponent.php
+++ b/administrator/components/com_config/src/Extension/ConfigComponent.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('name AS text, element AS value')
- ->from('#__extensions')
- ->where('enabled >= 1')
- ->where('type =' . $db->quote('component'));
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('name AS text, element AS value')
+ ->from('#__extensions')
+ ->where('enabled >= 1')
+ ->where('type =' . $db->quote('component'));
- $items = $db->setQuery($query)->loadObjectList();
+ $items = $db->setQuery($query)->loadObjectList();
- if ($items)
- {
- $lang = Factory::getLanguage();
+ if ($items) {
+ $lang = Factory::getLanguage();
- foreach ($items as &$item)
- {
- // Load language
- $extension = $item->value;
+ foreach ($items as &$item) {
+ // Load language
+ $extension = $item->value;
- if (File::exists(JPATH_ADMINISTRATOR . '/components/' . $extension . '/config.xml'))
- {
- $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
- $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
- || $lang->load("$extension.sys", $source);
+ if (File::exists(JPATH_ADMINISTRATOR . '/components/' . $extension . '/config.xml')) {
+ $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
+ $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
+ || $lang->load("$extension.sys", $source);
- // Translate component name
- $item->text = Text::_($item->text);
- }
- else
- {
- $item = null;
- }
- }
+ // Translate component name
+ $item->text = Text::_($item->text);
+ } else {
+ $item = null;
+ }
+ }
- // Sort by component name
- $items = ArrayHelper::sortObjects(array_filter($items), 'text', 1, true, true);
- }
+ // Sort by component name
+ $items = ArrayHelper::sortObjects(array_filter($items), 'text', 1, true, true);
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $items);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $items);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_config/src/Field/FiltersField.php b/administrator/components/com_config/src/Field/FiltersField.php
index 052c548face8b..34f18dbef7bab 100644
--- a/administrator/components/com_config/src/Field/FiltersField.php
+++ b/administrator/components/com_config/src/Field/FiltersField.php
@@ -1,4 +1,5 @@
getWebAssetManager()->useScript('com_config.filters');
-
- // Get the available user groups.
- $groups = $this->getUserGroups();
-
- // Build the form control.
- $html = array();
-
- // Open the table.
- $html[] = '';
-
- return implode("\n", $html);
- }
-
- /**
- * A helper to get the list of user groups.
- *
- * @return array
- *
- * @since 1.6
- */
- protected function getUserGroups()
- {
- // Get a database object.
- $db = $this->getDatabase();
-
- // Get the user groups from the database.
- $query = $db->getQuery(true);
- $query->select('a.id AS value, a.title AS text, COUNT(DISTINCT b.id) AS level, a.parent_id as parent');
- $query->from('#__usergroups AS a');
- $query->join('LEFT', '#__usergroups AS b on a.lft > b.lft AND a.rgt < b.rgt');
- $query->group('a.id, a.title, a.lft');
- $query->order('a.lft ASC');
- $db->setQuery($query);
- $options = $db->loadObjectList();
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'Filters';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @todo: Add access check.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ // Add translation string for notification
+ Text::script('COM_CONFIG_TEXT_FILTERS_NOTE');
+
+ // Add Javascript
+ Factory::getDocument()->getWebAssetManager()->useScript('com_config.filters');
+
+ // Get the available user groups.
+ $groups = $this->getUserGroups();
+
+ // Build the form control.
+ $html = array();
+
+ // Open the table.
+ $html[] = '';
+
+ return implode("\n", $html);
+ }
+
+ /**
+ * A helper to get the list of user groups.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ protected function getUserGroups()
+ {
+ // Get a database object.
+ $db = $this->getDatabase();
+
+ // Get the user groups from the database.
+ $query = $db->getQuery(true);
+ $query->select('a.id AS value, a.title AS text, COUNT(DISTINCT b.id) AS level, a.parent_id as parent');
+ $query->from('#__usergroups AS a');
+ $query->join('LEFT', '#__usergroups AS b on a.lft > b.lft AND a.rgt < b.rgt');
+ $query->group('a.id, a.title, a.lft');
+ $query->order('a.lft ASC');
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_config/src/Helper/ConfigHelper.php b/administrator/components/com_config/src/Helper/ConfigHelper.php
index ee5ed8b3d84f5..ac51bd0f83f03 100644
--- a/administrator/components/com_config/src/Helper/ConfigHelper.php
+++ b/administrator/components/com_config/src/Helper/ConfigHelper.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select('element')
- ->from('#__extensions')
- ->where('type = ' . $db->quote('component'))
- ->where('enabled = 1');
- $db->setQuery($query);
- $result = $db->loadColumn();
+ /**
+ * Get an array of all enabled components.
+ *
+ * @return array
+ *
+ * @since 3.0
+ */
+ public static function getAllComponents()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('element')
+ ->from('#__extensions')
+ ->where('type = ' . $db->quote('component'))
+ ->where('enabled = 1');
+ $db->setQuery($query);
+ $result = $db->loadColumn();
- return $result;
- }
+ return $result;
+ }
- /**
- * Returns true if the component has configuration options.
- *
- * @param string $component Component name
- *
- * @return boolean
- *
- * @since 3.0
- */
- public static function hasComponentConfig($component)
- {
- return is_file(JPATH_ADMINISTRATOR . '/components/' . $component . '/config.xml');
- }
+ /**
+ * Returns true if the component has configuration options.
+ *
+ * @param string $component Component name
+ *
+ * @return boolean
+ *
+ * @since 3.0
+ */
+ public static function hasComponentConfig($component)
+ {
+ return is_file(JPATH_ADMINISTRATOR . '/components/' . $component . '/config.xml');
+ }
- /**
- * Returns an array of all components with configuration options.
- * Optionally return only those components for which the current user has 'core.manage' rights.
- *
- * @param boolean $authCheck True to restrict to components where current user has 'core.manage' rights.
- *
- * @return array
- *
- * @since 3.0
- */
- public static function getComponentsWithConfig($authCheck = true)
- {
- $result = array();
- $components = self::getAllComponents();
- $user = Factory::getUser();
+ /**
+ * Returns an array of all components with configuration options.
+ * Optionally return only those components for which the current user has 'core.manage' rights.
+ *
+ * @param boolean $authCheck True to restrict to components where current user has 'core.manage' rights.
+ *
+ * @return array
+ *
+ * @since 3.0
+ */
+ public static function getComponentsWithConfig($authCheck = true)
+ {
+ $result = array();
+ $components = self::getAllComponents();
+ $user = Factory::getUser();
- // Remove com_config from the array as that may have weird side effects
- $components = array_diff($components, array('com_config'));
+ // Remove com_config from the array as that may have weird side effects
+ $components = array_diff($components, array('com_config'));
- foreach ($components as $component)
- {
- if (self::hasComponentConfig($component) && (!$authCheck || $user->authorise('core.manage', $component)))
- {
- self::loadLanguageForComponent($component);
- $result[$component] = ApplicationHelper::stringURLSafe(Text::_($component)) . '_' . $component;
- }
- }
+ foreach ($components as $component) {
+ if (self::hasComponentConfig($component) && (!$authCheck || $user->authorise('core.manage', $component))) {
+ self::loadLanguageForComponent($component);
+ $result[$component] = ApplicationHelper::stringURLSafe(Text::_($component)) . '_' . $component;
+ }
+ }
- asort($result);
+ asort($result);
- return array_keys($result);
- }
+ return array_keys($result);
+ }
- /**
- * Load the sys language for the given component.
- *
- * @param array $components Array of component names.
- *
- * @return void
- *
- * @since 3.0
- */
- public static function loadLanguageForComponents($components)
- {
- foreach ($components as $component)
- {
- self::loadLanguageForComponent($component);
- }
- }
+ /**
+ * Load the sys language for the given component.
+ *
+ * @param array $components Array of component names.
+ *
+ * @return void
+ *
+ * @since 3.0
+ */
+ public static function loadLanguageForComponents($components)
+ {
+ foreach ($components as $component) {
+ self::loadLanguageForComponent($component);
+ }
+ }
- /**
- * Load the sys language for the given component.
- *
- * @param string $component component name.
- *
- * @return void
- *
- * @since 3.5
- */
- public static function loadLanguageForComponent($component)
- {
- if (empty($component))
- {
- return;
- }
+ /**
+ * Load the sys language for the given component.
+ *
+ * @param string $component component name.
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public static function loadLanguageForComponent($component)
+ {
+ if (empty($component)) {
+ return;
+ }
- $lang = Factory::getLanguage();
+ $lang = Factory::getLanguage();
- // Load the core file then
- // Load extension-local file.
- $lang->load($component . '.sys', JPATH_BASE)
- || $lang->load($component . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component);
- }
+ // Load the core file then
+ // Load extension-local file.
+ $lang->load($component . '.sys', JPATH_BASE)
+ || $lang->load($component . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component);
+ }
}
diff --git a/administrator/components/com_config/src/Model/ApplicationModel.php b/administrator/components/com_config/src/Model/ApplicationModel.php
index 77d70c8669785..81c546ff8be96 100644
--- a/administrator/components/com_config/src/Model/ApplicationModel.php
+++ b/administrator/components/com_config/src/Model/ApplicationModel.php
@@ -1,4 +1,5 @@
loadForm('com_config.application', 'application', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the configuration data.
- *
- * This method will load the global configuration data straight from
- * JConfig. If configuration data has been saved in the session, that
- * data will be merged into the original data, overwriting it.
- *
- * @return array An array containing all global config data.
- *
- * @since 1.6
- */
- public function getData()
- {
- // Get the config data.
- $config = new \JConfig;
- $data = ArrayHelper::fromObject($config);
-
- // Get the correct driver at runtime
- $data['dbtype'] = $this->getDatabase()->getName();
-
- // Prime the asset_id for the rules.
- $data['asset_id'] = 1;
-
- // Get the text filter data
- $params = ComponentHelper::getParams('com_config');
- $data['filters'] = ArrayHelper::fromObject($params->get('filters'));
-
- // If no filter data found, get from com_content (update of 1.6/1.7 site)
- if (empty($data['filters']))
- {
- $contentParams = ComponentHelper::getParams('com_content');
- $data['filters'] = ArrayHelper::fromObject($contentParams->get('filters'));
- }
-
- // Check for data in the session.
- $temp = Factory::getApplication()->getUserState('com_config.config.global.data');
-
- // Merge in the session data.
- if (!empty($temp))
- {
- // $temp can sometimes be an object, and we need it to be an array
- if (is_object($temp))
- {
- $temp = ArrayHelper::fromObject($temp);
- }
-
- $data = array_merge($temp, $data);
- }
-
- // Correct error_reporting value, since we removed "development", the "maximum" should be set instead
- // @TODO: This can be removed in 5.0
- if (!empty($data['error_reporting']) && $data['error_reporting'] === 'development')
- {
- $data['error_reporting'] = 'maximum';
- }
-
- return $data;
- }
-
- /**
- * Method to validate the db connection properties.
- *
- * @param array $data An array containing all global config data.
- *
- * @return array|boolean Array with the validated global config data or boolean false on a validation failure.
- *
- * @since 4.0.0
- */
- public function validateDbConnection($data)
- {
- // Validate database connection encryption options
- if ((int) $data['dbencryption'] === 0)
- {
- // Reset unused options
- if (!empty($data['dbsslkey']))
- {
- $data['dbsslkey'] = '';
- }
-
- if (!empty($data['dbsslcert']))
- {
- $data['dbsslcert'] = '';
- }
-
- if ((bool) $data['dbsslverifyservercert'] === true)
- {
- $data['dbsslverifyservercert'] = false;
- }
-
- if (!empty($data['dbsslca']))
- {
- $data['dbsslca'] = '';
- }
-
- if (!empty($data['dbsslcipher']))
- {
- $data['dbsslcipher'] = '';
- }
- }
- else
- {
- // Check localhost
- if (strtolower($data['host']) === 'localhost')
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_LOCALHOST'), 'error');
-
- return false;
- }
-
- // Check CA file and folder depending on database type if server certificate verification
- if ((bool) $data['dbsslverifyservercert'] === true)
- {
- if (empty($data['dbsslca']))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
-
- if (!File::exists(Path::clean($data['dbsslca'])))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
- }
- else
- {
- // Reset unused option
- if (!empty($data['dbsslca']))
- {
- $data['dbsslca'] = '';
- }
- }
-
- // Check key and certificate if two-way encryption
- if ((int) $data['dbencryption'] === 2)
- {
- if (empty($data['dbsslkey']))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
-
- if (!File::exists(Path::clean($data['dbsslkey'])))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
-
- if (empty($data['dbsslcert']))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
-
- if (!File::exists(Path::clean($data['dbsslcert'])))
- {
- Factory::getApplication()->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
- Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL')
- ),
- 'error'
- );
-
- return false;
- }
- }
- else
- {
- // Reset unused options
- if (!empty($data['dbsslkey']))
- {
- $data['dbsslkey'] = '';
- }
-
- if (!empty($data['dbsslcert']))
- {
- $data['dbsslcert'] = '';
- }
- }
- }
-
- return $data;
- }
-
- /**
- * Method to save the configuration data.
- *
- * @param array $data An array containing all global config data.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $app = Factory::getApplication();
-
- // Try to load the values from the configuration file
- foreach ($this->protectedConfigurationFields as $fieldKey)
- {
- if (!isset($data[$fieldKey]))
- {
- $data[$fieldKey] = $app->get($fieldKey, '');
- }
- }
-
- // Check that we aren't setting wrong database configuration
- $options = array(
- 'driver' => $data['dbtype'],
- 'host' => $data['host'],
- 'user' => $data['user'],
- 'password' => $data['password'],
- 'database' => $data['db'],
- 'prefix' => $data['dbprefix'],
- );
-
- if ((int) $data['dbencryption'] !== 0)
- {
- $options['ssl'] = [
- 'enable' => true,
- 'verify_server_cert' => (bool) $data['dbsslverifyservercert'],
- ];
-
- foreach (['cipher', 'ca', 'key', 'cert'] as $value)
- {
- $confVal = trim($data['dbssl' . $value]);
-
- if ($confVal !== '')
- {
- $options['ssl'][$value] = $confVal;
- }
- }
- }
-
- try
- {
- $revisedDbo = DatabaseDriver::getInstance($options);
- $revisedDbo->getVersion();
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_DATABASE_NOT_AVAILABLE', $e->getCode(), $e->getMessage()), 'error');
-
- return false;
- }
-
- if ((int) $data['dbencryption'] !== 0 && empty($revisedDbo->getConnectionEncryption()))
- {
- if ($revisedDbo->isConnectionEncryptionSupported())
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'), 'error');
- }
- else
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'), 'error');
- }
-
- return false;
- }
-
- // Check if we can set the Force SSL option
- if ((int) $data['force_ssl'] !== 0 && (int) $data['force_ssl'] !== (int) $app->get('force_ssl', '0'))
- {
- try
- {
- // Make an HTTPS request to check if the site is available in HTTPS.
- $host = Uri::getInstance()->getHost();
- $options = new Registry;
- $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
-
- // Do not check for valid server certificate here, leave this to the user, moreover disable using a proxy if any is configured.
- $options->set('transport.curl',
- array(
- CURLOPT_SSL_VERIFYPEER => false,
- CURLOPT_SSL_VERIFYHOST => false,
- CURLOPT_PROXY => null,
- CURLOPT_PROXYUSERPWD => null,
- )
- );
- $response = HttpFactory::getHttp($options)->get('https://' . $host . Uri::root(true) . '/', array('Host' => $host), 10);
-
- // If available in HTTPS check also the status code.
- if (!in_array($response->code, array(200, 503, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 401), true))
- {
- throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE_HTTP_CODE'));
- }
- }
- catch (\RuntimeException $e)
- {
- $data['force_ssl'] = 0;
-
- // Also update the user state
- $app->setUserState('com_config.config.global.data.force_ssl', 0);
-
- // Inform the user
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE', $e->getMessage()), 'warning');
- }
- }
-
- // Save the rules
- if (isset($data['rules']))
- {
- $rules = new Rules($data['rules']);
-
- // Check that we aren't removing our Super User permission
- // Need to get groups from database, since they might have changed
- $myGroups = Access::getGroupsByUser(Factory::getUser()->get('id'));
- $myRules = $rules->getData();
- $hasSuperAdmin = $myRules['core.admin']->allow($myGroups);
-
- if (!$hasSuperAdmin)
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_REMOVING_SUPER_ADMIN'), 'error');
-
- return false;
- }
-
- $asset = Table::getInstance('asset');
-
- if ($asset->loadByName('root.1'))
- {
- $asset->rules = (string) $rules;
-
- if (!$asset->check() || !$asset->store())
- {
- $app->enqueueMessage($asset->getError(), 'error');
-
- return false;
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_ROOT_ASSET_NOT_FOUND'), 'error');
-
- return false;
- }
-
- unset($data['rules']);
- }
-
- // Save the text filters
- if (isset($data['filters']))
- {
- $registry = new Registry(array('filters' => $data['filters']));
-
- $extension = Table::getInstance('extension');
-
- // Get extension_id
- $extensionId = $extension->find(array('name' => 'com_config'));
-
- if ($extension->load((int) $extensionId))
- {
- $extension->params = (string) $registry;
-
- if (!$extension->check() || !$extension->store())
- {
- $app->enqueueMessage($extension->getError(), 'error');
-
- return false;
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIG_EXTENSION_NOT_FOUND'), 'error');
-
- return false;
- }
-
- unset($data['filters']);
- }
-
- // Get the previous configuration.
- $prev = new \JConfig;
- $prev = ArrayHelper::fromObject($prev);
-
- // Merge the new data in. We do this to preserve values that were not in the form.
- $data = array_merge($prev, $data);
-
- /*
- * Perform miscellaneous options based on configuration settings/changes.
- */
-
- // Escape the offline message if present.
- if (isset($data['offline_message']))
- {
- $data['offline_message'] = OutputFilter::ampReplace($data['offline_message']);
- }
-
- // Purge the database session table if we are changing to the database handler.
- if ($prev['session_handler'] != 'database' && $data['session_handler'] == 'database')
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__session'))
- ->where($db->quoteName('time') . ' < ' . (time() - 1));
- $db->setQuery($query);
- $db->execute();
- }
-
- // Purge the database session table if we are disabling session metadata
- if ($prev['session_metadata'] == 1 && $data['session_metadata'] == 0)
- {
- try
- {
- // If we are are using the session handler, purge the extra columns, otherwise truncate the whole session table
- if ($data['session_handler'] === 'database')
- {
- $revisedDbo->setQuery(
- $revisedDbo->getQuery(true)
- ->update('#__session')
- ->set(
- [
- $revisedDbo->quoteName('client_id') . ' = 0',
- $revisedDbo->quoteName('guest') . ' = NULL',
- $revisedDbo->quoteName('userid') . ' = NULL',
- $revisedDbo->quoteName('username') . ' = NULL',
- ]
- )
- )->execute();
- }
- else
- {
- $revisedDbo->truncateTable('#__session');
- }
- }
- catch (\RuntimeException $e)
- {
- /*
- * The database API logs errors on failures so we don't need to add any error handling mechanisms here.
- * Also, this data won't be added or checked anymore once the configuration is saved, so it'll purge itself
- * through normal garbage collection anyway or if not using the database handler someone can purge the
- * table on their own. Either way, carry on Soldier!
- */
- }
- }
-
- // Ensure custom session file path exists or try to create it if changed
- if (!empty($data['session_filesystem_path']))
- {
- $currentPath = $prev['session_filesystem_path'] ?? null;
-
- if ($currentPath)
- {
- $currentPath = Path::clean($currentPath);
- }
-
- $data['session_filesystem_path'] = Path::clean($data['session_filesystem_path']);
-
- if ($currentPath !== $data['session_filesystem_path'])
- {
- if (!Folder::exists($data['session_filesystem_path']) && !Folder::create($data['session_filesystem_path']))
- {
- try
- {
- Log::add(
- Text::sprintf(
- 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT',
- $data['session_filesystem_path']
- ),
- Log::WARNING,
- 'jerror'
- );
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(
- Text::sprintf(
- 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT',
- $data['session_filesystem_path']
- ),
- 'warning'
- );
- }
-
- $data['session_filesystem_path'] = $currentPath;
- }
- }
- }
-
- // Set the shared session configuration
- if (isset($data['shared_session']))
- {
- $currentShared = $prev['shared_session'] ?? '0';
-
- // Has the user enabled shared sessions?
- if ($data['shared_session'] == 1 && $currentShared == 0)
- {
- // Generate a random shared session name
- $data['session_name'] = UserHelper::genRandomPassword(16);
- }
-
- // Has the user disabled shared sessions?
- if ($data['shared_session'] == 0 && $currentShared == 1)
- {
- // Remove the session name value
- unset($data['session_name']);
- }
- }
-
- // Set the shared session configuration
- if (isset($data['shared_session']))
- {
- $currentShared = $prev['shared_session'] ?? '0';
-
- // Has the user enabled shared sessions?
- if ($data['shared_session'] == 1 && $currentShared == 0)
- {
- // Generate a random shared session name
- $data['session_name'] = UserHelper::genRandomPassword(16);
- }
-
- // Has the user disabled shared sessions?
- if ($data['shared_session'] == 0 && $currentShared == 1)
- {
- // Remove the session name value
- unset($data['session_name']);
- }
- }
-
- if (empty($data['cache_handler']))
- {
- $data['caching'] = 0;
- }
-
- /*
- * Look for a custom cache_path
- * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
- */
- if (!empty($data['cache_path']))
- {
- $path = $data['cache_path'];
- }
- elseif (!empty($prev['cache_path']))
- {
- $path = $prev['cache_path'];
- }
- else
- {
- $path = JPATH_CACHE;
- }
-
- // Give a warning if the cache-folder can not be opened
- if ($data['caching'] > 0 && $data['cache_handler'] == 'file' && @opendir($path) == false)
- {
- $error = true;
-
- // If a custom path is in use, try using the system default instead of disabling cache
- if ($path !== JPATH_CACHE && @opendir(JPATH_CACHE) != false)
- {
- try
- {
- Log::add(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE),
- Log::WARNING,
- 'jerror'
- );
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE),
- 'warning'
- );
- }
-
- $path = JPATH_CACHE;
- $error = false;
-
- $data['cache_path'] = '';
- }
-
- if ($error)
- {
- try
- {
- Log::add(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), 'warning');
- }
-
- $data['caching'] = 0;
- }
- }
-
- // Did the user remove their custom cache path? Don't save the variable to the config
- if (empty($data['cache_path']))
- {
- unset($data['cache_path']);
- }
-
- // Clean the cache if disabled but previously enabled or changing cache handlers; these operations use the `$prev` data already in memory
- if ((!$data['caching'] && $prev['caching']) || $data['cache_handler'] !== $prev['cache_handler'])
- {
- try
- {
- Factory::getCache()->clean();
- }
- catch (CacheConnectingException $exception)
- {
- try
- {
- Log::add(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), 'warning');
- }
- }
- catch (UnsupportedCacheException $exception)
- {
- try
- {
- Log::add(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), 'warning');
- }
- }
- }
-
- /*
- * Look for a custom tmp_path
- * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
- */
- $defaultTmpPath = JPATH_ROOT . '/tmp';
-
- if (!empty($data['tmp_path']))
- {
- $path = $data['tmp_path'];
- }
- elseif (!empty($prev['tmp_path']))
- {
- $path = $prev['tmp_path'];
- }
- else
- {
- $path = $defaultTmpPath;
- }
-
- $path = Path::clean($path);
-
- // Give a warning if the tmp-folder is not valid or not writable
- if (!is_dir($path) || !is_writable($path))
- {
- $error = true;
-
- // If a custom path is in use, try using the system default tmp path
- if ($path !== $defaultTmpPath && is_dir($defaultTmpPath) && is_writable($defaultTmpPath))
- {
- try
- {
- Log::add(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath),
- Log::WARNING,
- 'jerror'
- );
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath),
- 'warning'
- );
- }
-
- $error = false;
-
- $data['tmp_path'] = $defaultTmpPath;
- }
-
- if ($error)
- {
- try
- {
- Log::add(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), 'warning');
- }
- }
- }
-
- /*
- * Look for a custom log_path
- * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
- */
- $defaultLogPath = JPATH_ADMINISTRATOR . '/logs';
-
- if (!empty($data['log_path']))
- {
- $path = $data['log_path'];
- }
- elseif (!empty($prev['log_path']))
- {
- $path = $prev['log_path'];
- }
- else
- {
- $path = $defaultLogPath;
- }
-
- $path = Path::clean($path);
-
- // Give a warning if the log-folder is not valid or not writable
- if (!is_dir($path) || !is_writable($path))
- {
- $error = true;
-
- // If a custom path is in use, try using the system default log path
- if ($path !== $defaultLogPath && is_dir($defaultLogPath) && is_writable($defaultLogPath))
- {
- try
- {
- Log::add(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath),
- Log::WARNING,
- 'jerror'
- );
- }
- catch (\RuntimeException $logException)
- {
- $app->enqueueMessage(
- Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath),
- 'warning'
- );
- }
-
- $error = false;
- $data['log_path'] = $defaultLogPath;
- }
-
- if ($error)
- {
- try
- {
- Log::add(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), 'warning');
- }
- }
- }
-
- // Create the new configuration object.
- $config = new Registry($data);
-
- // Overwrite webservices cors settings
- $app->set('cors', $data['cors']);
- $app->set('cors_allow_origin', $data['cors_allow_origin']);
- $app->set('cors_allow_headers', $data['cors_allow_headers']);
- $app->set('cors_allow_methods', $data['cors_allow_methods']);
-
- // Clear cache of com_config component.
- $this->cleanCache('_system');
-
- $result = $app->triggerEvent('onApplicationBeforeSave', array($config));
-
- // Store the data.
- if (in_array(false, $result, true))
- {
- throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING'));
- }
-
- // Write the configuration file.
- $result = $this->writeConfigFile($config);
-
- // Trigger the after save event.
- $app->triggerEvent('onApplicationAfterSave', array($config));
-
- return $result;
- }
-
- /**
- * Method to unset the root_user value from configuration data.
- *
- * This method will load the global configuration data straight from
- * JConfig and remove the root_user value for security, then save the configuration.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 1.6
- */
- public function removeroot()
- {
- $app = Factory::getApplication();
-
- // Get the previous configuration.
- $prev = new \JConfig;
- $prev = ArrayHelper::fromObject($prev);
-
- // Create the new configuration object, and unset the root_user property
- unset($prev['root_user']);
- $config = new Registry($prev);
-
- $result = $app->triggerEvent('onApplicationBeforeSave', array($config));
-
- // Store the data.
- if (in_array(false, $result, true))
- {
- throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING'));
- }
-
- // Write the configuration file.
- $result = $this->writeConfigFile($config);
-
- // Trigger the after save event.
- $app->triggerEvent('onApplicationAfterSave', array($config));
-
- return $result;
- }
-
- /**
- * Method to write the configuration to a file.
- *
- * @param Registry $config A Registry object containing all global config data.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 2.5.4
- * @throws \RuntimeException
- */
- private function writeConfigFile(Registry $config)
- {
- // Set the configuration file path.
- $file = JPATH_CONFIGURATION . '/configuration.php';
-
- $app = Factory::getApplication();
-
- // Attempt to make the file writeable.
- if (Path::isOwner($file) && !Path::setPermissions($file, '0644'))
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice');
- }
-
- // Attempt to write the configuration file as a PHP class named JConfig.
- $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false));
-
- if (!File::write($file, $configuration))
- {
- throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_WRITE_FAILED'));
- }
-
- // Attempt to make the file unwriteable.
- if (Path::isOwner($file) && !Path::setPermissions($file, '0444'))
- {
- $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice');
- }
-
- return true;
- }
-
- /**
- * Method to store the permission values in the asset table.
- *
- * This method will get an array with permission key value pairs and transform it
- * into json and update the asset table in the database.
- *
- * @param string $permission Need an array with Permissions (component, rule, value and title)
- *
- * @return array|bool A list of result data or false on failure.
- *
- * @since 3.5
- */
- public function storePermissions($permission = null)
- {
- $app = Factory::getApplication();
- $user = Factory::getUser();
-
- if (is_null($permission))
- {
- // Get data from input.
- $permission = array(
- 'component' => $app->input->Json->get('comp'),
- 'action' => $app->input->Json->get('action'),
- 'rule' => $app->input->Json->get('rule'),
- 'value' => $app->input->Json->get('value'),
- 'title' => $app->input->Json->get('title', '', 'RAW')
- );
- }
-
- // We are creating a new item so we don't have an item id so don't allow.
- if (substr($permission['component'], -6) === '.false')
- {
- $app->enqueueMessage(Text::_('JLIB_RULES_SAVE_BEFORE_CHANGE_PERMISSIONS'), 'error');
-
- return false;
- }
-
- // Check if the user is authorized to do this.
- if (!$user->authorise('core.admin', $permission['component']))
- {
- $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
-
- return false;
- }
-
- $permission['component'] = empty($permission['component']) ? 'root.1' : $permission['component'];
-
- // Current view is global config?
- $isGlobalConfig = $permission['component'] === 'root.1';
-
- // Check if changed group has Super User permissions.
- $isSuperUserGroupBefore = Access::checkGroup($permission['rule'], 'core.admin');
-
- // Check if current user belongs to changed group.
- $currentUserBelongsToGroup = in_array((int) $permission['rule'], $user->groups) ? true : false;
-
- // Get current user groups tree.
- $currentUserGroupsTree = Access::getGroupsByUser($user->id, true);
-
- // Check if current user belongs to changed group.
- $currentUserSuperUser = $user->authorise('core.admin');
-
- // If user is not Super User cannot change the permissions of a group it belongs to.
- if (!$currentUserSuperUser && $currentUserBelongsToGroup)
- {
- $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_GROUPS'), 'error');
-
- return false;
- }
-
- // If user is not Super User cannot change the permissions of a group it belongs to.
- if (!$currentUserSuperUser && in_array((int) $permission['rule'], $currentUserGroupsTree))
- {
- $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_PARENT_GROUPS'), 'error');
-
- return false;
- }
-
- // If user is not Super User cannot change the permissions of a Super User Group.
- if (!$currentUserSuperUser && $isSuperUserGroupBefore && !$currentUserBelongsToGroup)
- {
- $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_SUPER_USER'), 'error');
-
- return false;
- }
-
- // If user is not Super User cannot change the Super User permissions in any group it belongs to.
- if ($isSuperUserGroupBefore && $currentUserBelongsToGroup && $permission['action'] === 'core.admin')
- {
- $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'), 'error');
-
- return false;
- }
-
- try
- {
- /** @var Asset $asset */
- $asset = Table::getInstance('asset');
- $result = $asset->loadByName($permission['component']);
-
- if ($result === false)
- {
- $data = array($permission['action'] => array($permission['rule'] => $permission['value']));
-
- $rules = new Rules($data);
- $asset->rules = (string) $rules;
- $asset->name = (string) $permission['component'];
- $asset->title = (string) $permission['title'];
-
- // Get the parent asset id so we have a correct tree.
- /** @var Asset $parentAsset */
- $parentAsset = Table::getInstance('Asset');
-
- if (strpos($asset->name, '.') !== false)
- {
- $assetParts = explode('.', $asset->name);
- $parentAsset->loadByName($assetParts[0]);
- $parentAssetId = $parentAsset->id;
- }
- else
- {
- $parentAssetId = $parentAsset->getRootId();
- }
-
- /**
- * @todo: incorrect ACL stored
- * When changing a permission of an item that doesn't have a row in the asset table the row a new row is created.
- * This works fine for item <-> component <-> global config scenario and component <-> global config scenario.
- * But doesn't work properly for item <-> section(s) <-> component <-> global config scenario,
- * because a wrong parent asset id (the component) is stored.
- * Happens when there is no row in the asset table (ex: deleted or not created on update).
- */
-
- $asset->setLocation($parentAssetId, 'last-child');
- }
- else
- {
- // Decode the rule settings.
- $temp = json_decode($asset->rules, true);
-
- // Check if a new value is to be set.
- if (isset($permission['value']))
- {
- // Check if we already have an action entry.
- if (!isset($temp[$permission['action']]))
- {
- $temp[$permission['action']] = array();
- }
-
- // Check if we already have a rule entry.
- if (!isset($temp[$permission['action']][$permission['rule']]))
- {
- $temp[$permission['action']][$permission['rule']] = array();
- }
-
- // Set the new permission.
- $temp[$permission['action']][$permission['rule']] = (int) $permission['value'];
-
- // Check if we have an inherited setting.
- if ($permission['value'] === '')
- {
- unset($temp[$permission['action']][$permission['rule']]);
- }
-
- // Check if we have any rules.
- if (!$temp[$permission['action']])
- {
- unset($temp[$permission['action']]);
- }
- }
- else
- {
- // There is no value so remove the action as it's not needed.
- unset($temp[$permission['action']]);
- }
-
- $asset->rules = json_encode($temp, JSON_FORCE_OBJECT);
- }
-
- if (!$asset->check() || !$asset->store())
- {
- $app->enqueueMessage(Text::_('JLIB_UNKNOWN'), 'error');
-
- return false;
- }
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- // All checks done.
- $result = array(
- 'text' => '',
- 'class' => '',
- 'result' => true,
- );
-
- // Show the current effective calculated permission considering current group, path and cascade.
-
- try
- {
- // The database instance
- $db = $this->getDatabase();
-
- // Get the asset id by the name of the component.
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('name') . ' = :component')
- ->bind(':component', $permission['component']);
-
- $db->setQuery($query);
-
- $assetId = (int) $db->loadResult();
-
- // Fetch the parent asset id.
- $parentAssetId = null;
-
- /**
- * @todo: incorrect info
- * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config).
- * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct.
- * Also, currently it uses the component permission, but should use the calculated permissions for a child of the component/section.
- */
-
- // If not in global config we need the parent_id asset to calculate permissions.
- if (!$isGlobalConfig)
- {
- // In this case we need to get the component rules too.
- $query->clear()
- ->select($db->quoteName('parent_id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('id') . ' = :assetid')
- ->bind(':assetid', $assetId, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $parentAssetId = (int) $db->loadResult();
- }
-
- // Get the group parent id of the current group.
- $rule = (int) $permission['rule'];
- $query->clear()
- ->select($db->quoteName('parent_id'))
- ->from($db->quoteName('#__usergroups'))
- ->where($db->quoteName('id') . ' = :rule')
- ->bind(':rule', $rule, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $parentGroupId = (int) $db->loadResult();
-
- // Count the number of child groups of the current group.
- $query->clear()
- ->select('COUNT(' . $db->quoteName('id') . ')')
- ->from($db->quoteName('#__usergroups'))
- ->where($db->quoteName('parent_id') . ' = :rule')
- ->bind(':rule', $rule, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $totalChildGroups = (int) $db->loadResult();
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- // Clear access statistics.
- Access::clearStatics();
-
- // After current group permission is changed we need to check again if the group has Super User permissions.
- $isSuperUserGroupAfter = Access::checkGroup($permission['rule'], 'core.admin');
-
- // Get the rule for just this asset (non-recursive) and get the actual setting for the action for this group.
- $assetRule = Access::getAssetRules($assetId, false, false)->allow($permission['action'], $permission['rule']);
-
- // Get the group, group parent id, and group global config recursive calculated permission for the chosen action.
- $inheritedGroupRule = Access::checkGroup($permission['rule'], $permission['action'], $assetId);
-
- if (!empty($parentAssetId))
- {
- $inheritedGroupParentAssetRule = Access::checkGroup($permission['rule'], $permission['action'], $parentAssetId);
- }
- else
- {
- $inheritedGroupParentAssetRule = null;
- }
-
- $inheritedParentGroupRule = !empty($parentGroupId) ? Access::checkGroup($parentGroupId, $permission['action'], $assetId) : null;
-
- // Current group is a Super User group, so calculated setting is "Allowed (Super User)".
- if ($isSuperUserGroupAfter)
- {
- $result['class'] = 'badge bg-success';
- $result['text'] = ' ' . Text::_('JLIB_RULES_ALLOWED_ADMIN');
- }
- // Not super user.
- else
- {
- // First get the real recursive calculated setting and add (Inherited) to it.
-
- // If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)".
- if ($inheritedGroupRule === null || $inheritedGroupRule === false)
- {
- $result['class'] = 'badge bg-danger';
- $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_INHERITED');
- }
- // If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)".
- else
- {
- $result['class'] = 'badge bg-success';
- $result['text'] = Text::_('JLIB_RULES_ALLOWED_INHERITED');
- }
-
- // Second part: Overwrite the calculated permissions labels if there is an explicit permission in the current group.
-
- /**
- * @todo: incorrect info
- * If a component has a permission that doesn't exists in global config (ex: frontend editing in com_modules) by default
- * we get "Not Allowed (Inherited)" when we should get "Not Allowed (Default)".
- */
-
- // If there is an explicit permission "Not Allowed". Calculated permission is "Not Allowed".
- if ($assetRule === false)
- {
- $result['class'] = 'badge bg-danger';
- $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED');
- }
- // If there is an explicit permission is "Allowed". Calculated permission is "Allowed".
- elseif ($assetRule === true)
- {
- $result['class'] = 'badge bg-success';
- $result['text'] = Text::_('JLIB_RULES_ALLOWED');
- }
-
- // Third part: Overwrite the calculated permissions labels for special cases.
-
- // Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)".
- if (empty($parentGroupId) && $isGlobalConfig === true && $assetRule === null)
- {
- $result['class'] = 'badge bg-danger';
- $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_DEFAULT');
- }
-
- /**
- * Component/Item with explicit "Denied" permission at parent Asset (Category, Component or Global config) configuration.
- * Or some parent group has an explicit "Denied".
- * Calculated permission is "Not Allowed (Locked)".
- */
- elseif ($inheritedGroupParentAssetRule === false || $inheritedParentGroupRule === false)
- {
- $result['class'] = 'badge bg-danger';
- $result['text'] = ' ' . Text::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
- }
- }
-
- // If removed or added super user from group, we need to refresh the page to recalculate all settings.
- if ($isSuperUserGroupBefore != $isSuperUserGroupAfter)
- {
- $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_PERMISSIONS'), 'notice');
- }
-
- // If this group has child groups, we need to refresh the page to recalculate the child settings.
- if ($totalChildGroups > 0)
- {
- $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_CHILDS_PERMISSIONS'), 'notice');
- }
-
- return $result;
- }
-
- /**
- * Method to send a test mail which is called via an AJAX request
- *
- * @return boolean
- *
- * @since 3.5
- */
- public function sendTestMail()
- {
- // Set the new values to test with the current settings
- $app = Factory::getApplication();
- $user = Factory::getUser();
- $input = $app->input->json;
- $smtppass = $input->get('smtppass', null, 'RAW');
-
- $app->set('smtpauth', $input->get('smtpauth'));
- $app->set('smtpuser', $input->get('smtpuser', '', 'STRING'));
- $app->set('smtphost', $input->get('smtphost'));
- $app->set('smtpsecure', $input->get('smtpsecure'));
- $app->set('smtpport', $input->get('smtpport'));
- $app->set('mailfrom', $input->get('mailfrom', '', 'STRING'));
- $app->set('fromname', $input->get('fromname', '', 'STRING'));
- $app->set('mailer', $input->get('mailer'));
- $app->set('mailonline', $input->get('mailonline'));
-
- // Use smtppass only if it was submitted
- if ($smtppass !== null)
- {
- $app->set('smtppass', $smtppass);
- }
-
- $mail = Factory::getMailer();
-
- // Prepare email and try to send it
- $mailer = new MailTemplate('com_config.test_mail', $user->getParam('language', $app->get('language')), $mail);
- $mailer->addTemplateData(
- array(
- 'sitename' => $app->get('sitename'),
- 'method' => Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer))
- )
- );
- $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname'));
-
- try
- {
- $mailSent = $mailer->send();
- }
- catch (MailDisabledException | phpMailerException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- if ($mailSent === true)
- {
- $methodName = Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer));
-
- // If JMail send the mail using PHP Mail as fallback.
- if ($mail->Mailer !== $app->get('mailer'))
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS_FALLBACK', $app->get('mailfrom'), $methodName), 'warning');
- }
- else
- {
- $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS', $app->get('mailfrom'), $methodName), 'message');
- }
-
- return true;
- }
-
- $app->enqueueMessage(Text::_('COM_CONFIG_SENDMAIL_ERROR'), 'error');
-
- return false;
- }
+ /**
+ * Array of protected password fields from the configuration.php
+ *
+ * @var array
+ * @since 3.9.23
+ */
+ private $protectedConfigurationFields = array('password', 'secret', 'smtppass', 'redis_server_auth', 'session_redis_server_auth');
+
+ /**
+ * Method to get a form object.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return mixed A JForm object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_config.application', 'application', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the configuration data.
+ *
+ * This method will load the global configuration data straight from
+ * JConfig. If configuration data has been saved in the session, that
+ * data will be merged into the original data, overwriting it.
+ *
+ * @return array An array containing all global config data.
+ *
+ * @since 1.6
+ */
+ public function getData()
+ {
+ // Get the config data.
+ $config = new \JConfig();
+ $data = ArrayHelper::fromObject($config);
+
+ // Get the correct driver at runtime
+ $data['dbtype'] = $this->getDatabase()->getName();
+
+ // Prime the asset_id for the rules.
+ $data['asset_id'] = 1;
+
+ // Get the text filter data
+ $params = ComponentHelper::getParams('com_config');
+ $data['filters'] = ArrayHelper::fromObject($params->get('filters'));
+
+ // If no filter data found, get from com_content (update of 1.6/1.7 site)
+ if (empty($data['filters'])) {
+ $contentParams = ComponentHelper::getParams('com_content');
+ $data['filters'] = ArrayHelper::fromObject($contentParams->get('filters'));
+ }
+
+ // Check for data in the session.
+ $temp = Factory::getApplication()->getUserState('com_config.config.global.data');
+
+ // Merge in the session data.
+ if (!empty($temp)) {
+ // $temp can sometimes be an object, and we need it to be an array
+ if (is_object($temp)) {
+ $temp = ArrayHelper::fromObject($temp);
+ }
+
+ $data = array_merge($temp, $data);
+ }
+
+ // Correct error_reporting value, since we removed "development", the "maximum" should be set instead
+ // @TODO: This can be removed in 5.0
+ if (!empty($data['error_reporting']) && $data['error_reporting'] === 'development') {
+ $data['error_reporting'] = 'maximum';
+ }
+
+ return $data;
+ }
+
+ /**
+ * Method to validate the db connection properties.
+ *
+ * @param array $data An array containing all global config data.
+ *
+ * @return array|boolean Array with the validated global config data or boolean false on a validation failure.
+ *
+ * @since 4.0.0
+ */
+ public function validateDbConnection($data)
+ {
+ // Validate database connection encryption options
+ if ((int) $data['dbencryption'] === 0) {
+ // Reset unused options
+ if (!empty($data['dbsslkey'])) {
+ $data['dbsslkey'] = '';
+ }
+
+ if (!empty($data['dbsslcert'])) {
+ $data['dbsslcert'] = '';
+ }
+
+ if ((bool) $data['dbsslverifyservercert'] === true) {
+ $data['dbsslverifyservercert'] = false;
+ }
+
+ if (!empty($data['dbsslca'])) {
+ $data['dbsslca'] = '';
+ }
+
+ if (!empty($data['dbsslcipher'])) {
+ $data['dbsslcipher'] = '';
+ }
+ } else {
+ // Check localhost
+ if (strtolower($data['host']) === 'localhost') {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_LOCALHOST'), 'error');
+
+ return false;
+ }
+
+ // Check CA file and folder depending on database type if server certificate verification
+ if ((bool) $data['dbsslverifyservercert'] === true) {
+ if (empty($data['dbsslca'])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+
+ if (!File::exists(Path::clean($data['dbsslca']))) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CA_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+ } else {
+ // Reset unused option
+ if (!empty($data['dbsslca'])) {
+ $data['dbsslca'] = '';
+ }
+ }
+
+ // Check key and certificate if two-way encryption
+ if ((int) $data['dbencryption'] === 2) {
+ if (empty($data['dbsslkey'])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+
+ if (!File::exists(Path::clean($data['dbsslkey']))) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_KEY_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+
+ if (empty($data['dbsslcert'])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_EMPTY',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+
+ if (!File::exists(Path::clean($data['dbsslcert']))) {
+ Factory::getApplication()->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_DATABASE_ENCRYPTION_FILE_FIELD_BAD',
+ Text::_('COM_CONFIG_FIELD_DATABASE_ENCRYPTION_CERT_LABEL')
+ ),
+ 'error'
+ );
+
+ return false;
+ }
+ } else {
+ // Reset unused options
+ if (!empty($data['dbsslkey'])) {
+ $data['dbsslkey'] = '';
+ }
+
+ if (!empty($data['dbsslcert'])) {
+ $data['dbsslcert'] = '';
+ }
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Method to save the configuration data.
+ *
+ * @param array $data An array containing all global config data.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $app = Factory::getApplication();
+
+ // Try to load the values from the configuration file
+ foreach ($this->protectedConfigurationFields as $fieldKey) {
+ if (!isset($data[$fieldKey])) {
+ $data[$fieldKey] = $app->get($fieldKey, '');
+ }
+ }
+
+ // Check that we aren't setting wrong database configuration
+ $options = array(
+ 'driver' => $data['dbtype'],
+ 'host' => $data['host'],
+ 'user' => $data['user'],
+ 'password' => $data['password'],
+ 'database' => $data['db'],
+ 'prefix' => $data['dbprefix'],
+ );
+
+ if ((int) $data['dbencryption'] !== 0) {
+ $options['ssl'] = [
+ 'enable' => true,
+ 'verify_server_cert' => (bool) $data['dbsslverifyservercert'],
+ ];
+
+ foreach (['cipher', 'ca', 'key', 'cert'] as $value) {
+ $confVal = trim($data['dbssl' . $value]);
+
+ if ($confVal !== '') {
+ $options['ssl'][$value] = $confVal;
+ }
+ }
+ }
+
+ try {
+ $revisedDbo = DatabaseDriver::getInstance($options);
+ $revisedDbo->getVersion();
+ } catch (\Exception $e) {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_DATABASE_NOT_AVAILABLE', $e->getCode(), $e->getMessage()), 'error');
+
+ return false;
+ }
+
+ if ((int) $data['dbencryption'] !== 0 && empty($revisedDbo->getConnectionEncryption())) {
+ if ($revisedDbo->isConnectionEncryptionSupported()) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'), 'error');
+ } else {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'), 'error');
+ }
+
+ return false;
+ }
+
+ // Check if we can set the Force SSL option
+ if ((int) $data['force_ssl'] !== 0 && (int) $data['force_ssl'] !== (int) $app->get('force_ssl', '0')) {
+ try {
+ // Make an HTTPS request to check if the site is available in HTTPS.
+ $host = Uri::getInstance()->getHost();
+ $options = new Registry();
+ $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
+
+ // Do not check for valid server certificate here, leave this to the user, moreover disable using a proxy if any is configured.
+ $options->set(
+ 'transport.curl',
+ array(
+ CURLOPT_SSL_VERIFYPEER => false,
+ CURLOPT_SSL_VERIFYHOST => false,
+ CURLOPT_PROXY => null,
+ CURLOPT_PROXYUSERPWD => null,
+ )
+ );
+ $response = HttpFactory::getHttp($options)->get('https://' . $host . Uri::root(true) . '/', array('Host' => $host), 10);
+
+ // If available in HTTPS check also the status code.
+ if (!in_array($response->code, array(200, 503, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 401), true)) {
+ throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE_HTTP_CODE'));
+ }
+ } catch (\RuntimeException $e) {
+ $data['force_ssl'] = 0;
+
+ // Also update the user state
+ $app->setUserState('com_config.config.global.data.force_ssl', 0);
+
+ // Inform the user
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_SSL_NOT_AVAILABLE', $e->getMessage()), 'warning');
+ }
+ }
+
+ // Save the rules
+ if (isset($data['rules'])) {
+ $rules = new Rules($data['rules']);
+
+ // Check that we aren't removing our Super User permission
+ // Need to get groups from database, since they might have changed
+ $myGroups = Access::getGroupsByUser(Factory::getUser()->get('id'));
+ $myRules = $rules->getData();
+ $hasSuperAdmin = $myRules['core.admin']->allow($myGroups);
+
+ if (!$hasSuperAdmin) {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_REMOVING_SUPER_ADMIN'), 'error');
+
+ return false;
+ }
+
+ $asset = Table::getInstance('asset');
+
+ if ($asset->loadByName('root.1')) {
+ $asset->rules = (string) $rules;
+
+ if (!$asset->check() || !$asset->store()) {
+ $app->enqueueMessage($asset->getError(), 'error');
+
+ return false;
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_ROOT_ASSET_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ unset($data['rules']);
+ }
+
+ // Save the text filters
+ if (isset($data['filters'])) {
+ $registry = new Registry(array('filters' => $data['filters']));
+
+ $extension = Table::getInstance('extension');
+
+ // Get extension_id
+ $extensionId = $extension->find(array('name' => 'com_config'));
+
+ if ($extension->load((int) $extensionId)) {
+ $extension->params = (string) $registry;
+
+ if (!$extension->check() || !$extension->store()) {
+ $app->enqueueMessage($extension->getError(), 'error');
+
+ return false;
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIG_EXTENSION_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ unset($data['filters']);
+ }
+
+ // Get the previous configuration.
+ $prev = new \JConfig();
+ $prev = ArrayHelper::fromObject($prev);
+
+ // Merge the new data in. We do this to preserve values that were not in the form.
+ $data = array_merge($prev, $data);
+
+ /*
+ * Perform miscellaneous options based on configuration settings/changes.
+ */
+
+ // Escape the offline message if present.
+ if (isset($data['offline_message'])) {
+ $data['offline_message'] = OutputFilter::ampReplace($data['offline_message']);
+ }
+
+ // Purge the database session table if we are changing to the database handler.
+ if ($prev['session_handler'] != 'database' && $data['session_handler'] == 'database') {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__session'))
+ ->where($db->quoteName('time') . ' < ' . (time() - 1));
+ $db->setQuery($query);
+ $db->execute();
+ }
+
+ // Purge the database session table if we are disabling session metadata
+ if ($prev['session_metadata'] == 1 && $data['session_metadata'] == 0) {
+ try {
+ // If we are are using the session handler, purge the extra columns, otherwise truncate the whole session table
+ if ($data['session_handler'] === 'database') {
+ $revisedDbo->setQuery(
+ $revisedDbo->getQuery(true)
+ ->update('#__session')
+ ->set(
+ [
+ $revisedDbo->quoteName('client_id') . ' = 0',
+ $revisedDbo->quoteName('guest') . ' = NULL',
+ $revisedDbo->quoteName('userid') . ' = NULL',
+ $revisedDbo->quoteName('username') . ' = NULL',
+ ]
+ )
+ )->execute();
+ } else {
+ $revisedDbo->truncateTable('#__session');
+ }
+ } catch (\RuntimeException $e) {
+ /*
+ * The database API logs errors on failures so we don't need to add any error handling mechanisms here.
+ * Also, this data won't be added or checked anymore once the configuration is saved, so it'll purge itself
+ * through normal garbage collection anyway or if not using the database handler someone can purge the
+ * table on their own. Either way, carry on Soldier!
+ */
+ }
+ }
+
+ // Ensure custom session file path exists or try to create it if changed
+ if (!empty($data['session_filesystem_path'])) {
+ $currentPath = $prev['session_filesystem_path'] ?? null;
+
+ if ($currentPath) {
+ $currentPath = Path::clean($currentPath);
+ }
+
+ $data['session_filesystem_path'] = Path::clean($data['session_filesystem_path']);
+
+ if ($currentPath !== $data['session_filesystem_path']) {
+ if (!Folder::exists($data['session_filesystem_path']) && !Folder::create($data['session_filesystem_path'])) {
+ try {
+ Log::add(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT',
+ $data['session_filesystem_path']
+ ),
+ Log::WARNING,
+ 'jerror'
+ );
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(
+ Text::sprintf(
+ 'COM_CONFIG_ERROR_CUSTOM_SESSION_FILESYSTEM_PATH_NOTWRITABLE_USING_DEFAULT',
+ $data['session_filesystem_path']
+ ),
+ 'warning'
+ );
+ }
+
+ $data['session_filesystem_path'] = $currentPath;
+ }
+ }
+ }
+
+ // Set the shared session configuration
+ if (isset($data['shared_session'])) {
+ $currentShared = $prev['shared_session'] ?? '0';
+
+ // Has the user enabled shared sessions?
+ if ($data['shared_session'] == 1 && $currentShared == 0) {
+ // Generate a random shared session name
+ $data['session_name'] = UserHelper::genRandomPassword(16);
+ }
+
+ // Has the user disabled shared sessions?
+ if ($data['shared_session'] == 0 && $currentShared == 1) {
+ // Remove the session name value
+ unset($data['session_name']);
+ }
+ }
+
+ // Set the shared session configuration
+ if (isset($data['shared_session'])) {
+ $currentShared = $prev['shared_session'] ?? '0';
+
+ // Has the user enabled shared sessions?
+ if ($data['shared_session'] == 1 && $currentShared == 0) {
+ // Generate a random shared session name
+ $data['session_name'] = UserHelper::genRandomPassword(16);
+ }
+
+ // Has the user disabled shared sessions?
+ if ($data['shared_session'] == 0 && $currentShared == 1) {
+ // Remove the session name value
+ unset($data['session_name']);
+ }
+ }
+
+ if (empty($data['cache_handler'])) {
+ $data['caching'] = 0;
+ }
+
+ /*
+ * Look for a custom cache_path
+ * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
+ */
+ if (!empty($data['cache_path'])) {
+ $path = $data['cache_path'];
+ } elseif (!empty($prev['cache_path'])) {
+ $path = $prev['cache_path'];
+ } else {
+ $path = JPATH_CACHE;
+ }
+
+ // Give a warning if the cache-folder can not be opened
+ if ($data['caching'] > 0 && $data['cache_handler'] == 'file' && @opendir($path) == false) {
+ $error = true;
+
+ // If a custom path is in use, try using the system default instead of disabling cache
+ if ($path !== JPATH_CACHE && @opendir(JPATH_CACHE) != false) {
+ try {
+ Log::add(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE),
+ Log::WARNING,
+ 'jerror'
+ );
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_CACHE_PATH_NOTWRITABLE_USING_DEFAULT', $path, JPATH_CACHE),
+ 'warning'
+ );
+ }
+
+ $path = JPATH_CACHE;
+ $error = false;
+
+ $data['cache_path'] = '';
+ }
+
+ if ($error) {
+ try {
+ Log::add(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_CACHE_PATH_NOTWRITABLE', $path), 'warning');
+ }
+
+ $data['caching'] = 0;
+ }
+ }
+
+ // Did the user remove their custom cache path? Don't save the variable to the config
+ if (empty($data['cache_path'])) {
+ unset($data['cache_path']);
+ }
+
+ // Clean the cache if disabled but previously enabled or changing cache handlers; these operations use the `$prev` data already in memory
+ if ((!$data['caching'] && $prev['caching']) || $data['cache_handler'] !== $prev['cache_handler']) {
+ try {
+ Factory::getCache()->clean();
+ } catch (CacheConnectingException $exception) {
+ try {
+ Log::add(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_CONNECTION_FAILED'), 'warning');
+ }
+ } catch (UnsupportedCacheException $exception) {
+ try {
+ Log::add(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CACHE_DRIVER_UNSUPPORTED'), 'warning');
+ }
+ }
+ }
+
+ /*
+ * Look for a custom tmp_path
+ * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
+ */
+ $defaultTmpPath = JPATH_ROOT . '/tmp';
+
+ if (!empty($data['tmp_path'])) {
+ $path = $data['tmp_path'];
+ } elseif (!empty($prev['tmp_path'])) {
+ $path = $prev['tmp_path'];
+ } else {
+ $path = $defaultTmpPath;
+ }
+
+ $path = Path::clean($path);
+
+ // Give a warning if the tmp-folder is not valid or not writable
+ if (!is_dir($path) || !is_writable($path)) {
+ $error = true;
+
+ // If a custom path is in use, try using the system default tmp path
+ if ($path !== $defaultTmpPath && is_dir($defaultTmpPath) && is_writable($defaultTmpPath)) {
+ try {
+ Log::add(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath),
+ Log::WARNING,
+ 'jerror'
+ );
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_TEMP_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultTmpPath),
+ 'warning'
+ );
+ }
+
+ $error = false;
+
+ $data['tmp_path'] = $defaultTmpPath;
+ }
+
+ if ($error) {
+ try {
+ Log::add(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_TMP_PATH_NOTWRITABLE', $path), 'warning');
+ }
+ }
+ }
+
+ /*
+ * Look for a custom log_path
+ * First check if a path is given in the submitted data, then check if a path exists in the previous data, otherwise use the default
+ */
+ $defaultLogPath = JPATH_ADMINISTRATOR . '/logs';
+
+ if (!empty($data['log_path'])) {
+ $path = $data['log_path'];
+ } elseif (!empty($prev['log_path'])) {
+ $path = $prev['log_path'];
+ } else {
+ $path = $defaultLogPath;
+ }
+
+ $path = Path::clean($path);
+
+ // Give a warning if the log-folder is not valid or not writable
+ if (!is_dir($path) || !is_writable($path)) {
+ $error = true;
+
+ // If a custom path is in use, try using the system default log path
+ if ($path !== $defaultLogPath && is_dir($defaultLogPath) && is_writable($defaultLogPath)) {
+ try {
+ Log::add(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath),
+ Log::WARNING,
+ 'jerror'
+ );
+ } catch (\RuntimeException $logException) {
+ $app->enqueueMessage(
+ Text::sprintf('COM_CONFIG_ERROR_CUSTOM_LOG_PATH_NOTWRITABLE_USING_DEFAULT', $path, $defaultLogPath),
+ 'warning'
+ );
+ }
+
+ $error = false;
+ $data['log_path'] = $defaultLogPath;
+ }
+
+ if ($error) {
+ try {
+ Log::add(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_ERROR_LOG_PATH_NOTWRITABLE', $path), 'warning');
+ }
+ }
+ }
+
+ // Create the new configuration object.
+ $config = new Registry($data);
+
+ // Overwrite webservices cors settings
+ $app->set('cors', $data['cors']);
+ $app->set('cors_allow_origin', $data['cors_allow_origin']);
+ $app->set('cors_allow_headers', $data['cors_allow_headers']);
+ $app->set('cors_allow_methods', $data['cors_allow_methods']);
+
+ // Clear cache of com_config component.
+ $this->cleanCache('_system');
+
+ $result = $app->triggerEvent('onApplicationBeforeSave', array($config));
+
+ // Store the data.
+ if (in_array(false, $result, true)) {
+ throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING'));
+ }
+
+ // Write the configuration file.
+ $result = $this->writeConfigFile($config);
+
+ // Trigger the after save event.
+ $app->triggerEvent('onApplicationAfterSave', array($config));
+
+ return $result;
+ }
+
+ /**
+ * Method to unset the root_user value from configuration data.
+ *
+ * This method will load the global configuration data straight from
+ * JConfig and remove the root_user value for security, then save the configuration.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function removeroot()
+ {
+ $app = Factory::getApplication();
+
+ // Get the previous configuration.
+ $prev = new \JConfig();
+ $prev = ArrayHelper::fromObject($prev);
+
+ // Create the new configuration object, and unset the root_user property
+ unset($prev['root_user']);
+ $config = new Registry($prev);
+
+ $result = $app->triggerEvent('onApplicationBeforeSave', array($config));
+
+ // Store the data.
+ if (in_array(false, $result, true)) {
+ throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_UNKNOWN_BEFORE_SAVING'));
+ }
+
+ // Write the configuration file.
+ $result = $this->writeConfigFile($config);
+
+ // Trigger the after save event.
+ $app->triggerEvent('onApplicationAfterSave', array($config));
+
+ return $result;
+ }
+
+ /**
+ * Method to write the configuration to a file.
+ *
+ * @param Registry $config A Registry object containing all global config data.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 2.5.4
+ * @throws \RuntimeException
+ */
+ private function writeConfigFile(Registry $config)
+ {
+ // Set the configuration file path.
+ $file = JPATH_CONFIGURATION . '/configuration.php';
+
+ $app = Factory::getApplication();
+
+ // Attempt to make the file writeable.
+ if (Path::isOwner($file) && !Path::setPermissions($file, '0644')) {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTWRITABLE'), 'notice');
+ }
+
+ // Attempt to write the configuration file as a PHP class named JConfig.
+ $configuration = $config->toString('PHP', array('class' => 'JConfig', 'closingtag' => false));
+
+ if (!File::write($file, $configuration)) {
+ throw new \RuntimeException(Text::_('COM_CONFIG_ERROR_WRITE_FAILED'));
+ }
+
+ // Attempt to make the file unwriteable.
+ if (Path::isOwner($file) && !Path::setPermissions($file, '0444')) {
+ $app->enqueueMessage(Text::_('COM_CONFIG_ERROR_CONFIGURATION_PHP_NOTUNWRITABLE'), 'notice');
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to store the permission values in the asset table.
+ *
+ * This method will get an array with permission key value pairs and transform it
+ * into json and update the asset table in the database.
+ *
+ * @param string $permission Need an array with Permissions (component, rule, value and title)
+ *
+ * @return array|bool A list of result data or false on failure.
+ *
+ * @since 3.5
+ */
+ public function storePermissions($permission = null)
+ {
+ $app = Factory::getApplication();
+ $user = Factory::getUser();
+
+ if (is_null($permission)) {
+ // Get data from input.
+ $permission = array(
+ 'component' => $app->input->Json->get('comp'),
+ 'action' => $app->input->Json->get('action'),
+ 'rule' => $app->input->Json->get('rule'),
+ 'value' => $app->input->Json->get('value'),
+ 'title' => $app->input->Json->get('title', '', 'RAW')
+ );
+ }
+
+ // We are creating a new item so we don't have an item id so don't allow.
+ if (substr($permission['component'], -6) === '.false') {
+ $app->enqueueMessage(Text::_('JLIB_RULES_SAVE_BEFORE_CHANGE_PERMISSIONS'), 'error');
+
+ return false;
+ }
+
+ // Check if the user is authorized to do this.
+ if (!$user->authorise('core.admin', $permission['component'])) {
+ $app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+
+ return false;
+ }
+
+ $permission['component'] = empty($permission['component']) ? 'root.1' : $permission['component'];
+
+ // Current view is global config?
+ $isGlobalConfig = $permission['component'] === 'root.1';
+
+ // Check if changed group has Super User permissions.
+ $isSuperUserGroupBefore = Access::checkGroup($permission['rule'], 'core.admin');
+
+ // Check if current user belongs to changed group.
+ $currentUserBelongsToGroup = in_array((int) $permission['rule'], $user->groups) ? true : false;
+
+ // Get current user groups tree.
+ $currentUserGroupsTree = Access::getGroupsByUser($user->id, true);
+
+ // Check if current user belongs to changed group.
+ $currentUserSuperUser = $user->authorise('core.admin');
+
+ // If user is not Super User cannot change the permissions of a group it belongs to.
+ if (!$currentUserSuperUser && $currentUserBelongsToGroup) {
+ $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_GROUPS'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the permissions of a group it belongs to.
+ if (!$currentUserSuperUser && in_array((int) $permission['rule'], $currentUserGroupsTree)) {
+ $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_OWN_PARENT_GROUPS'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the permissions of a Super User Group.
+ if (!$currentUserSuperUser && $isSuperUserGroupBefore && !$currentUserBelongsToGroup) {
+ $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_CHANGE_SUPER_USER'), 'error');
+
+ return false;
+ }
+
+ // If user is not Super User cannot change the Super User permissions in any group it belongs to.
+ if ($isSuperUserGroupBefore && $currentUserBelongsToGroup && $permission['action'] === 'core.admin') {
+ $app->enqueueMessage(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'), 'error');
+
+ return false;
+ }
+
+ try {
+ /** @var Asset $asset */
+ $asset = Table::getInstance('asset');
+ $result = $asset->loadByName($permission['component']);
+
+ if ($result === false) {
+ $data = array($permission['action'] => array($permission['rule'] => $permission['value']));
+
+ $rules = new Rules($data);
+ $asset->rules = (string) $rules;
+ $asset->name = (string) $permission['component'];
+ $asset->title = (string) $permission['title'];
+
+ // Get the parent asset id so we have a correct tree.
+ /** @var Asset $parentAsset */
+ $parentAsset = Table::getInstance('Asset');
+
+ if (strpos($asset->name, '.') !== false) {
+ $assetParts = explode('.', $asset->name);
+ $parentAsset->loadByName($assetParts[0]);
+ $parentAssetId = $parentAsset->id;
+ } else {
+ $parentAssetId = $parentAsset->getRootId();
+ }
+
+ /**
+ * @todo: incorrect ACL stored
+ * When changing a permission of an item that doesn't have a row in the asset table the row a new row is created.
+ * This works fine for item <-> component <-> global config scenario and component <-> global config scenario.
+ * But doesn't work properly for item <-> section(s) <-> component <-> global config scenario,
+ * because a wrong parent asset id (the component) is stored.
+ * Happens when there is no row in the asset table (ex: deleted or not created on update).
+ */
+
+ $asset->setLocation($parentAssetId, 'last-child');
+ } else {
+ // Decode the rule settings.
+ $temp = json_decode($asset->rules, true);
+
+ // Check if a new value is to be set.
+ if (isset($permission['value'])) {
+ // Check if we already have an action entry.
+ if (!isset($temp[$permission['action']])) {
+ $temp[$permission['action']] = array();
+ }
+
+ // Check if we already have a rule entry.
+ if (!isset($temp[$permission['action']][$permission['rule']])) {
+ $temp[$permission['action']][$permission['rule']] = array();
+ }
+
+ // Set the new permission.
+ $temp[$permission['action']][$permission['rule']] = (int) $permission['value'];
+
+ // Check if we have an inherited setting.
+ if ($permission['value'] === '') {
+ unset($temp[$permission['action']][$permission['rule']]);
+ }
+
+ // Check if we have any rules.
+ if (!$temp[$permission['action']]) {
+ unset($temp[$permission['action']]);
+ }
+ } else {
+ // There is no value so remove the action as it's not needed.
+ unset($temp[$permission['action']]);
+ }
+
+ $asset->rules = json_encode($temp, JSON_FORCE_OBJECT);
+ }
+
+ if (!$asset->check() || !$asset->store()) {
+ $app->enqueueMessage(Text::_('JLIB_UNKNOWN'), 'error');
+
+ return false;
+ }
+ } catch (\Exception $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ // All checks done.
+ $result = array(
+ 'text' => '',
+ 'class' => '',
+ 'result' => true,
+ );
+
+ // Show the current effective calculated permission considering current group, path and cascade.
+
+ try {
+ // The database instance
+ $db = $this->getDatabase();
+
+ // Get the asset id by the name of the component.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('name') . ' = :component')
+ ->bind(':component', $permission['component']);
+
+ $db->setQuery($query);
+
+ $assetId = (int) $db->loadResult();
+
+ // Fetch the parent asset id.
+ $parentAssetId = null;
+
+ /**
+ * @todo: incorrect info
+ * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config).
+ * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct.
+ * Also, currently it uses the component permission, but should use the calculated permissions for a child of the component/section.
+ */
+
+ // If not in global config we need the parent_id asset to calculate permissions.
+ if (!$isGlobalConfig) {
+ // In this case we need to get the component rules too.
+ $query->clear()
+ ->select($db->quoteName('parent_id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('id') . ' = :assetid')
+ ->bind(':assetid', $assetId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $parentAssetId = (int) $db->loadResult();
+ }
+
+ // Get the group parent id of the current group.
+ $rule = (int) $permission['rule'];
+ $query->clear()
+ ->select($db->quoteName('parent_id'))
+ ->from($db->quoteName('#__usergroups'))
+ ->where($db->quoteName('id') . ' = :rule')
+ ->bind(':rule', $rule, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $parentGroupId = (int) $db->loadResult();
+
+ // Count the number of child groups of the current group.
+ $query->clear()
+ ->select('COUNT(' . $db->quoteName('id') . ')')
+ ->from($db->quoteName('#__usergroups'))
+ ->where($db->quoteName('parent_id') . ' = :rule')
+ ->bind(':rule', $rule, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $totalChildGroups = (int) $db->loadResult();
+ } catch (\Exception $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ // Clear access statistics.
+ Access::clearStatics();
+
+ // After current group permission is changed we need to check again if the group has Super User permissions.
+ $isSuperUserGroupAfter = Access::checkGroup($permission['rule'], 'core.admin');
+
+ // Get the rule for just this asset (non-recursive) and get the actual setting for the action for this group.
+ $assetRule = Access::getAssetRules($assetId, false, false)->allow($permission['action'], $permission['rule']);
+
+ // Get the group, group parent id, and group global config recursive calculated permission for the chosen action.
+ $inheritedGroupRule = Access::checkGroup($permission['rule'], $permission['action'], $assetId);
+
+ if (!empty($parentAssetId)) {
+ $inheritedGroupParentAssetRule = Access::checkGroup($permission['rule'], $permission['action'], $parentAssetId);
+ } else {
+ $inheritedGroupParentAssetRule = null;
+ }
+
+ $inheritedParentGroupRule = !empty($parentGroupId) ? Access::checkGroup($parentGroupId, $permission['action'], $assetId) : null;
+
+ // Current group is a Super User group, so calculated setting is "Allowed (Super User)".
+ if ($isSuperUserGroupAfter) {
+ $result['class'] = 'badge bg-success';
+ $result['text'] = ' ' . Text::_('JLIB_RULES_ALLOWED_ADMIN');
+ } else {
+ // Not super user.
+ // First get the real recursive calculated setting and add (Inherited) to it.
+
+ // If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)".
+ if ($inheritedGroupRule === null || $inheritedGroupRule === false) {
+ $result['class'] = 'badge bg-danger';
+ $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_INHERITED');
+ } else {
+ // If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)".
+ $result['class'] = 'badge bg-success';
+ $result['text'] = Text::_('JLIB_RULES_ALLOWED_INHERITED');
+ }
+
+ // Second part: Overwrite the calculated permissions labels if there is an explicit permission in the current group.
+
+ /**
+ * @todo: incorrect info
+ * If a component has a permission that doesn't exists in global config (ex: frontend editing in com_modules) by default
+ * we get "Not Allowed (Inherited)" when we should get "Not Allowed (Default)".
+ */
+
+ // If there is an explicit permission "Not Allowed". Calculated permission is "Not Allowed".
+ if ($assetRule === false) {
+ $result['class'] = 'badge bg-danger';
+ $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED');
+ } elseif ($assetRule === true) {
+ // If there is an explicit permission is "Allowed". Calculated permission is "Allowed".
+ $result['class'] = 'badge bg-success';
+ $result['text'] = Text::_('JLIB_RULES_ALLOWED');
+ }
+
+ // Third part: Overwrite the calculated permissions labels for special cases.
+
+ // Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)".
+ if (empty($parentGroupId) && $isGlobalConfig === true && $assetRule === null) {
+ $result['class'] = 'badge bg-danger';
+ $result['text'] = Text::_('JLIB_RULES_NOT_ALLOWED_DEFAULT');
+ } elseif ($inheritedGroupParentAssetRule === false || $inheritedParentGroupRule === false) {
+ /**
+ * Component/Item with explicit "Denied" permission at parent Asset (Category, Component or Global config) configuration.
+ * Or some parent group has an explicit "Denied".
+ * Calculated permission is "Not Allowed (Locked)".
+ */
+ $result['class'] = 'badge bg-danger';
+ $result['text'] = ' ' . Text::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
+ }
+ }
+
+ // If removed or added super user from group, we need to refresh the page to recalculate all settings.
+ if ($isSuperUserGroupBefore != $isSuperUserGroupAfter) {
+ $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_PERMISSIONS'), 'notice');
+ }
+
+ // If this group has child groups, we need to refresh the page to recalculate the child settings.
+ if ($totalChildGroups > 0) {
+ $app->enqueueMessage(Text::_('JLIB_RULES_NOTICE_RECALCULATE_GROUP_CHILDS_PERMISSIONS'), 'notice');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to send a test mail which is called via an AJAX request
+ *
+ * @return boolean
+ *
+ * @since 3.5
+ */
+ public function sendTestMail()
+ {
+ // Set the new values to test with the current settings
+ $app = Factory::getApplication();
+ $user = Factory::getUser();
+ $input = $app->input->json;
+ $smtppass = $input->get('smtppass', null, 'RAW');
+
+ $app->set('smtpauth', $input->get('smtpauth'));
+ $app->set('smtpuser', $input->get('smtpuser', '', 'STRING'));
+ $app->set('smtphost', $input->get('smtphost'));
+ $app->set('smtpsecure', $input->get('smtpsecure'));
+ $app->set('smtpport', $input->get('smtpport'));
+ $app->set('mailfrom', $input->get('mailfrom', '', 'STRING'));
+ $app->set('fromname', $input->get('fromname', '', 'STRING'));
+ $app->set('mailer', $input->get('mailer'));
+ $app->set('mailonline', $input->get('mailonline'));
+
+ // Use smtppass only if it was submitted
+ if ($smtppass !== null) {
+ $app->set('smtppass', $smtppass);
+ }
+
+ $mail = Factory::getMailer();
+
+ // Prepare email and try to send it
+ $mailer = new MailTemplate('com_config.test_mail', $user->getParam('language', $app->get('language')), $mail);
+ $mailer->addTemplateData(
+ array(
+ 'sitename' => $app->get('sitename'),
+ 'method' => Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer))
+ )
+ );
+ $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname'));
+
+ try {
+ $mailSent = $mailer->send();
+ } catch (MailDisabledException | phpMailerException $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ if ($mailSent === true) {
+ $methodName = Text::_('COM_CONFIG_SENDMAIL_METHOD_' . strtoupper($mail->Mailer));
+
+ // If JMail send the mail using PHP Mail as fallback.
+ if ($mail->Mailer !== $app->get('mailer')) {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS_FALLBACK', $app->get('mailfrom'), $methodName), 'warning');
+ } else {
+ $app->enqueueMessage(Text::sprintf('COM_CONFIG_SENDMAIL_SUCCESS', $app->get('mailfrom'), $methodName), 'message');
+ }
+
+ return true;
+ }
+
+ $app->enqueueMessage(Text::_('COM_CONFIG_SENDMAIL_ERROR'), 'error');
+
+ return false;
+ }
}
diff --git a/administrator/components/com_config/src/Model/ComponentModel.php b/administrator/components/com_config/src/Model/ComponentModel.php
index 34e97a703fb44..726cc9ac9ea4f 100644
--- a/administrator/components/com_config/src/Model/ComponentModel.php
+++ b/administrator/components/com_config/src/Model/ComponentModel.php
@@ -1,4 +1,5 @@
input;
-
- // Set the component (option) we are dealing with.
- $component = $input->get('component');
-
- $this->state->set('component.option', $component);
-
- // Set an alternative path for the configuration file.
- if ($path = $input->getString('path'))
- {
- $path = Path::clean(JPATH_SITE . '/' . $path);
- Path::check($path);
- $this->state->set('component.path', $path);
- }
- }
-
- /**
- * Method to get a form object.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return mixed A JForm object on success, false on failure
- *
- * @since 3.2
- */
- public function getForm($data = array(), $loadData = true)
- {
- $state = $this->getState();
- $option = $state->get('component.option');
-
- if ($path = $state->get('component.path'))
- {
- // Add the search path for the admin component config.xml file.
- Form::addFormPath($path);
- }
- else
- {
- // Add the search path for the admin component config.xml file.
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/' . $option);
- }
-
- // Get the form.
- $form = $this->loadForm(
- 'com_config.component',
- 'config',
- array('control' => 'jform', 'load_data' => $loadData),
- false,
- '/config'
- );
-
- if (empty($form))
- {
- return false;
- }
-
- $lang = Factory::getLanguage();
- $lang->load($option, JPATH_BASE)
- || $lang->load($option, JPATH_BASE . "/components/$option");
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return array The default data is an empty array.
- *
- * @since 4.0.0
- */
- protected function loadFormData()
- {
- $option = $this->getState()->get('component.option');
-
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_config.edit.component.' . $option . '.data', []);
-
- if (empty($data))
- {
- return $this->getComponent()->getParams()->toArray();
- }
-
- return $data;
- }
-
- /**
- * Get the component information.
- *
- * @return object
- *
- * @since 3.2
- */
- public function getComponent()
- {
- $state = $this->getState();
- $option = $state->get('component.option');
-
- // Load common and local language files.
- $lang = Factory::getLanguage();
- $lang->load($option, JPATH_BASE)
- || $lang->load($option, JPATH_BASE . "/components/$option");
-
- $result = ComponentHelper::getComponent($option);
-
- return $result;
- }
-
- /**
- * Method to save the configuration data.
- *
- * @param array $data An array containing all global config data.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 3.2
- * @throws \RuntimeException
- */
- public function save($data)
- {
- $table = Table::getInstance('extension');
- $context = $this->option . '.' . $this->name;
- PluginHelper::importPlugin('extension');
-
- // Check super user group.
- if (isset($data['params']) && !Factory::getUser()->authorise('core.admin'))
- {
- $form = $this->getForm(array(), false);
-
- foreach ($form->getFieldsets() as $fieldset)
- {
- foreach ($form->getFieldset($fieldset->name) as $field)
- {
- if ($field->type === 'UserGroupList' && isset($data['params'][$field->fieldname])
- && (int) $field->getAttribute('checksuperusergroup', 0) === 1
- && Access::checkGroup($data['params'][$field->fieldname], 'core.admin'))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
- }
- }
- }
- }
-
- // Save the rules.
- if (isset($data['params']) && isset($data['params']['rules']))
- {
- if (!Factory::getUser()->authorise('core.admin', $data['option']))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
- }
-
- $rules = new Rules($data['params']['rules']);
- $asset = Table::getInstance('asset');
-
- if (!$asset->loadByName($data['option']))
- {
- $root = Table::getInstance('asset');
- $root->loadByName('root.1');
- $asset->name = $data['option'];
- $asset->title = $data['option'];
- $asset->setLocation($root->id, 'last-child');
- }
-
- $asset->rules = (string) $rules;
-
- if (!$asset->check() || !$asset->store())
- {
- throw new \RuntimeException($asset->getError());
- }
-
- // We don't need this anymore
- unset($data['option']);
- unset($data['params']['rules']);
- }
-
- // Load the previous Data
- if (!$table->load($data['id']))
- {
- throw new \RuntimeException($table->getError());
- }
-
- unset($data['id']);
-
- // Bind the data.
- if (!$table->bind($data))
- {
- throw new \RuntimeException($table->getError());
- }
-
- // Check the data.
- if (!$table->check())
- {
- throw new \RuntimeException($table->getError());
- }
-
- $result = Factory::getApplication()->triggerEvent('onExtensionBeforeSave', array($context, $table, false));
-
- // Store the data.
- if (in_array(false, $result, true) || !$table->store())
- {
- throw new \RuntimeException($table->getError());
- }
-
- Factory::getApplication()->triggerEvent('onExtensionAfterSave', array($context, $table, false));
-
- // Clean the component cache.
- $this->cleanCache('_system');
-
- return true;
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function populateState()
+ {
+ $input = Factory::getApplication()->input;
+
+ // Set the component (option) we are dealing with.
+ $component = $input->get('component');
+
+ $this->state->set('component.option', $component);
+
+ // Set an alternative path for the configuration file.
+ if ($path = $input->getString('path')) {
+ $path = Path::clean(JPATH_SITE . '/' . $path);
+ Path::check($path);
+ $this->state->set('component.path', $path);
+ }
+ }
+
+ /**
+ * Method to get a form object.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return mixed A JForm object on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $state = $this->getState();
+ $option = $state->get('component.option');
+
+ if ($path = $state->get('component.path')) {
+ // Add the search path for the admin component config.xml file.
+ Form::addFormPath($path);
+ } else {
+ // Add the search path for the admin component config.xml file.
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/' . $option);
+ }
+
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_config.component',
+ 'config',
+ array('control' => 'jform', 'load_data' => $loadData),
+ false,
+ '/config'
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $lang = Factory::getLanguage();
+ $lang->load($option, JPATH_BASE)
+ || $lang->load($option, JPATH_BASE . "/components/$option");
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return array The default data is an empty array.
+ *
+ * @since 4.0.0
+ */
+ protected function loadFormData()
+ {
+ $option = $this->getState()->get('component.option');
+
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_config.edit.component.' . $option . '.data', []);
+
+ if (empty($data)) {
+ return $this->getComponent()->getParams()->toArray();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Get the component information.
+ *
+ * @return object
+ *
+ * @since 3.2
+ */
+ public function getComponent()
+ {
+ $state = $this->getState();
+ $option = $state->get('component.option');
+
+ // Load common and local language files.
+ $lang = Factory::getLanguage();
+ $lang->load($option, JPATH_BASE)
+ || $lang->load($option, JPATH_BASE . "/components/$option");
+
+ $result = ComponentHelper::getComponent($option);
+
+ return $result;
+ }
+
+ /**
+ * Method to save the configuration data.
+ *
+ * @param array $data An array containing all global config data.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 3.2
+ * @throws \RuntimeException
+ */
+ public function save($data)
+ {
+ $table = Table::getInstance('extension');
+ $context = $this->option . '.' . $this->name;
+ PluginHelper::importPlugin('extension');
+
+ // Check super user group.
+ if (isset($data['params']) && !Factory::getUser()->authorise('core.admin')) {
+ $form = $this->getForm(array(), false);
+
+ foreach ($form->getFieldsets() as $fieldset) {
+ foreach ($form->getFieldset($fieldset->name) as $field) {
+ if (
+ $field->type === 'UserGroupList' && isset($data['params'][$field->fieldname])
+ && (int) $field->getAttribute('checksuperusergroup', 0) === 1
+ && Access::checkGroup($data['params'][$field->fieldname], 'core.admin')
+ ) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
+ }
+ }
+ }
+ }
+
+ // Save the rules.
+ if (isset($data['params']) && isset($data['params']['rules'])) {
+ if (!Factory::getUser()->authorise('core.admin', $data['option'])) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
+ }
+
+ $rules = new Rules($data['params']['rules']);
+ $asset = Table::getInstance('asset');
+
+ if (!$asset->loadByName($data['option'])) {
+ $root = Table::getInstance('asset');
+ $root->loadByName('root.1');
+ $asset->name = $data['option'];
+ $asset->title = $data['option'];
+ $asset->setLocation($root->id, 'last-child');
+ }
+
+ $asset->rules = (string) $rules;
+
+ if (!$asset->check() || !$asset->store()) {
+ throw new \RuntimeException($asset->getError());
+ }
+
+ // We don't need this anymore
+ unset($data['option']);
+ unset($data['params']['rules']);
+ }
+
+ // Load the previous Data
+ if (!$table->load($data['id'])) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ unset($data['id']);
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ // Check the data.
+ if (!$table->check()) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ $result = Factory::getApplication()->triggerEvent('onExtensionBeforeSave', array($context, $table, false));
+
+ // Store the data.
+ if (in_array(false, $result, true) || !$table->store()) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ Factory::getApplication()->triggerEvent('onExtensionAfterSave', array($context, $table, false));
+
+ // Clean the component cache.
+ $this->cleanCache('_system');
+
+ return true;
+ }
}
diff --git a/administrator/components/com_config/src/View/Application/HtmlView.php b/administrator/components/com_config/src/View/Application/HtmlView.php
index 4811211852143..750e4371ad217 100644
--- a/administrator/components/com_config/src/View/Application/HtmlView.php
+++ b/administrator/components/com_config/src/View/Application/HtmlView.php
@@ -1,4 +1,5 @@
get('form');
- $data = $this->get('data');
- $user = $this->getCurrentUser();
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return;
- }
-
- // Bind data
- if ($form && $data)
- {
- $form->bind($data);
- }
-
- // Get the params for com_users.
- $usersParams = ComponentHelper::getParams('com_users');
-
- // Get the params for com_media.
- $mediaParams = ComponentHelper::getParams('com_media');
-
- $this->form = &$form;
- $this->data = &$data;
- $this->usersParams = &$usersParams;
- $this->mediaParams = &$mediaParams;
- $this->components = ConfigHelper::getComponentsWithConfig();
- ConfigHelper::loadLanguageForComponents($this->components);
-
- $this->userIsSuperAdmin = $user->authorise('core.admin');
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.2
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_('COM_CONFIG_GLOBAL_CONFIGURATION'), 'cog config');
- ToolbarHelper::apply('application.apply');
- ToolbarHelper::divider();
- ToolbarHelper::save('application.save');
- ToolbarHelper::divider();
- ToolbarHelper::cancel('application.cancel', 'JTOOLBAR_CLOSE');
- ToolbarHelper::divider();
- ToolbarHelper::inlinehelp();
- ToolbarHelper::help('Site_Global_Configuration');
- }
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ * @since 3.2
+ */
+ public $state;
+
+ /**
+ * The form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ * @since 3.2
+ */
+ public $form;
+
+ /**
+ * The data to be displayed in the form
+ *
+ * @var array
+ * @since 3.2
+ */
+ public $data;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see \JViewLegacy::loadTemplate()
+ * @since 3.0
+ */
+ public function display($tpl = null)
+ {
+ try {
+ // Load Form and Data
+ $form = $this->get('form');
+ $data = $this->get('data');
+ $user = $this->getCurrentUser();
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return;
+ }
+
+ // Bind data
+ if ($form && $data) {
+ $form->bind($data);
+ }
+
+ // Get the params for com_users.
+ $usersParams = ComponentHelper::getParams('com_users');
+
+ // Get the params for com_media.
+ $mediaParams = ComponentHelper::getParams('com_media');
+
+ $this->form = &$form;
+ $this->data = &$data;
+ $this->usersParams = &$usersParams;
+ $this->mediaParams = &$mediaParams;
+ $this->components = ConfigHelper::getComponentsWithConfig();
+ ConfigHelper::loadLanguageForComponents($this->components);
+
+ $this->userIsSuperAdmin = $user->authorise('core.admin');
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_CONFIG_GLOBAL_CONFIGURATION'), 'cog config');
+ ToolbarHelper::apply('application.apply');
+ ToolbarHelper::divider();
+ ToolbarHelper::save('application.save');
+ ToolbarHelper::divider();
+ ToolbarHelper::cancel('application.cancel', 'JTOOLBAR_CLOSE');
+ ToolbarHelper::divider();
+ ToolbarHelper::inlinehelp();
+ ToolbarHelper::help('Site_Global_Configuration');
+ }
}
diff --git a/administrator/components/com_config/src/View/Component/HtmlView.php b/administrator/components/com_config/src/View/Component/HtmlView.php
index b29e47b5031ad..20c3122bf3910 100644
--- a/administrator/components/com_config/src/View/Component/HtmlView.php
+++ b/administrator/components/com_config/src/View/Component/HtmlView.php
@@ -1,4 +1,5 @@
get('component');
-
- if (!$component->enabled)
- {
- return;
- }
-
- $form = $this->get('form');
- $user = $this->getCurrentUser();
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return;
- }
-
- $this->fieldsets = $form ? $form->getFieldsets() : null;
- $this->formControl = $form ? $form->getFormControl() : null;
-
- // Don't show permissions fieldset if not authorised.
- if (!$user->authorise('core.admin', $component->option) && isset($this->fieldsets['permissions']))
- {
- unset($this->fieldsets['permissions']);
- }
-
- $this->form = &$form;
- $this->component = &$component;
-
- $this->components = ConfigHelper::getComponentsWithConfig();
-
- $this->userIsSuperAdmin = $user->authorise('core.admin');
- $this->currentComponent = Factory::getApplication()->input->get('component');
- $this->return = Factory::getApplication()->input->get('return', '', 'base64');
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.2
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_($this->component->option . '_configuration'), 'cog config');
- ToolbarHelper::apply('component.apply');
- ToolbarHelper::divider();
- ToolbarHelper::save('component.save');
- ToolbarHelper::divider();
- ToolbarHelper::cancel('component.cancel', 'JTOOLBAR_CLOSE');
- ToolbarHelper::divider();
-
- $inlinehelp = (string) $this->form->getXml()->config->inlinehelp['button'] == 'show' ?: false;
- $targetClass = (string) $this->form->getXml()->config->inlinehelp['targetclass'] ?: 'hide-aware-inline-help';
-
- if ($inlinehelp)
- {
- ToolbarHelper::inlinehelp($targetClass);
- }
-
- $helpUrl = $this->form->getData()->get('helpURL');
- $helpKey = (string) $this->form->getXml()->config->help['key'];
-
- // Try with legacy language key
- if (!$helpKey)
- {
- $language = Factory::getApplication()->getLanguage();
- $languageKey = 'JHELP_COMPONENTS_' . strtoupper($this->currentComponent) . '_OPTIONS';
-
- if ($language->hasKey($languageKey))
- {
- $helpKey = $languageKey;
- }
- }
-
- ToolbarHelper::help($helpKey, (boolean) $helpUrl, null, $this->currentComponent);
- }
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ * @since 3.2
+ */
+ public $state;
+
+ /**
+ * The form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ * @since 3.2
+ */
+ public $form;
+
+ /**
+ * An object with the information for the component
+ *
+ * @var \Joomla\CMS\Component\ComponentRecord
+ * @since 3.2
+ */
+ public $component;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see \JViewLegacy::loadTemplate()
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ try {
+ $component = $this->get('component');
+
+ if (!$component->enabled) {
+ return;
+ }
+
+ $form = $this->get('form');
+ $user = $this->getCurrentUser();
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return;
+ }
+
+ $this->fieldsets = $form ? $form->getFieldsets() : null;
+ $this->formControl = $form ? $form->getFormControl() : null;
+
+ // Don't show permissions fieldset if not authorised.
+ if (!$user->authorise('core.admin', $component->option) && isset($this->fieldsets['permissions'])) {
+ unset($this->fieldsets['permissions']);
+ }
+
+ $this->form = &$form;
+ $this->component = &$component;
+
+ $this->components = ConfigHelper::getComponentsWithConfig();
+
+ $this->userIsSuperAdmin = $user->authorise('core.admin');
+ $this->currentComponent = Factory::getApplication()->input->get('component');
+ $this->return = Factory::getApplication()->input->get('return', '', 'base64');
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_($this->component->option . '_configuration'), 'cog config');
+ ToolbarHelper::apply('component.apply');
+ ToolbarHelper::divider();
+ ToolbarHelper::save('component.save');
+ ToolbarHelper::divider();
+ ToolbarHelper::cancel('component.cancel', 'JTOOLBAR_CLOSE');
+ ToolbarHelper::divider();
+
+ $inlinehelp = (string) $this->form->getXml()->config->inlinehelp['button'] == 'show' ?: false;
+ $targetClass = (string) $this->form->getXml()->config->inlinehelp['targetclass'] ?: 'hide-aware-inline-help';
+
+ if ($inlinehelp) {
+ ToolbarHelper::inlinehelp($targetClass);
+ }
+
+ $helpUrl = $this->form->getData()->get('helpURL');
+ $helpKey = (string) $this->form->getXml()->config->help['key'];
+
+ // Try with legacy language key
+ if (!$helpKey) {
+ $language = Factory::getApplication()->getLanguage();
+ $languageKey = 'JHELP_COMPONENTS_' . strtoupper($this->currentComponent) . '_OPTIONS';
+
+ if ($language->hasKey($languageKey)) {
+ $helpKey = $languageKey;
+ }
+ }
+
+ ToolbarHelper::help($helpKey, (bool) $helpUrl, null, $this->currentComponent);
+ }
}
diff --git a/administrator/components/com_config/tmpl/application/default.php b/administrator/components/com_config/tmpl/application/default.php
index 9dba06f4e808e..9cc769e0d13e1 100644
--- a/administrator/components/com_config/tmpl/application/default.php
+++ b/administrator/components/com_config/tmpl/application/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
// Load JS message titles
Text::script('ERROR');
@@ -26,56 +27,56 @@
?>
diff --git a/administrator/components/com_config/tmpl/application/default_cache.php b/administrator/components/com_config/tmpl/application/default_cache.php
index 79d8bb004f498..621f93e66383d 100644
--- a/administrator/components/com_config/tmpl/application/default_cache.php
+++ b/administrator/components/com_config/tmpl/application/default_cache.php
@@ -1,4 +1,5 @@
document->getWebAssetManager()
- ->useScript('webcomponent.field-send-test-mail');
+ ->useScript('webcomponent.field-send-test-mail');
// Load JavaScript message titles
Text::script('ERROR');
@@ -42,9 +43,9 @@
?>
-
+
-
-
-
+
+
+
diff --git a/administrator/components/com_config/tmpl/application/default_metadata.php b/administrator/components/com_config/tmpl/application/default_metadata.php
index 912499a3b6a36..75f9eeb1fea93 100644
--- a/administrator/components/com_config/tmpl/application/default_metadata.php
+++ b/administrator/components/com_config/tmpl/application/default_metadata.php
@@ -1,4 +1,5 @@
- userIsSuperAdmin) : ?>
-
-
-
-
-
-
-
- components as $component) : ?>
-
-
-
-
+ userIsSuperAdmin) : ?>
+
+
+
+
+
+
+
+ components as $component) : ?>
+
+
+
+
diff --git a/administrator/components/com_config/tmpl/application/default_permissions.php b/administrator/components/com_config/tmpl/application/default_permissions.php
index 83cd3c43bfe0b..929e62b1a3cfd 100644
--- a/administrator/components/com_config/tmpl/application/default_permissions.php
+++ b/administrator/components/com_config/tmpl/application/default_permissions.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('form.validate')
- ->useScript('keepalive');
+ ->useScript('keepalive');
-if ($this->fieldsets)
-{
- HTMLHelper::_('bootstrap.framework');
+if ($this->fieldsets) {
+ HTMLHelper::_('bootstrap.framework');
}
$xml = $this->form->getXml();
?>
diff --git a/administrator/components/com_config/tmpl/component/default_navigation.php b/administrator/components/com_config/tmpl/component/default_navigation.php
index 7f8f9a68b12cb..2eb654443da86 100644
--- a/administrator/components/com_config/tmpl/component/default_navigation.php
+++ b/administrator/components/com_config/tmpl/component/default_navigation.php
@@ -1,4 +1,5 @@
- userIsSuperAdmin) : ?>
-
-
-
-
-
- components as $component) : ?>
- currentComponent === $component)
- {
- $active = ' active';
- }
- ?>
-
-
-
-
+ userIsSuperAdmin) : ?>
+
+
+
+
+
+ components as $component) : ?>
+ currentComponent === $component) {
+ $active = ' active';
+ }
+ ?>
+
+
+
+
diff --git a/administrator/components/com_contact/helpers/contact.php b/administrator/components/com_contact/helpers/contact.php
index 262294130943f..8b26f64d3aa49 100644
--- a/administrator/components/com_contact/helpers/contact.php
+++ b/administrator/components/com_contact/helpers/contact.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Contact component helper.
diff --git a/administrator/components/com_contact/services/provider.php b/administrator/components/com_contact/services/provider.php
index d9d3d0c348f22..21f9104d289ff 100644
--- a/administrator/components/com_contact/services/provider.php
+++ b/administrator/components/com_contact/services/provider.php
@@ -1,4 +1,5 @@
set(AssociationExtensionInterface::class, new AssociationsHelper);
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(AssociationExtensionInterface::class, new AssociationsHelper());
- $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Contact'));
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Contact'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Contact'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Contact'));
+ $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Contact'));
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Contact'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Contact'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Contact'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new ContactComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new ContactComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
+ $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_contact/src/Controller/AjaxController.php b/administrator/components/com_contact/src/Controller/AjaxController.php
index 1aff104743200..4a4649f3ab51f 100644
--- a/administrator/components/com_contact/src/Controller/AjaxController.php
+++ b/administrator/components/com_contact/src/Controller/AjaxController.php
@@ -1,4 +1,5 @@
input->getInt('assocId', 0);
+ /**
+ * Method to fetch associations of a contact
+ *
+ * The method assumes that the following http parameters are passed in an Ajax Get request:
+ * token: the form token
+ * assocId: the id of the contact whose associations are to be returned
+ * excludeLang: the association for this language is to be excluded
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function fetchAssociations()
+ {
+ if (!Session::checkToken('get')) {
+ echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
+ } else {
+ $assocId = $this->input->getInt('assocId', 0);
- if ($assocId == 0)
- {
- echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
+ if ($assocId == 0) {
+ echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
- return;
- }
+ return;
+ }
- $excludeLang = $this->input->get('excludeLang', '', 'STRING');
+ $excludeLang = $this->input->get('excludeLang', '', 'STRING');
- $associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', (int) $assocId);
+ $associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', (int) $assocId);
- unset($associations[$excludeLang]);
+ unset($associations[$excludeLang]);
- // Add the title to each of the associated records
- $contactTable = $this->factory->createTable('Contact', 'Administrator');
+ // Add the title to each of the associated records
+ $contactTable = $this->factory->createTable('Contact', 'Administrator');
- foreach ($associations as $lang => $association)
- {
- $contactTable->load($association->id);
- $associations[$lang]->title = $contactTable->name;
- }
+ foreach ($associations as $lang => $association) {
+ $contactTable->load($association->id);
+ $associations[$lang]->title = $contactTable->name;
+ }
- $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
+ $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
- if (count($associations) == 0)
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
- }
- elseif ($countContentLanguages > count($associations) + 2)
- {
- $tags = implode(', ', array_keys($associations));
- $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
- }
- else
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
- }
+ if (count($associations) == 0) {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
+ } elseif ($countContentLanguages > count($associations) + 2) {
+ $tags = implode(', ', array_keys($associations));
+ $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
+ } else {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
+ }
- echo new JsonResponse($associations, $message);
- }
- }
+ echo new JsonResponse($associations, $message);
+ }
+ }
}
diff --git a/administrator/components/com_contact/src/Controller/ContactController.php b/administrator/components/com_contact/src/Controller/ContactController.php
index 0817e4245da84..c305e6a5af50f 100644
--- a/administrator/components/com_contact/src/Controller/ContactController.php
+++ b/administrator/components/com_contact/src/Controller/ContactController.php
@@ -1,4 +1,5 @@
input->getInt('filter_category_id'), 'int');
-
- if ($categoryId)
- {
- // If the category has been passed in the URL check it.
- return $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
- }
-
- // In the absence of better information, revert to the component permissions.
- return parent::allowAdd($data);
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
-
- // Since there is no asset tracking, fallback to the component permissions.
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Get the item.
- $item = $this->getModel()->getItem($recordId);
-
- // Since there is no item, return false.
- if (empty($item))
- {
- return false;
- }
-
- $user = $this->app->getIdentity();
-
- // Check if can edit own core.edit.own.
- $canEditOwn = $user->authorise('core.edit.own', $this->option . '.category.' . (int) $item->catid) && $item->created_by == $user->id;
-
- // Check the category core.edit permissions.
- return $canEditOwn || $user->authorise('core.edit', $this->option . '.category.' . (int) $item->catid);
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- /** @var \Joomla\Component\Contact\Administrator\Model\ContactModel $model */
- $model = $this->getModel('Contact', 'Administrator', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_contact&view=contacts' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
-
- /**
- * Function that allows child controller access to model data
- * after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = [])
- {
- if ($this->getTask() === 'save2menu')
- {
- $editState = [];
-
- $id = $model->getState('contact.id');
-
- $link = 'index.php?option=com_contact&view=contact';
- $type = 'component';
-
- $editState['id'] = $id;
- $editState['link'] = $link;
- $editState['title'] = $model->getItem($id)->name;
- $editState['type'] = $type;
- $editState['request']['id'] = $id;
-
- $this->app->setUserState(
- 'com_menus.edit.item',
- [
- 'data' => $editState,
- 'type' => $type,
- 'link' => $link,
- ]
- );
-
- $this->setRedirect(Route::_('index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit', false));
- }
- }
+ use VersionableControllerTrait;
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('filter_category_id'), 'int');
+
+ if ($categoryId) {
+ // If the category has been passed in the URL check it.
+ return $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
+ }
+
+ // In the absence of better information, revert to the component permissions.
+ return parent::allowAdd($data);
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+
+ // Since there is no asset tracking, fallback to the component permissions.
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Get the item.
+ $item = $this->getModel()->getItem($recordId);
+
+ // Since there is no item, return false.
+ if (empty($item)) {
+ return false;
+ }
+
+ $user = $this->app->getIdentity();
+
+ // Check if can edit own core.edit.own.
+ $canEditOwn = $user->authorise('core.edit.own', $this->option . '.category.' . (int) $item->catid) && $item->created_by == $user->id;
+
+ // Check the category core.edit permissions.
+ return $canEditOwn || $user->authorise('core.edit', $this->option . '.category.' . (int) $item->catid);
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ /** @var \Joomla\Component\Contact\Administrator\Model\ContactModel $model */
+ $model = $this->getModel('Contact', 'Administrator', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_contact&view=contacts' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Function that allows child controller access to model data
+ * after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = [])
+ {
+ if ($this->getTask() === 'save2menu') {
+ $editState = [];
+
+ $id = $model->getState('contact.id');
+
+ $link = 'index.php?option=com_contact&view=contact';
+ $type = 'component';
+
+ $editState['id'] = $id;
+ $editState['link'] = $link;
+ $editState['title'] = $model->getItem($id)->name;
+ $editState['type'] = $type;
+ $editState['request']['id'] = $id;
+
+ $this->app->setUserState(
+ 'com_menus.edit.item',
+ [
+ 'data' => $editState,
+ 'type' => $type,
+ 'link' => $link,
+ ]
+ );
+
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit', false));
+ }
+ }
}
diff --git a/administrator/components/com_contact/src/Controller/ContactsController.php b/administrator/components/com_contact/src/Controller/ContactsController.php
index 953b454c28863..87c6ea36ffaaa 100644
--- a/administrator/components/com_contact/src/Controller/ContactsController.php
+++ b/administrator/components/com_contact/src/Controller/ContactsController.php
@@ -1,4 +1,5 @@
registerTask('unfeatured', 'featured');
- }
-
- /**
- * Method to toggle the featured setting of a list of contacts.
- *
- * @return void
- *
- * @since 1.6
- */
- public function featured()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
- $values = array('featured' => 1, 'unfeatured' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($values, $task, 0, 'int');
-
- // Get the model.
- /** @var \Joomla\Component\Contact\Administrator\Model\ContactModel $model */
- $model = $this->getModel();
-
- // Access checks.
- foreach ($ids as $i => $id)
- {
- // Remove zero value resulting from input filter
- if ($id === 0)
- {
- unset($ids[$i]);
-
- continue;
- }
-
- $item = $model->getItem($id);
-
- if (!$this->app->getIdentity()->authorise('core.edit.state', 'com_contact.category.' . (int) $item->catid))
- {
- // Prune items that you can't change.
- unset($ids[$i]);
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
- }
- }
-
- if (empty($ids))
- {
- $message = null;
-
- $this->app->enqueueMessage(Text::_('COM_CONTACT_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- // Publish the items.
- if (!$model->featured($ids, $value))
- {
- $this->app->enqueueMessage($model->getError(), 'warning');
- }
-
- if ($value == 1)
- {
- $message = Text::plural('COM_CONTACT_N_ITEMS_FEATURED', count($ids));
- }
- else
- {
- $message = Text::plural('COM_CONTACT_N_ITEMS_UNFEATURED', count($ids));
- }
- }
-
- $this->setRedirect('index.php?option=com_contact&view=contacts', $message);
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The name of the model.
- * @param string $prefix The prefix for the PHP class name.
- * @param array $config Array of configuration parameters.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
- *
- * @since 1.6
- */
- public function getModel($name = 'Contact', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('unfeatured', 'featured');
+ }
+
+ /**
+ * Method to toggle the featured setting of a list of contacts.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function featured()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+ $values = array('featured' => 1, 'unfeatured' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+
+ // Get the model.
+ /** @var \Joomla\Component\Contact\Administrator\Model\ContactModel $model */
+ $model = $this->getModel();
+
+ // Access checks.
+ foreach ($ids as $i => $id) {
+ // Remove zero value resulting from input filter
+ if ($id === 0) {
+ unset($ids[$i]);
+
+ continue;
+ }
+
+ $item = $model->getItem($id);
+
+ if (!$this->app->getIdentity()->authorise('core.edit.state', 'com_contact.category.' . (int) $item->catid)) {
+ // Prune items that you can't change.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+ }
+
+ if (empty($ids)) {
+ $message = null;
+
+ $this->app->enqueueMessage(Text::_('COM_CONTACT_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ // Publish the items.
+ if (!$model->featured($ids, $value)) {
+ $this->app->enqueueMessage($model->getError(), 'warning');
+ }
+
+ if ($value == 1) {
+ $message = Text::plural('COM_CONTACT_N_ITEMS_FEATURED', count($ids));
+ } else {
+ $message = Text::plural('COM_CONTACT_N_ITEMS_UNFEATURED', count($ids));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_contact&view=contacts', $message);
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model.
+ * @param string $prefix The prefix for the PHP class name.
+ * @param array $config Array of configuration parameters.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Contact', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_contact/src/Controller/DisplayController.php b/administrator/components/com_contact/src/Controller/DisplayController.php
index 81f436b15d2a6..abbc104d0a4fb 100644
--- a/administrator/components/com_contact/src/Controller/DisplayController.php
+++ b/administrator/components/com_contact/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', $this->default_view);
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if ($view == 'contact' && $layout == 'edit' && !$this->checkEditId('com_contact.edit.contact', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_contact&view=contacts', false));
-
- return false;
- }
-
- return parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'contacts';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static |boolean This object to support chaining. False on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view', $this->default_view);
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if ($view == 'contact' && $layout == 'edit' && !$this->checkEditId('com_contact.edit.contact', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_contact&view=contacts', false));
+
+ return false;
+ }
+
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_contact/src/Extension/ContactComponent.php b/administrator/components/com_contact/src/Extension/ContactComponent.php
index cfddee3de41a3..9bb600d4ab5a4 100644
--- a/administrator/components/com_contact/src/Extension/ContactComponent.php
+++ b/administrator/components/com_contact/src/Extension/ContactComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('contactadministrator', new AdministratorService);
- $this->getRegistry()->register('contacticon', new Icon($container->get(UserFactoryInterface::class)));
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('contactadministrator', new AdministratorService());
+ $this->getRegistry()->register('contacticon', new Icon($container->get(UserFactoryInterface::class)));
+ }
- /**
- * Returns a valid section for the given section. If it is not valid then null
- * is returned.
- *
- * @param string $section The section to get the mapping for
- * @param object $item The item
- *
- * @return string|null The new section
- *
- * @since 4.0.0
- */
- public function validateSection($section, $item = null)
- {
- if (Factory::getApplication()->isClient('site') && $section == 'contact' && $item instanceof Form)
- {
- // The contact form needs to be the mail section
- $section = 'mail';
- }
+ /**
+ * Returns a valid section for the given section. If it is not valid then null
+ * is returned.
+ *
+ * @param string $section The section to get the mapping for
+ * @param object $item The item
+ *
+ * @return string|null The new section
+ *
+ * @since 4.0.0
+ */
+ public function validateSection($section, $item = null)
+ {
+ if (Factory::getApplication()->isClient('site') && $section == 'contact' && $item instanceof Form) {
+ // The contact form needs to be the mail section
+ $section = 'mail';
+ }
- if (Factory::getApplication()->isClient('site') && ($section === 'category' || $section === 'form'))
- {
- // The contact form needs to be the mail section
- $section = 'contact';
- }
+ if (Factory::getApplication()->isClient('site') && ($section === 'category' || $section === 'form')) {
+ // The contact form needs to be the mail section
+ $section = 'contact';
+ }
- if ($section !== 'mail' && $section !== 'contact')
- {
- // We don't know other sections
- return null;
- }
+ if ($section !== 'mail' && $section !== 'contact') {
+ // We don't know other sections
+ return null;
+ }
- return $section;
- }
+ return $section;
+ }
- /**
- * Returns valid contexts
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getContexts(): array
- {
- Factory::getLanguage()->load('com_contact', JPATH_ADMINISTRATOR);
+ /**
+ * Returns valid contexts
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getContexts(): array
+ {
+ Factory::getLanguage()->load('com_contact', JPATH_ADMINISTRATOR);
- $contexts = array(
- 'com_contact.contact' => Text::_('COM_CONTACT_FIELDS_CONTEXT_CONTACT'),
- 'com_contact.mail' => Text::_('COM_CONTACT_FIELDS_CONTEXT_MAIL'),
- 'com_contact.categories' => Text::_('JCATEGORY')
- );
+ $contexts = array(
+ 'com_contact.contact' => Text::_('COM_CONTACT_FIELDS_CONTEXT_CONTACT'),
+ 'com_contact.mail' => Text::_('COM_CONTACT_FIELDS_CONTEXT_MAIL'),
+ 'com_contact.categories' => Text::_('JCATEGORY')
+ );
- return $contexts;
- }
+ return $contexts;
+ }
- /**
- * Returns the table for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getTableNameForSection(string $section = null)
- {
- return ($section === 'category' ? 'categories' : 'contact_details');
- }
+ /**
+ * Returns the table for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getTableNameForSection(string $section = null)
+ {
+ return ($section === 'category' ? 'categories' : 'contact_details');
+ }
- /**
- * Returns the state column for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getStateColumnForSection(string $section = null)
- {
- return 'published';
- }
+ /**
+ * Returns the state column for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getStateColumnForSection(string $section = null)
+ {
+ return 'published';
+ }
}
diff --git a/administrator/components/com_contact/src/Field/Modal/ContactField.php b/administrator/components/com_contact/src/Field/Modal/ContactField.php
index a67d64497d0df..5f1268e84a881 100644
--- a/administrator/components/com_contact/src/Field/Modal/ContactField.php
+++ b/administrator/components/com_contact/src/Field/Modal/ContactField.php
@@ -1,4 +1,5 @@
element['new'] == 'true');
- $allowEdit = ((string) $this->element['edit'] == 'true');
- $allowClear = ((string) $this->element['clear'] != 'false');
- $allowSelect = ((string) $this->element['select'] != 'false');
- $allowPropagate = ((string) $this->element['propagate'] == 'true');
-
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- // Load language
- Factory::getLanguage()->load('com_contact', JPATH_ADMINISTRATOR);
-
- // The active contact id field.
- $value = (int) $this->value ?: '';
-
- // Create the modal id.
- $modalId = 'Contact_' . $this->id;
-
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
-
- // Add the modal field script to the document head.
- $wa->useScript('field.modal-fields');
-
- // Script to proxy the select modal function to the modal-fields.js file.
- if ($allowSelect)
- {
- static $scriptSelect = null;
-
- if (is_null($scriptSelect))
- {
- $scriptSelect = array();
- }
-
- if (!isset($scriptSelect[$this->id]))
- {
- $wa->addInlineScript("
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'Modal_Contact';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $allowNew = ((string) $this->element['new'] == 'true');
+ $allowEdit = ((string) $this->element['edit'] == 'true');
+ $allowClear = ((string) $this->element['clear'] != 'false');
+ $allowSelect = ((string) $this->element['select'] != 'false');
+ $allowPropagate = ((string) $this->element['propagate'] == 'true');
+
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ // Load language
+ Factory::getLanguage()->load('com_contact', JPATH_ADMINISTRATOR);
+
+ // The active contact id field.
+ $value = (int) $this->value ?: '';
+
+ // Create the modal id.
+ $modalId = 'Contact_' . $this->id;
+
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+
+ // Add the modal field script to the document head.
+ $wa->useScript('field.modal-fields');
+
+ // Script to proxy the select modal function to the modal-fields.js file.
+ if ($allowSelect) {
+ static $scriptSelect = null;
+
+ if (is_null($scriptSelect)) {
+ $scriptSelect = array();
+ }
+
+ if (!isset($scriptSelect[$this->id])) {
+ $wa->addInlineScript(
+ "
window.jSelectContact_" . $this->id . " = function (id, title, object) {
window.processModalSelect('Contact', '" . $this->id . "', id, title, '', object);
}",
- [],
- ['type' => 'module']
- );
-
- Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
-
- $scriptSelect[$this->id] = true;
- }
- }
-
- // Setup variables for display.
- $linkContacts = 'index.php?option=com_contact&view=contacts&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
- $linkContact = 'index.php?option=com_contact&view=contact&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
- $modalTitle = Text::_('COM_CONTACT_SELECT_A_CONTACT');
-
- if (isset($this->element['language']))
- {
- $linkContacts .= '&forcedLanguage=' . $this->element['language'];
- $linkContact .= '&forcedLanguage=' . $this->element['language'];
- $modalTitle .= ' — ' . $this->element['label'];
- }
-
- $urlSelect = $linkContacts . '&function=jSelectContact_' . $this->id;
- $urlEdit = $linkContact . '&task=contact.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
- $urlNew = $linkContact . '&task=contact.add';
-
- if ($value)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('name'))
- ->from($db->quoteName('#__contact_details'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $value, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $title = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- $title = empty($title) ? Text::_('COM_CONTACT_SELECT_A_CONTACT') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
-
- // The current contact display field.
- $html = '';
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= '';
- }
-
- $html .= ' ';
-
- // Select contact button
- if ($allowSelect)
- {
- $html .= ''
- . ' ' . Text::_('JSELECT')
- . ' ';
- }
-
- // New contact button
- if ($allowNew)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_CREATE')
- . ' ';
- }
-
- // Edit contact button
- if ($allowEdit)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_EDIT')
- . ' ';
- }
-
- // Clear contact button
- if ($allowClear)
- {
- $html .= ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
- }
-
- // Propagate contact button
- if ($allowPropagate && count($languages) > 2)
- {
- // Strip off language tag at the end
- $tagLength = (int) strlen($this->element['language']);
- $callbackFunctionStem = substr("jSelectContact_" . $this->id, 0, -$tagLength);
-
- $html .= ''
- . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
- . ' ';
- }
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= ' ';
- }
-
- // Select contact modal
- if ($allowSelect)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalSelect' . $modalId,
- array(
- 'title' => $modalTitle,
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
- }
-
- // New contact modal
- if ($allowNew)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalNew' . $modalId,
- array(
- 'title' => Text::_('COM_CONTACT_NEW_CONTACT'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlNew,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Edit contact modal.
- if ($allowEdit)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalEdit' . $modalId,
- array(
- 'title' => Text::_('COM_CONTACT_EDIT_CONTACT'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlEdit,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Note: class='required' for client side validation.
- $class = $this->required ? ' class="required modal-value"' : '';
-
- $html .= ' ';
-
- return $html;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 3.4
- */
- protected function getLabel()
- {
- return str_replace($this->id, $this->id . '_name', parent::getLabel());
- }
+ [],
+ ['type' => 'module']
+ );
+
+ Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
+
+ $scriptSelect[$this->id] = true;
+ }
+ }
+
+ // Setup variables for display.
+ $linkContacts = 'index.php?option=com_contact&view=contacts&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+ $linkContact = 'index.php?option=com_contact&view=contact&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+ $modalTitle = Text::_('COM_CONTACT_SELECT_A_CONTACT');
+
+ if (isset($this->element['language'])) {
+ $linkContacts .= '&forcedLanguage=' . $this->element['language'];
+ $linkContact .= '&forcedLanguage=' . $this->element['language'];
+ $modalTitle .= ' — ' . $this->element['label'];
+ }
+
+ $urlSelect = $linkContacts . '&function=jSelectContact_' . $this->id;
+ $urlEdit = $linkContact . '&task=contact.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
+ $urlNew = $linkContact . '&task=contact.add';
+
+ if ($value) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('name'))
+ ->from($db->quoteName('#__contact_details'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $title = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $title = empty($title) ? Text::_('COM_CONTACT_SELECT_A_CONTACT') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
+
+ // The current contact display field.
+ $html = '';
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= '';
+ }
+
+ $html .= ' ';
+
+ // Select contact button
+ if ($allowSelect) {
+ $html .= ''
+ . ' ' . Text::_('JSELECT')
+ . ' ';
+ }
+
+ // New contact button
+ if ($allowNew) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_CREATE')
+ . ' ';
+ }
+
+ // Edit contact button
+ if ($allowEdit) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_EDIT')
+ . ' ';
+ }
+
+ // Clear contact button
+ if ($allowClear) {
+ $html .= ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+ }
+
+ // Propagate contact button
+ if ($allowPropagate && count($languages) > 2) {
+ // Strip off language tag at the end
+ $tagLength = (int) strlen($this->element['language']);
+ $callbackFunctionStem = substr("jSelectContact_" . $this->id, 0, -$tagLength);
+
+ $html .= ''
+ . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
+ . ' ';
+ }
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= ' ';
+ }
+
+ // Select contact modal
+ if ($allowSelect) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalSelect' . $modalId,
+ array(
+ 'title' => $modalTitle,
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+ }
+
+ // New contact modal
+ if ($allowNew) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalNew' . $modalId,
+ array(
+ 'title' => Text::_('COM_CONTACT_NEW_CONTACT'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlNew,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Edit contact modal.
+ if ($allowEdit) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalEdit' . $modalId,
+ array(
+ 'title' => Text::_('COM_CONTACT_EDIT_CONTACT'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlEdit,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Note: class='required' for client side validation.
+ $class = $this->required ? ' class="required modal-value"' : '';
+
+ $html .= ' ';
+
+ return $html;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 3.4
+ */
+ protected function getLabel()
+ {
+ return str_replace($this->id, $this->id . '_name', parent::getLabel());
+ }
}
diff --git a/administrator/components/com_contact/src/Helper/AssociationsHelper.php b/administrator/components/com_contact/src/Helper/AssociationsHelper.php
index 87551616a9836..f8e7c64f091d8 100644
--- a/administrator/components/com_contact/src/Helper/AssociationsHelper.php
+++ b/administrator/components/com_contact/src/Helper/AssociationsHelper.php
@@ -1,4 +1,5 @@
getType($typeName);
-
- $context = $this->extension . '.item';
- $catidField = 'catid';
-
- if ($typeName === 'category')
- {
- $context = 'com_categories.item';
- $catidField = '';
- }
-
- // Get the associations.
- $associations = Associations::getAssociations(
- $this->extension,
- $type['tables']['a'],
- $context,
- $id,
- 'id',
- 'alias',
- $catidField
- );
-
- return $associations;
- }
-
- /**
- * Get item information
- *
- * @param string $typeName The item type
- * @param int $id The id of item for which we need the associated items
- *
- * @return Table|null
- *
- * @since 3.7.0
- */
- public function getItem($typeName, $id)
- {
- if (empty($id))
- {
- return null;
- }
-
- $table = null;
-
- switch ($typeName)
- {
- case 'contact':
- $table = Table::getInstance('ContactTable', 'Joomla\\Component\\Contact\\Administrator\\Table\\');
- break;
-
- case 'category':
- $table = Table::getInstance('Category');
- break;
- }
-
- if (empty($table))
- {
- return null;
- }
-
- $table->load($id);
-
- return $table;
- }
-
- /**
- * Get information about the type
- *
- * @param string $typeName The item type
- *
- * @return array Array of item types
- *
- * @since 3.7.0
- */
- public function getType($typeName = '')
- {
- $fields = $this->getFieldsTemplate();
- $tables = array();
- $joins = array();
- $support = $this->getSupportTemplate();
- $title = '';
-
- if (in_array($typeName, $this->itemTypes))
- {
- switch ($typeName)
- {
- case 'contact':
- $fields['title'] = 'a.name';
- $fields['state'] = 'a.published';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['category'] = true;
- $support['save2copy'] = true;
-
- $tables = array(
- 'a' => '#__contact_details'
- );
-
- $title = 'contact';
- break;
-
- case 'category':
- $fields['created_user_id'] = 'a.created_user_id';
- $fields['ordering'] = 'a.lft';
- $fields['level'] = 'a.level';
- $fields['catid'] = '';
- $fields['state'] = 'a.published';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['level'] = true;
-
- $tables = array(
- 'a' => '#__categories'
- );
-
- $title = 'category';
- break;
- }
- }
-
- return array(
- 'fields' => $fields,
- 'support' => $support,
- 'tables' => $tables,
- 'joins' => $joins,
- 'title' => $title
- );
- }
+ /**
+ * The extension name
+ *
+ * @var array $extension
+ *
+ * @since 3.7.0
+ */
+ protected $extension = 'com_contact';
+
+ /**
+ * Array of item types
+ *
+ * @var array $itemTypes
+ *
+ * @since 3.7.0
+ */
+ protected $itemTypes = array('contact', 'category');
+
+ /**
+ * Has the extension association support
+ *
+ * @var boolean $associationsSupport
+ *
+ * @since 3.7.0
+ */
+ protected $associationsSupport = true;
+
+ /**
+ * Method to get the associations for a given item.
+ *
+ * @param integer $id Id of the item
+ * @param string $view Name of the view
+ *
+ * @return array Array of associations for the item
+ *
+ * @since 4.0.0
+ */
+ public function getAssociationsForItem($id = 0, $view = null)
+ {
+ return AssociationHelper::getAssociations($id, $view);
+ }
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public function getAssociations($typeName, $id)
+ {
+ $type = $this->getType($typeName);
+
+ $context = $this->extension . '.item';
+ $catidField = 'catid';
+
+ if ($typeName === 'category') {
+ $context = 'com_categories.item';
+ $catidField = '';
+ }
+
+ // Get the associations.
+ $associations = Associations::getAssociations(
+ $this->extension,
+ $type['tables']['a'],
+ $context,
+ $id,
+ 'id',
+ 'alias',
+ $catidField
+ );
+
+ return $associations;
+ }
+
+ /**
+ * Get item information
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return Table|null
+ *
+ * @since 3.7.0
+ */
+ public function getItem($typeName, $id)
+ {
+ if (empty($id)) {
+ return null;
+ }
+
+ $table = null;
+
+ switch ($typeName) {
+ case 'contact':
+ $table = Table::getInstance('ContactTable', 'Joomla\\Component\\Contact\\Administrator\\Table\\');
+ break;
+
+ case 'category':
+ $table = Table::getInstance('Category');
+ break;
+ }
+
+ if (empty($table)) {
+ return null;
+ }
+
+ $table->load($id);
+
+ return $table;
+ }
+
+ /**
+ * Get information about the type
+ *
+ * @param string $typeName The item type
+ *
+ * @return array Array of item types
+ *
+ * @since 3.7.0
+ */
+ public function getType($typeName = '')
+ {
+ $fields = $this->getFieldsTemplate();
+ $tables = array();
+ $joins = array();
+ $support = $this->getSupportTemplate();
+ $title = '';
+
+ if (in_array($typeName, $this->itemTypes)) {
+ switch ($typeName) {
+ case 'contact':
+ $fields['title'] = 'a.name';
+ $fields['state'] = 'a.published';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['category'] = true;
+ $support['save2copy'] = true;
+
+ $tables = array(
+ 'a' => '#__contact_details'
+ );
+
+ $title = 'contact';
+ break;
+
+ case 'category':
+ $fields['created_user_id'] = 'a.created_user_id';
+ $fields['ordering'] = 'a.lft';
+ $fields['level'] = 'a.level';
+ $fields['catid'] = '';
+ $fields['state'] = 'a.published';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['level'] = true;
+
+ $tables = array(
+ 'a' => '#__categories'
+ );
+
+ $title = 'category';
+ break;
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'support' => $support,
+ 'tables' => $tables,
+ 'joins' => $joins,
+ 'title' => $title
+ );
+ }
}
diff --git a/administrator/components/com_contact/src/Helper/ContactHelper.php b/administrator/components/com_contact/src/Helper/ContactHelper.php
index 8928555cc685d..a6857efffcd35 100644
--- a/administrator/components/com_contact/src/Helper/ContactHelper.php
+++ b/administrator/components/com_contact/src/Helper/ContactHelper.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage',
- 'tag' => 'batchTag',
- 'user_id' => 'batchUser',
- );
-
- /**
- * Name of the form
- *
- * @var string
- * @since 4.0.0
- */
- protected $formName = 'contact';
-
- /**
- * Batch change a linked user.
- *
- * @param integer $value The new value matching a User ID.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- protected function batchUser($value, $pks, $contexts)
- {
- foreach ($pks as $pk)
- {
- if ($this->user->authorise('core.edit', $contexts[$pk]))
- {
- $this->table->reset();
- $this->table->load($pk);
- $this->table->user_id = (int) $value;
-
- if (!$this->table->store())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
- }
- else
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.delete', 'com_contact.category.' . (int) $record->catid);
- }
-
- /**
- * Method to test whether a record can have its state edited.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- // Check against the category.
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.edit.state', 'com_contact.category.' . (int) $record->catid);
- }
-
- // Default to component settings if category not known.
- return parent::canEditState($record);
- }
-
- /**
- * Method to get the row form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- Form::addFieldPath(JPATH_ADMINISTRATOR . '/components/com_users/models/fields');
-
- // Get the form.
- $form = $this->loadForm('com_contact.' . $this->formName, $this->formName, array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('featured', 'disabled', 'true');
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('featured', 'filter', 'unset');
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- // Don't allow to change the created_by user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_by', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- if ($item = parent::getItem($pk))
- {
- // Convert the metadata field to an array.
- $registry = new Registry($item->metadata);
- $item->metadata = $registry->toArray();
- }
-
- // Load associated contact items
- $assoc = Associations::isEnabled();
-
- if ($assoc)
- {
- $item->associations = array();
-
- if ($item->id != null)
- {
- $associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', $item->id);
-
- foreach ($associations as $tag => $association)
- {
- $item->associations[$tag] = $association->id;
- }
- }
- }
-
- // Load item tags
- if (!empty($item->id))
- {
- $item->tags = new TagsHelper;
- $item->tags->getTagIds($item->id, 'com_contact.contact');
- }
-
- return $item;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- $app = Factory::getApplication();
-
- // Check the session for previously entered form data.
- $data = $app->getUserState('com_contact.edit.contact.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Prime some default values.
- if ($this->getState('contact.id') == 0)
- {
- $data->set('catid', $app->input->get('catid', $app->getUserState('com_contact.contacts.filter.category_id'), 'int'));
- }
- }
-
- $this->preprocessData('com_contact.contact', $data);
-
- return $data;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 3.0
- */
- public function save($data)
- {
- $input = Factory::getApplication()->input;
-
- // Create new category, if needed.
- $createCategory = true;
-
- // If category ID is provided, check if it's valid.
- if (is_numeric($data['catid']) && $data['catid'])
- {
- $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_contact');
- }
-
- // Save New Category
- if ($createCategory && $this->canCreateCategory())
- {
- $category = [
- // Remove #new# prefix, if exists.
- 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
- 'parent_id' => 1,
- 'extension' => 'com_contact',
- 'language' => $data['language'],
- 'published' => 1,
- ];
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
- $categoryModel = Factory::getApplication()->bootComponent('com_categories')
- ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
-
- // Create new category.
- if (!$categoryModel->save($category))
- {
- $this->setError($categoryModel->getError());
-
- return false;
- }
-
- // Get the Category ID.
- $data['catid'] = $categoryModel->getState('category.id');
- }
-
- // Alter the name for save as copy
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['name'] == $origTable->name)
- {
- list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
- $data['name'] = $name;
- $data['alias'] = $alias;
- }
- else
- {
- if ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
- }
-
- $data['published'] = 0;
- }
-
- $links = array('linka', 'linkb', 'linkc', 'linkd', 'linke');
-
- foreach ($links as $link)
- {
- if (!empty($data['params'][$link]))
- {
- $data['params'][$link] = PunycodeHelper::urlToPunycode($data['params'][$link]);
- }
- }
-
- return parent::save($data);
- }
-
- /**
- * Prepare and sanitise the table prior to saving.
- *
- * @param \Joomla\CMS\Table\Table $table The Table object
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- $date = Factory::getDate()->toSql();
-
- $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
-
- $table->generateAlias();
-
- if (empty($table->id))
- {
- // Set the values
- $table->created = $date;
-
- // Set ordering to the last item if not set
- if (empty($table->ordering))
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('MAX(ordering)')
- ->from($db->quoteName('#__contact_details'));
- $db->setQuery($query);
- $max = $db->loadResult();
-
- $table->ordering = $max + 1;
- }
- }
- else
- {
- // Set the values
- $table->modified = $date;
- $table->modified_by = Factory::getUser()->id;
- }
-
- // Increment the content version number.
- $table->version++;
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param \Joomla\CMS\Table\Table $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- return [
- $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
- ];
- }
-
- /**
- * Preprocess the form.
- *
- * @param Form $form Form object.
- * @param object $data Data object.
- * @param string $group Group name.
- *
- * @return void
- *
- * @since 3.0.3
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- if ($this->canCreateCategory())
- {
- $form->setFieldAttribute('catid', 'allowAdd', 'true');
-
- // Add a prefix for categories created on the fly.
- $form->setFieldAttribute('catid', 'customPrefix', '#new#');
- }
-
- // Association contact items
- if (Associations::isEnabled())
- {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (count($languages) > 1)
- {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language)
- {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_contact');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
- }
-
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to toggle the featured setting of contacts.
- *
- * @param array $pks The ids of the items to toggle.
- * @param integer $value The value to toggle to.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function featured($pks, $value = 0)
- {
- // Sanitize the ids.
- $pks = ArrayHelper::toInteger((array) $pks);
-
- if (empty($pks))
- {
- $this->setError(Text::_('COM_CONTACT_NO_ITEM_SELECTED'));
-
- return false;
- }
-
- $table = $this->getTable();
-
- try
- {
- $db = $this->getDatabase();
-
- $query = $db->getQuery(true);
- $query->update($db->quoteName('#__contact_details'));
- $query->set($db->quoteName('featured') . ' = :featured');
- $query->whereIn($db->quoteName('id'), $pks);
- $query->bind(':featured', $value, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $db->execute();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $table->reorder();
-
- // Clean component's cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Is the user allowed to create an on the fly category?
- *
- * @return boolean
- *
- * @since 3.6.1
- */
- private function canCreateCategory()
- {
- return Factory::getUser()->authorise('core.create', 'com_contact');
- }
+ use VersionableModelTrait;
+
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_contact.contact';
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_contact.item';
+
+ /**
+ * Batch copy/move command. If set to false, the batch copy/move command is not supported
+ *
+ * @var string
+ */
+ protected $batch_copymove = 'category_id';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage',
+ 'tag' => 'batchTag',
+ 'user_id' => 'batchUser',
+ );
+
+ /**
+ * Name of the form
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $formName = 'contact';
+
+ /**
+ * Batch change a linked user.
+ *
+ * @param integer $value The new value matching a User ID.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ protected function batchUser($value, $pks, $contexts)
+ {
+ foreach ($pks as $pk) {
+ if ($this->user->authorise('core.edit', $contexts[$pk])) {
+ $this->table->reset();
+ $this->table->load($pk);
+ $this->table->user_id = (int) $value;
+
+ if (!$this->table->store()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+ } else {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.delete', 'com_contact.category.' . (int) $record->catid);
+ }
+
+ /**
+ * Method to test whether a record can have its state edited.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ // Check against the category.
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.edit.state', 'com_contact.category.' . (int) $record->catid);
+ }
+
+ // Default to component settings if category not known.
+ return parent::canEditState($record);
+ }
+
+ /**
+ * Method to get the row form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ Form::addFieldPath(JPATH_ADMINISTRATOR . '/components/com_users/models/fields');
+
+ // Get the form.
+ $form = $this->loadForm('com_contact.' . $this->formName, $this->formName, array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('featured', 'disabled', 'true');
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('featured', 'filter', 'unset');
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ if ($item = parent::getItem($pk)) {
+ // Convert the metadata field to an array.
+ $registry = new Registry($item->metadata);
+ $item->metadata = $registry->toArray();
+ }
+
+ // Load associated contact items
+ $assoc = Associations::isEnabled();
+
+ if ($assoc) {
+ $item->associations = array();
+
+ if ($item->id != null) {
+ $associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', $item->id);
+
+ foreach ($associations as $tag => $association) {
+ $item->associations[$tag] = $association->id;
+ }
+ }
+ }
+
+ // Load item tags
+ if (!empty($item->id)) {
+ $item->tags = new TagsHelper();
+ $item->tags->getTagIds($item->id, 'com_contact.contact');
+ }
+
+ return $item;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ $app = Factory::getApplication();
+
+ // Check the session for previously entered form data.
+ $data = $app->getUserState('com_contact.edit.contact.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Prime some default values.
+ if ($this->getState('contact.id') == 0) {
+ $data->set('catid', $app->input->get('catid', $app->getUserState('com_contact.contacts.filter.category_id'), 'int'));
+ }
+ }
+
+ $this->preprocessData('com_contact.contact', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.0
+ */
+ public function save($data)
+ {
+ $input = Factory::getApplication()->input;
+
+ // Create new category, if needed.
+ $createCategory = true;
+
+ // If category ID is provided, check if it's valid.
+ if (is_numeric($data['catid']) && $data['catid']) {
+ $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_contact');
+ }
+
+ // Save New Category
+ if ($createCategory && $this->canCreateCategory()) {
+ $category = [
+ // Remove #new# prefix, if exists.
+ 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
+ 'parent_id' => 1,
+ 'extension' => 'com_contact',
+ 'language' => $data['language'],
+ 'published' => 1,
+ ];
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
+ $categoryModel = Factory::getApplication()->bootComponent('com_categories')
+ ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
+
+ // Create new category.
+ if (!$categoryModel->save($category)) {
+ $this->setError($categoryModel->getError());
+
+ return false;
+ }
+
+ // Get the Category ID.
+ $data['catid'] = $categoryModel->getState('category.id');
+ }
+
+ // Alter the name for save as copy
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['name'] == $origTable->name) {
+ list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
+ $data['name'] = $name;
+ $data['alias'] = $alias;
+ } else {
+ if ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+ }
+
+ $data['published'] = 0;
+ }
+
+ $links = array('linka', 'linkb', 'linkc', 'linkd', 'linke');
+
+ foreach ($links as $link) {
+ if (!empty($data['params'][$link])) {
+ $data['params'][$link] = PunycodeHelper::urlToPunycode($data['params'][$link]);
+ }
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Prepare and sanitise the table prior to saving.
+ *
+ * @param \Joomla\CMS\Table\Table $table The Table object
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ $date = Factory::getDate()->toSql();
+
+ $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
+
+ $table->generateAlias();
+
+ if (empty($table->id)) {
+ // Set the values
+ $table->created = $date;
+
+ // Set ordering to the last item if not set
+ if (empty($table->ordering)) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('MAX(ordering)')
+ ->from($db->quoteName('#__contact_details'));
+ $db->setQuery($query);
+ $max = $db->loadResult();
+
+ $table->ordering = $max + 1;
+ }
+ } else {
+ // Set the values
+ $table->modified = $date;
+ $table->modified_by = Factory::getUser()->id;
+ }
+
+ // Increment the content version number.
+ $table->version++;
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param \Joomla\CMS\Table\Table $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ return [
+ $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
+ ];
+ }
+
+ /**
+ * Preprocess the form.
+ *
+ * @param Form $form Form object.
+ * @param object $data Data object.
+ * @param string $group Group name.
+ *
+ * @return void
+ *
+ * @since 3.0.3
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ if ($this->canCreateCategory()) {
+ $form->setFieldAttribute('catid', 'allowAdd', 'true');
+
+ // Add a prefix for categories created on the fly.
+ $form->setFieldAttribute('catid', 'customPrefix', '#new#');
+ }
+
+ // Association contact items
+ if (Associations::isEnabled()) {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (count($languages) > 1) {
+ $addform = new \SimpleXMLElement('');
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('type', 'modal_contact');
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+ }
+
+ $form->load($addform, false);
+ }
+ }
+
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to toggle the featured setting of contacts.
+ *
+ * @param array $pks The ids of the items to toggle.
+ * @param integer $value The value to toggle to.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function featured($pks, $value = 0)
+ {
+ // Sanitize the ids.
+ $pks = ArrayHelper::toInteger((array) $pks);
+
+ if (empty($pks)) {
+ $this->setError(Text::_('COM_CONTACT_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ $table = $this->getTable();
+
+ try {
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true);
+ $query->update($db->quoteName('#__contact_details'));
+ $query->set($db->quoteName('featured') . ' = :featured');
+ $query->whereIn($db->quoteName('id'), $pks);
+ $query->bind(':featured', $value, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $db->execute();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $table->reorder();
+
+ // Clean component's cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Is the user allowed to create an on the fly category?
+ *
+ * @return boolean
+ *
+ * @since 3.6.1
+ */
+ private function canCreateCategory()
+ {
+ return Factory::getUser()->authorise('core.create', 'com_contact');
+ }
}
diff --git a/administrator/components/com_contact/src/Model/ContactsModel.php b/administrator/components/com_contact/src/Model/ContactsModel.php
index f4adaf4746963..d49442ffe5c11 100644
--- a/administrator/components/com_contact/src/Model/ContactsModel.php
+++ b/administrator/components/com_contact/src/Model/ContactsModel.php
@@ -1,4 +1,5 @@
input->get('forcedLanguage', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language.
- if (!empty($forcedLanguage))
- {
- $this->setState('filter.language', $forcedLanguage);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . serialize($this->getState('filter.category_id'));
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . serialize($this->getState('filter.tag'));
- $id .= ':' . $this->getState('filter.level');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- // Select the required fields from the table.
- $query->select(
- $db->quoteName(
- explode(
- ', ',
- $this->getState(
- 'list.select',
- 'a.id, a.name, a.alias, a.checked_out, a.checked_out_time, a.catid, a.user_id' .
- ', a.published, a.access, a.created, a.created_by, a.ordering, a.featured, a.language' .
- ', a.publish_up, a.publish_down'
- )
- )
- )
- );
- $query->from($db->quoteName('#__contact_details', 'a'));
-
- // Join over the users for the linked user.
- $query->select(
- array(
- $db->quoteName('ul.name', 'linked_user'),
- $db->quoteName('ul.email')
- )
- )
- ->join(
- 'LEFT',
- $db->quoteName('#__users', 'ul') . ' ON ' . $db->quoteName('ul.id') . ' = ' . $db->quoteName('a.user_id')
- );
-
- // Join over the language
- $query->select($db->quoteName('l.title', 'language_title'))
- ->select($db->quoteName('l.image', 'language_image'))
- ->join(
- 'LEFT',
- $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')
- );
-
- // Join over the users for the checked out user.
- $query->select($db->quoteName('uc.name', 'editor'))
- ->join(
- 'LEFT',
- $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')
- );
-
- // Join over the asset groups.
- $query->select($db->quoteName('ag.title', 'access_level'))
- ->join(
- 'LEFT',
- $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')
- );
-
- // Join over the categories.
- $query->select($db->quoteName('c.title', 'category_title'))
- ->join(
- 'LEFT',
- $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid')
- );
-
- // Join over the associations.
- if (Associations::isEnabled())
- {
- $subQuery = $db->getQuery(true)
- ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
- ->from($db->quoteName('#__associations', 'asso1'))
- ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
- ->where(
- [
- $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
- $db->quoteName('asso1.context') . ' = ' . $db->quote('com_contact.item'),
- ]
- );
-
- $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
- }
-
- // Filter by featured.
- $featured = (string) $this->getState('filter.featured');
-
- if (in_array($featured, ['0','1']))
- {
- $query->where($db->quoteName('a.featured') . ' = ' . (int) $featured);
- }
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- $query->where($db->quoteName('a.access') . ' = :access');
- $query->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels());
- }
-
- // Filter by published state
- $published = (string) $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $query->where($db->quoteName('a.published') . ' = :published');
- $query->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)');
- }
-
- // Filter by search in name.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . trim($search) . '%';
- $query->where(
- '(' . $db->quoteName('a.name') . ' LIKE :name OR ' . $db->quoteName('a.alias') . ' LIKE :alias)'
- );
- $query->bind(':name', $search);
- $query->bind(':alias', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language');
- $query->bind(':language', $language);
- }
-
- // Filter by a single or group of tags.
- $tag = $this->getState('filter.tag');
-
- // Run simplified query when filtering by one tag.
- if (\is_array($tag) && \count($tag) === 1)
- {
- $tag = $tag[0];
- }
-
- if ($tag && \is_array($tag))
- {
- $tag = ArrayHelper::toInteger($tag);
-
- $subQuery = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('content_item_id'))
- ->from($db->quoteName('#__contentitem_tag_map'))
- ->where(
- [
- $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
- $db->quoteName('type_alias') . ' = ' . $db->quote('com_contact.contact'),
- ]
- );
-
- $query->join(
- 'INNER',
- '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- );
- }
- elseif ($tag = (int) $tag)
- {
- $query->join(
- 'INNER',
- $db->quoteName('#__contentitem_tag_map', 'tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- )
- ->where(
- [
- $db->quoteName('tagmap.tag_id') . ' = :tag',
- $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_contact.contact'),
- ]
- )
- ->bind(':tag', $tag, ParameterType::INTEGER);
- }
-
- // Filter by categories and by level
- $categoryId = $this->getState('filter.category_id', array());
- $level = $this->getState('filter.level');
-
- if (!is_array($categoryId))
- {
- $categoryId = $categoryId ? array($categoryId) : array();
- }
-
- // Case: Using both categories filter and by level filter
- if (count($categoryId))
- {
- $categoryId = ArrayHelper::toInteger($categoryId);
- $categoryTable = Table::getInstance('Category', 'JTable');
- $subCatItemsWhere = array();
-
- // @todo: Convert to prepared statement
- foreach ($categoryId as $filter_catid)
- {
- $categoryTable->load($filter_catid);
- $subCatItemsWhere[] = '(' .
- ($level ? 'c.level <= ' . ((int) $level + (int) $categoryTable->level - 1) . ' AND ' : '') .
- 'c.lft >= ' . (int) $categoryTable->lft . ' AND ' .
- 'c.rgt <= ' . (int) $categoryTable->rgt . ')';
- }
-
- $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')');
- }
-
- // Case: Using only the by level filter
- elseif ($level)
- {
- $query->where($db->quoteName('c.level') . ' <= :level');
- $query->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 'a.name');
- $orderDirn = $this->state->get('list.direction', 'asc');
-
- if ($orderCol == 'a.ordering' || $orderCol == 'category_title')
- {
- $orderCol = $db->quoteName('c.title') . ' ' . $orderDirn . ', ' . $db->quoteName('a.ordering');
- }
-
- $query->order($db->escape($orderCol . ' ' . $orderDirn));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'name', 'a.name',
+ 'alias', 'a.alias',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'catid', 'a.catid', 'category_id', 'category_title',
+ 'user_id', 'a.user_id',
+ 'published', 'a.published',
+ 'access', 'a.access', 'access_level',
+ 'created', 'a.created',
+ 'created_by', 'a.created_by',
+ 'ordering', 'a.ordering',
+ 'featured', 'a.featured',
+ 'language', 'a.language', 'language_title',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'ul.name', 'linked_user',
+ 'tag',
+ 'level', 'c.level',
+ );
+
+ if (Associations::isEnabled()) {
+ $config['filter_fields'][] = 'association';
+ }
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.name', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language.
+ if (!empty($forcedLanguage)) {
+ $this->setState('filter.language', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . serialize($this->getState('filter.category_id'));
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . serialize($this->getState('filter.tag'));
+ $id .= ':' . $this->getState('filter.level');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ // Select the required fields from the table.
+ $query->select(
+ $db->quoteName(
+ explode(
+ ', ',
+ $this->getState(
+ 'list.select',
+ 'a.id, a.name, a.alias, a.checked_out, a.checked_out_time, a.catid, a.user_id' .
+ ', a.published, a.access, a.created, a.created_by, a.ordering, a.featured, a.language' .
+ ', a.publish_up, a.publish_down'
+ )
+ )
+ )
+ );
+ $query->from($db->quoteName('#__contact_details', 'a'));
+
+ // Join over the users for the linked user.
+ $query->select(
+ array(
+ $db->quoteName('ul.name', 'linked_user'),
+ $db->quoteName('ul.email')
+ )
+ )
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'ul') . ' ON ' . $db->quoteName('ul.id') . ' = ' . $db->quoteName('a.user_id')
+ );
+
+ // Join over the language
+ $query->select($db->quoteName('l.title', 'language_title'))
+ ->select($db->quoteName('l.image', 'language_image'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language')
+ );
+
+ // Join over the users for the checked out user.
+ $query->select($db->quoteName('uc.name', 'editor'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out')
+ );
+
+ // Join over the asset groups.
+ $query->select($db->quoteName('ag.title', 'access_level'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access')
+ );
+
+ // Join over the categories.
+ $query->select($db->quoteName('c.title', 'category_title'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid')
+ );
+
+ // Join over the associations.
+ if (Associations::isEnabled()) {
+ $subQuery = $db->getQuery(true)
+ ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
+ ->from($db->quoteName('#__associations', 'asso1'))
+ ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
+ ->where(
+ [
+ $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
+ $db->quoteName('asso1.context') . ' = ' . $db->quote('com_contact.item'),
+ ]
+ );
+
+ $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+ }
+
+ // Filter by featured.
+ $featured = (string) $this->getState('filter.featured');
+
+ if (in_array($featured, ['0','1'])) {
+ $query->where($db->quoteName('a.featured') . ' = ' . (int) $featured);
+ }
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ $query->where($db->quoteName('a.access') . ' = :access');
+ $query->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels());
+ }
+
+ // Filter by published state
+ $published = (string) $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $query->where($db->quoteName('a.published') . ' = :published');
+ $query->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->where('(' . $db->quoteName('a.published') . ' = 0 OR ' . $db->quoteName('a.published') . ' = 1)');
+ }
+
+ // Filter by search in name.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search = substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . trim($search) . '%';
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' LIKE :name OR ' . $db->quoteName('a.alias') . ' LIKE :alias)'
+ );
+ $query->bind(':name', $search);
+ $query->bind(':alias', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language');
+ $query->bind(':language', $language);
+ }
+
+ // Filter by a single or group of tags.
+ $tag = $this->getState('filter.tag');
+
+ // Run simplified query when filtering by one tag.
+ if (\is_array($tag) && \count($tag) === 1) {
+ $tag = $tag[0];
+ }
+
+ if ($tag && \is_array($tag)) {
+ $tag = ArrayHelper::toInteger($tag);
+
+ $subQuery = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('content_item_id'))
+ ->from($db->quoteName('#__contentitem_tag_map'))
+ ->where(
+ [
+ $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
+ $db->quoteName('type_alias') . ' = ' . $db->quote('com_contact.contact'),
+ ]
+ );
+
+ $query->join(
+ 'INNER',
+ '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ );
+ } elseif ($tag = (int) $tag) {
+ $query->join(
+ 'INNER',
+ $db->quoteName('#__contentitem_tag_map', 'tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ )
+ ->where(
+ [
+ $db->quoteName('tagmap.tag_id') . ' = :tag',
+ $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_contact.contact'),
+ ]
+ )
+ ->bind(':tag', $tag, ParameterType::INTEGER);
+ }
+
+ // Filter by categories and by level
+ $categoryId = $this->getState('filter.category_id', array());
+ $level = $this->getState('filter.level');
+
+ if (!is_array($categoryId)) {
+ $categoryId = $categoryId ? array($categoryId) : array();
+ }
+
+ // Case: Using both categories filter and by level filter
+ if (count($categoryId)) {
+ $categoryId = ArrayHelper::toInteger($categoryId);
+ $categoryTable = Table::getInstance('Category', 'JTable');
+ $subCatItemsWhere = array();
+
+ // @todo: Convert to prepared statement
+ foreach ($categoryId as $filter_catid) {
+ $categoryTable->load($filter_catid);
+ $subCatItemsWhere[] = '(' .
+ ($level ? 'c.level <= ' . ((int) $level + (int) $categoryTable->level - 1) . ' AND ' : '') .
+ 'c.lft >= ' . (int) $categoryTable->lft . ' AND ' .
+ 'c.rgt <= ' . (int) $categoryTable->rgt . ')';
+ }
+
+ $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')');
+ } elseif ($level) {
+ // Case: Using only the by level filter
+ $query->where($db->quoteName('c.level') . ' <= :level');
+ $query->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 'a.name');
+ $orderDirn = $this->state->get('list.direction', 'asc');
+
+ if ($orderCol == 'a.ordering' || $orderCol == 'category_title') {
+ $orderCol = $db->quoteName('c.title') . ' ' . $orderDirn . ', ' . $db->quoteName('a.ordering');
+ }
+
+ $query->order($db->escape($orderCol . ' ' . $orderDirn));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_contact/src/Service/HTML/AdministratorService.php b/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
index 7255f69339d97..beb84949e984a 100644
--- a/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_contact/src/Service/HTML/AdministratorService.php
@@ -1,4 +1,5 @@
$associated)
- {
- $associations[$tag] = (int) $associated->id;
- }
-
- // Get the associated contact items
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('c.id'),
- $db->quoteName('c.name', 'title'),
- $db->quoteName('l.sef', 'lang_sef'),
- $db->quoteName('lang_code'),
- $db->quoteName('cat.title', 'category_title'),
- $db->quoteName('l.image'),
- $db->quoteName('l.title', 'language_title'),
- ]
- )
- ->from($db->quoteName('#__contact_details', 'c'))
- ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
- ->whereIn($db->quoteName('c.id'), array_values($associations))
- ->where($db->quoteName('c.id') . ' != :id')
- ->bind(':id', $contactid, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $items = $db->loadObjectList('id');
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500, $e);
- }
-
- if ($items)
- {
- $languages = LanguageHelper::getContentLanguages(array(0, 1));
- $content_languages = array_column($languages, 'lang_code');
-
- foreach ($items as &$item)
- {
- if (in_array($item->lang_code, $content_languages))
- {
- $text = $item->lang_code;
- $url = Route::_('index.php?option=com_contact&task=contact.edit&id=' . (int) $item->id);
- $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
- $classes = 'badge bg-secondary';
-
- $item->link = '' . $text . ' '
- . '' . $tooltip . '
';
- }
- else
- {
- // Display warning if Content Language is trashed or deleted
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
- }
- }
- }
-
- $html = LayoutHelper::render('joomla.content.associations', $items);
- }
-
- return $html;
- }
-
- /**
- * Show the featured/not-featured icon.
- *
- * @param integer $value The featured value.
- * @param integer $i Id of the item.
- * @param boolean $canChange Whether the value can be changed or not.
- *
- * @return string The anchor tag to toggle featured/unfeatured contacts.
- *
- * @since 1.6
- */
- public function featured($value, $i, $canChange = true)
- {
- // Array of image, task, title, action
- $states = array(
- 0 => array('unfeatured', 'contacts.featured', 'COM_CONTACT_UNFEATURED', 'JGLOBAL_ITEM_FEATURE'),
- 1 => array('featured', 'contacts.unfeatured', 'JFEATURED', 'JGLOBAL_ITEM_UNFEATURE'),
- );
- $state = ArrayHelper::getValue($states, (int) $value, $states[1]);
- $icon = $state[0] === 'featured' ? 'star featured' : 'circle';
- $onclick = 'onclick="return Joomla.listItemTask(\'cb' . $i . '\',\'' . $state[1] . '\')"';
- $tooltipText = Text::_($state[3]);
-
- if (!$canChange)
- {
- $onclick = 'disabled';
- $tooltipText = Text::_($state[2]);
- }
-
- $html = ''
- . ' '
- . ' '
- . '' . $tooltipText . '
';
-
- return $html;
- }
+ /**
+ * Get the associated language flags
+ *
+ * @param integer $contactid The item id to search associations
+ *
+ * @return string The language HTML
+ *
+ * @throws \Exception
+ */
+ public function association($contactid)
+ {
+ // Defaults
+ $html = '';
+
+ // Get the associations
+ if ($associations = Associations::getAssociations('com_contact', '#__contact_details', 'com_contact.item', $contactid)) {
+ foreach ($associations as $tag => $associated) {
+ $associations[$tag] = (int) $associated->id;
+ }
+
+ // Get the associated contact items
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('c.id'),
+ $db->quoteName('c.name', 'title'),
+ $db->quoteName('l.sef', 'lang_sef'),
+ $db->quoteName('lang_code'),
+ $db->quoteName('cat.title', 'category_title'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.title', 'language_title'),
+ ]
+ )
+ ->from($db->quoteName('#__contact_details', 'c'))
+ ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
+ ->whereIn($db->quoteName('c.id'), array_values($associations))
+ ->where($db->quoteName('c.id') . ' != :id')
+ ->bind(':id', $contactid, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $items = $db->loadObjectList('id');
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500, $e);
+ }
+
+ if ($items) {
+ $languages = LanguageHelper::getContentLanguages(array(0, 1));
+ $content_languages = array_column($languages, 'lang_code');
+
+ foreach ($items as &$item) {
+ if (in_array($item->lang_code, $content_languages)) {
+ $text = $item->lang_code;
+ $url = Route::_('index.php?option=com_contact&task=contact.edit&id=' . (int) $item->id);
+ $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
+ $classes = 'badge bg-secondary';
+
+ $item->link = '' . $text . ' '
+ . '' . $tooltip . '
';
+ } else {
+ // Display warning if Content Language is trashed or deleted
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
+ }
+ }
+ }
+
+ $html = LayoutHelper::render('joomla.content.associations', $items);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Show the featured/not-featured icon.
+ *
+ * @param integer $value The featured value.
+ * @param integer $i Id of the item.
+ * @param boolean $canChange Whether the value can be changed or not.
+ *
+ * @return string The anchor tag to toggle featured/unfeatured contacts.
+ *
+ * @since 1.6
+ */
+ public function featured($value, $i, $canChange = true)
+ {
+ // Array of image, task, title, action
+ $states = array(
+ 0 => array('unfeatured', 'contacts.featured', 'COM_CONTACT_UNFEATURED', 'JGLOBAL_ITEM_FEATURE'),
+ 1 => array('featured', 'contacts.unfeatured', 'JFEATURED', 'JGLOBAL_ITEM_UNFEATURE'),
+ );
+ $state = ArrayHelper::getValue($states, (int) $value, $states[1]);
+ $icon = $state[0] === 'featured' ? 'star featured' : 'circle';
+ $onclick = 'onclick="return Joomla.listItemTask(\'cb' . $i . '\',\'' . $state[1] . '\')"';
+ $tooltipText = Text::_($state[3]);
+
+ if (!$canChange) {
+ $onclick = 'disabled';
+ $tooltipText = Text::_($state[2]);
+ }
+
+ $html = ''
+ . ' '
+ . ' '
+ . '' . $tooltipText . '
';
+
+ return $html;
+ }
}
diff --git a/administrator/components/com_contact/src/Service/HTML/Icon.php b/administrator/components/com_contact/src/Service/HTML/Icon.php
index 07bb27759e342..a3ebe1a55b1cd 100644
--- a/administrator/components/com_contact/src/Service/HTML/Icon.php
+++ b/administrator/components/com_contact/src/Service/HTML/Icon.php
@@ -1,4 +1,5 @@
userFactory = $userFactory;
- }
-
- /**
- * Method to generate a link to the create item page for the given category
- *
- * @param object $category The category information
- * @param Registry $params The item parameters
- * @param array $attribs Optional attributes for the link
- *
- * @return string The HTML markup for the create item link
- *
- * @since 4.0.0
- */
- public function create($category, $params, $attribs = array())
- {
- $uri = Uri::getInstance();
-
- $url = 'index.php?option=com_contact&task=contact.add&return=' . base64_encode($uri) . '&id=0&catid=' . $category->id;
-
- $text = '';
-
- if ($params->get('show_icons'))
- {
- $text .= ' ';
- }
-
- $text .= Text::_('COM_CONTACT_NEW_CONTACT');
-
- // Add the button classes to the attribs array
- if (isset($attribs['class']))
- {
- $attribs['class'] .= ' btn btn-primary';
- }
- else
- {
- $attribs['class'] = 'btn btn-primary';
- }
-
- $button = HTMLHelper::_('link', Route::_($url), $text, $attribs);
-
- return $button;
- }
-
- /**
- * Display an edit icon for the contact.
- *
- * This icon will not display in a popup window, nor if the contact is trashed.
- * Edit access checks must be performed in the calling code.
- *
- * @param object $contact The contact information
- * @param Registry $params The item parameters
- * @param array $attribs Optional attributes for the link
- * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
- *
- * @return string The HTML for the contact edit icon.
- *
- * @since 4.0.0
- */
- public function edit($contact, $params, $attribs = array(), $legacy = false)
- {
- $user = Factory::getUser();
- $uri = Uri::getInstance();
-
- // Ignore if in a popup window.
- if ($params && $params->get('popup'))
- {
- return '';
- }
-
- // Ignore if the state is negative (trashed).
- if ($contact->published < 0)
- {
- return '';
- }
-
- // Show checked_out icon if the contact is checked out by a different user
- if (property_exists($contact, 'checked_out')
- && property_exists($contact, 'checked_out_time')
- && !is_null($contact->checked_out)
- && $contact->checked_out !== $user->get('id'))
- {
- $checkoutUser = $this->userFactory->loadUserById($contact->checked_out);
- $date = HTMLHelper::_('date', $contact->checked_out_time);
- $tooltip = Text::sprintf('COM_CONTACT_CHECKED_OUT_BY', $checkoutUser->name)
- . ' ' . $date;
-
- $text = LayoutHelper::render('joomla.content.icons.edit_lock', array('contact' => $contact, 'tooltip' => $tooltip, 'legacy' => $legacy));
-
- $attribs['aria-describedby'] = 'editcontact-' . (int) $contact->id;
- $output = HTMLHelper::_('link', '#', $text, $attribs);
-
- return $output;
- }
-
- $contactUrl = RouteHelper::getContactRoute($contact->slug, $contact->catid, $contact->language);
- $url = $contactUrl . '&task=contact.edit&id=' . $contact->id . '&return=' . base64_encode($uri);
-
- if ((int) $contact->published === 0)
- {
- $tooltip = Text::_('COM_CONTACT_EDIT_UNPUBLISHED_CONTACT');
- }
- else
- {
- $tooltip = Text::_('COM_CONTACT_EDIT_PUBLISHED_CONTACT');
- }
-
- $nowDate = strtotime(Factory::getDate());
- $icon = $contact->published ? 'edit' : 'eye-slash';
-
- if (($contact->publish_up !== null && strtotime($contact->publish_up) > $nowDate)
- || ($contact->publish_down !== null && strtotime($contact->publish_down) < $nowDate))
- {
- $icon = 'eye-slash';
- }
-
- $aria_described = 'editcontact-' . (int) $contact->id;
-
- $text = ' ';
- $text .= Text::_('JGLOBAL_EDIT');
- $text .= '' . $tooltip . '
';
-
- $attribs['aria-describedby'] = $aria_described;
- $output = HTMLHelper::_('link', Route::_($url), $text, $attribs);
-
- return $output;
- }
+ /**
+ * The user factory
+ *
+ * @var UserFactoryInterface
+ *
+ * @since 4.2.0
+ */
+ private $userFactory;
+
+ /**
+ * Service constructor
+ *
+ * @param UserFactoryInterface $userFactory The userFactory
+ *
+ * @since 4.0.0
+ */
+ public function __construct(UserFactoryInterface $userFactory)
+ {
+ $this->userFactory = $userFactory;
+ }
+
+ /**
+ * Method to generate a link to the create item page for the given category
+ *
+ * @param object $category The category information
+ * @param Registry $params The item parameters
+ * @param array $attribs Optional attributes for the link
+ *
+ * @return string The HTML markup for the create item link
+ *
+ * @since 4.0.0
+ */
+ public function create($category, $params, $attribs = array())
+ {
+ $uri = Uri::getInstance();
+
+ $url = 'index.php?option=com_contact&task=contact.add&return=' . base64_encode($uri) . '&id=0&catid=' . $category->id;
+
+ $text = '';
+
+ if ($params->get('show_icons')) {
+ $text .= ' ';
+ }
+
+ $text .= Text::_('COM_CONTACT_NEW_CONTACT');
+
+ // Add the button classes to the attribs array
+ if (isset($attribs['class'])) {
+ $attribs['class'] .= ' btn btn-primary';
+ } else {
+ $attribs['class'] = 'btn btn-primary';
+ }
+
+ $button = HTMLHelper::_('link', Route::_($url), $text, $attribs);
+
+ return $button;
+ }
+
+ /**
+ * Display an edit icon for the contact.
+ *
+ * This icon will not display in a popup window, nor if the contact is trashed.
+ * Edit access checks must be performed in the calling code.
+ *
+ * @param object $contact The contact information
+ * @param Registry $params The item parameters
+ * @param array $attribs Optional attributes for the link
+ * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
+ *
+ * @return string The HTML for the contact edit icon.
+ *
+ * @since 4.0.0
+ */
+ public function edit($contact, $params, $attribs = array(), $legacy = false)
+ {
+ $user = Factory::getUser();
+ $uri = Uri::getInstance();
+
+ // Ignore if in a popup window.
+ if ($params && $params->get('popup')) {
+ return '';
+ }
+
+ // Ignore if the state is negative (trashed).
+ if ($contact->published < 0) {
+ return '';
+ }
+
+ // Show checked_out icon if the contact is checked out by a different user
+ if (
+ property_exists($contact, 'checked_out')
+ && property_exists($contact, 'checked_out_time')
+ && !is_null($contact->checked_out)
+ && $contact->checked_out !== $user->get('id')
+ ) {
+ $checkoutUser = $this->userFactory->loadUserById($contact->checked_out);
+ $date = HTMLHelper::_('date', $contact->checked_out_time);
+ $tooltip = Text::sprintf('COM_CONTACT_CHECKED_OUT_BY', $checkoutUser->name)
+ . ' ' . $date;
+
+ $text = LayoutHelper::render('joomla.content.icons.edit_lock', array('contact' => $contact, 'tooltip' => $tooltip, 'legacy' => $legacy));
+
+ $attribs['aria-describedby'] = 'editcontact-' . (int) $contact->id;
+ $output = HTMLHelper::_('link', '#', $text, $attribs);
+
+ return $output;
+ }
+
+ $contactUrl = RouteHelper::getContactRoute($contact->slug, $contact->catid, $contact->language);
+ $url = $contactUrl . '&task=contact.edit&id=' . $contact->id . '&return=' . base64_encode($uri);
+
+ if ((int) $contact->published === 0) {
+ $tooltip = Text::_('COM_CONTACT_EDIT_UNPUBLISHED_CONTACT');
+ } else {
+ $tooltip = Text::_('COM_CONTACT_EDIT_PUBLISHED_CONTACT');
+ }
+
+ $nowDate = strtotime(Factory::getDate());
+ $icon = $contact->published ? 'edit' : 'eye-slash';
+
+ if (
+ ($contact->publish_up !== null && strtotime($contact->publish_up) > $nowDate)
+ || ($contact->publish_down !== null && strtotime($contact->publish_down) < $nowDate)
+ ) {
+ $icon = 'eye-slash';
+ }
+
+ $aria_described = 'editcontact-' . (int) $contact->id;
+
+ $text = ' ';
+ $text .= Text::_('JGLOBAL_EDIT');
+ $text .= '' . $tooltip . '
';
+
+ $attribs['aria-describedby'] = $aria_described;
+ $output = HTMLHelper::_('link', Route::_($url), $text, $attribs);
+
+ return $output;
+ }
}
diff --git a/administrator/components/com_contact/src/Table/ContactTable.php b/administrator/components/com_contact/src/Table/ContactTable.php
index 4748f0ed52217..697b9fd765cf9 100644
--- a/administrator/components/com_contact/src/Table/ContactTable.php
+++ b/administrator/components/com_contact/src/Table/ContactTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_contact.contact';
-
- parent::__construct('#__contact_details', 'id', $db);
-
- $this->setColumnAlias('title', 'name');
- }
-
- /**
- * Stores a contact.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 1.6
- */
- public function store($updateNulls = true)
- {
- $date = Factory::getDate()->toSql();
- $userId = Factory::getUser()->id;
-
- // Set created date if not set.
- if (!(int) $this->created)
- {
- $this->created = $date;
- }
-
- if ($this->id)
- {
- // Existing item
- $this->modified_by = $userId;
- $this->modified = $date;
- }
- else
- {
- // Field created_by field can be set by the user, so we don't touch it if it's set.
- if (empty($this->created_by))
- {
- $this->created_by = $userId;
- }
-
- if (!(int) $this->modified)
- {
- $this->modified = $date;
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $userId;
- }
- }
-
- // Store utf8 email as punycode
- if ($this->email_to !== null)
- {
- $this->email_to = PunycodeHelper::emailToPunycode($this->email_to);
- }
-
- // Convert IDN urls to punycode
- if ($this->webpage !== null)
- {
- $this->webpage = PunycodeHelper::urlToPunycode($this->webpage);
- }
-
- // Verify that the alias is unique
- $table = Table::getInstance('ContactTable', __NAMESPACE__ . '\\', array('dbo' => $this->getDbo()));
-
- if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0))
- {
- $this->setError(Text::_('COM_CONTACT_ERROR_UNIQUE_ALIAS'));
-
- return false;
- }
-
- return parent::store($updateNulls);
- }
-
- /**
- * Overloaded check function
- *
- * @return boolean True on success, false on failure
- *
- * @see \JTable::check
- * @since 1.5
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $this->default_con = (int) $this->default_con;
-
- if ($this->webpage !== null && InputFilter::checkAttribute(array('href', $this->webpage)))
- {
- $this->setError(Text::_('COM_CONTACT_WARNING_PROVIDE_VALID_URL'));
-
- return false;
- }
-
- // Check for valid name
- if (trim($this->name) == '')
- {
- $this->setError(Text::_('COM_CONTACT_WARNING_PROVIDE_VALID_NAME'));
-
- return false;
- }
-
- // Generate a valid alias
- $this->generateAlias();
-
- // Check for a valid category.
- if (!$this->catid = (int) $this->catid)
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
-
- return false;
- }
-
- // Sanity check for user_id
- if (!$this->user_id)
- {
- $this->user_id = 0;
- }
-
- // Check the publish down date is not earlier than publish up.
- if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up)
- {
- $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
-
- return false;
- }
-
- if (!$this->id)
- {
- // Hits must be zero on a new item
- $this->hits = 0;
- }
-
- // Clean up description -- eliminate quotes and <> brackets
- if (!empty($this->metadesc))
- {
- // Only process if not empty
- $badCharacters = array("\"", '<', '>');
- $this->metadesc = StringHelper::str_ireplace($badCharacters, '', $this->metadesc);
- }
- else
- {
- $this->metadesc = '';
- }
-
- if (empty($this->params))
- {
- $this->params = '{}';
- }
-
- if (empty($this->metadata))
- {
- $this->metadata = '{}';
- }
-
- // Set publish_up, publish_down to null if not set
- if (!$this->publish_up)
- {
- $this->publish_up = null;
- }
-
- if (!$this->publish_down)
- {
- $this->publish_down = null;
- }
-
- if (!$this->modified)
- {
- $this->modified = $this->created;
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_by;
- }
-
- return true;
- }
-
- /**
- * Generate a valid alias from title / date.
- * Remains public to be able to check for duplicated alias before saving
- *
- * @return string
- */
- public function generateAlias()
- {
- if (empty($this->alias))
- {
- $this->alias = $this->name;
- }
-
- $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
-
- if (trim(str_replace('-', '', $this->alias)) == '')
- {
- $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
- }
-
- return $this->alias;
- }
-
-
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ use TaggableTableTrait;
+
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Ensure the params and metadata in json encoded in the bind method
+ *
+ * @var array
+ * @since 3.3
+ */
+ protected $_jsonEncode = array('params', 'metadata');
+
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 1.0
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_contact.contact';
+
+ parent::__construct('#__contact_details', 'id', $db);
+
+ $this->setColumnAlias('title', 'name');
+ }
+
+ /**
+ * Stores a contact.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function store($updateNulls = true)
+ {
+ $date = Factory::getDate()->toSql();
+ $userId = Factory::getUser()->id;
+
+ // Set created date if not set.
+ if (!(int) $this->created) {
+ $this->created = $date;
+ }
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_by = $userId;
+ $this->modified = $date;
+ } else {
+ // Field created_by field can be set by the user, so we don't touch it if it's set.
+ if (empty($this->created_by)) {
+ $this->created_by = $userId;
+ }
+
+ if (!(int) $this->modified) {
+ $this->modified = $date;
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $userId;
+ }
+ }
+
+ // Store utf8 email as punycode
+ if ($this->email_to !== null) {
+ $this->email_to = PunycodeHelper::emailToPunycode($this->email_to);
+ }
+
+ // Convert IDN urls to punycode
+ if ($this->webpage !== null) {
+ $this->webpage = PunycodeHelper::urlToPunycode($this->webpage);
+ }
+
+ // Verify that the alias is unique
+ $table = Table::getInstance('ContactTable', __NAMESPACE__ . '\\', array('dbo' => $this->getDbo()));
+
+ if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0)) {
+ $this->setError(Text::_('COM_CONTACT_ERROR_UNIQUE_ALIAS'));
+
+ return false;
+ }
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Overloaded check function
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @see \JTable::check
+ * @since 1.5
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $this->default_con = (int) $this->default_con;
+
+ if ($this->webpage !== null && InputFilter::checkAttribute(array('href', $this->webpage))) {
+ $this->setError(Text::_('COM_CONTACT_WARNING_PROVIDE_VALID_URL'));
+
+ return false;
+ }
+
+ // Check for valid name
+ if (trim($this->name) == '') {
+ $this->setError(Text::_('COM_CONTACT_WARNING_PROVIDE_VALID_NAME'));
+
+ return false;
+ }
+
+ // Generate a valid alias
+ $this->generateAlias();
+
+ // Check for a valid category.
+ if (!$this->catid = (int) $this->catid) {
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
+
+ return false;
+ }
+
+ // Sanity check for user_id
+ if (!$this->user_id) {
+ $this->user_id = 0;
+ }
+
+ // Check the publish down date is not earlier than publish up.
+ if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up) {
+ $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
+
+ return false;
+ }
+
+ if (!$this->id) {
+ // Hits must be zero on a new item
+ $this->hits = 0;
+ }
+
+ // Clean up description -- eliminate quotes and <> brackets
+ if (!empty($this->metadesc)) {
+ // Only process if not empty
+ $badCharacters = array("\"", '<', '>');
+ $this->metadesc = StringHelper::str_ireplace($badCharacters, '', $this->metadesc);
+ } else {
+ $this->metadesc = '';
+ }
+
+ if (empty($this->params)) {
+ $this->params = '{}';
+ }
+
+ if (empty($this->metadata)) {
+ $this->metadata = '{}';
+ }
+
+ // Set publish_up, publish_down to null if not set
+ if (!$this->publish_up) {
+ $this->publish_up = null;
+ }
+
+ if (!$this->publish_down) {
+ $this->publish_down = null;
+ }
+
+ if (!$this->modified) {
+ $this->modified = $this->created;
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_by;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate a valid alias from title / date.
+ * Remains public to be able to check for duplicated alias before saving
+ *
+ * @return string
+ */
+ public function generateAlias()
+ {
+ if (empty($this->alias)) {
+ $this->alias = $this->name;
+ }
+
+ $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
+
+ if (trim(str_replace('-', '', $this->alias)) == '') {
+ $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
+ }
+
+ return $this->alias;
+ }
+
+
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
}
diff --git a/administrator/components/com_contact/src/View/Contact/HtmlView.php b/administrator/components/com_contact/src/View/Contact/HtmlView.php
index c3b55bb0fa0a9..15e0f15dc22ee 100644
--- a/administrator/components/com_contact/src/View/Contact/HtmlView.php
+++ b/administrator/components/com_contact/src/View/Contact/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // If we are forcing a language in modal (used for associations).
- if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
- {
- // Set the language field to the forcedLanguage and disable changing it.
- $this->form->setValue('language', null, $forcedLanguage);
- $this->form->setFieldAttribute('language', 'readonly', 'true');
-
- // Only allow to select categories with All language or with the forced language.
- $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
-
- // Only allow to select tags with All language or with the forced language.
- $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Since we don't track these assets at the item level, use the category id.
- $canDo = ContentHelper::getActions('com_contact', 'category', $this->item->catid);
-
- $toolbar = Toolbar::getInstance();
-
- ToolbarHelper::title($isNew ? Text::_('COM_CONTACT_MANAGER_CONTACT_NEW') : Text::_('COM_CONTACT_MANAGER_CONTACT_EDIT'), 'address-book contact');
-
- // Build the actions for new and existing records.
- if ($isNew)
- {
- // For new records, check the create permission.
- if (count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0)
- {
- ToolbarHelper::apply('contact.apply');
-
- $saveGroup = $toolbar->dropdownButton('save-group');
-
- $saveGroup->configure(
- function (Toolbar $childBar) use ($user)
- {
- $childBar->save('contact.save');
-
- if ($user->authorise('core.create', 'com_menus.menu'))
- {
- $childBar->save('contact.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
- }
-
- $childBar->save2new('contact.save2new');
- }
- );
- }
-
- ToolbarHelper::cancel('contact.cancel');
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- $toolbar->apply('contact.apply');
- }
-
- $saveGroup = $toolbar->dropdownButton('save-group');
-
- $saveGroup->configure(
- function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user)
- {
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- $childBar->save('contact.save');
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $childBar->save2new('contact.save2new');
- }
- }
-
- // If checked out, we can still save2menu
- if ($user->authorise('core.create', 'com_menus.menu'))
- {
- $childBar->save('contact.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
- }
-
- // If checked out, we can still save
- if ($canDo->get('core.create'))
- {
- $childBar->save2copy('contact.save2copy');
- }
- }
- );
-
- ToolbarHelper::cancel('contact.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable)
- {
- ToolbarHelper::versions('com_contact.contact', $this->item->id);
- }
-
- if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations'))
- {
- ToolbarHelper::custom('contact.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
- }
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Contacts:_New_or_Edit');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ // Initialise variables.
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
+
+ // Only allow to select tags with All language or with the forced language.
+ $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Since we don't track these assets at the item level, use the category id.
+ $canDo = ContentHelper::getActions('com_contact', 'category', $this->item->catid);
+
+ $toolbar = Toolbar::getInstance();
+
+ ToolbarHelper::title($isNew ? Text::_('COM_CONTACT_MANAGER_CONTACT_NEW') : Text::_('COM_CONTACT_MANAGER_CONTACT_EDIT'), 'address-book contact');
+
+ // Build the actions for new and existing records.
+ if ($isNew) {
+ // For new records, check the create permission.
+ if (count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0) {
+ ToolbarHelper::apply('contact.apply');
+
+ $saveGroup = $toolbar->dropdownButton('save-group');
+
+ $saveGroup->configure(
+ function (Toolbar $childBar) use ($user) {
+ $childBar->save('contact.save');
+
+ if ($user->authorise('core.create', 'com_menus.menu')) {
+ $childBar->save('contact.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
+ }
+
+ $childBar->save2new('contact.save2new');
+ }
+ );
+ }
+
+ ToolbarHelper::cancel('contact.cancel');
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ $toolbar->apply('contact.apply');
+ }
+
+ $saveGroup = $toolbar->dropdownButton('save-group');
+
+ $saveGroup->configure(
+ function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user) {
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ $childBar->save('contact.save');
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $childBar->save2new('contact.save2new');
+ }
+ }
+
+ // If checked out, we can still save2menu
+ if ($user->authorise('core.create', 'com_menus.menu')) {
+ $childBar->save('contact.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
+ }
+
+ // If checked out, we can still save
+ if ($canDo->get('core.create')) {
+ $childBar->save2copy('contact.save2copy');
+ }
+ }
+ );
+
+ ToolbarHelper::cancel('contact.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable) {
+ ToolbarHelper::versions('com_contact.contact', $this->item->id);
+ }
+
+ if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations')) {
+ ToolbarHelper::custom('contact.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
+ }
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Contacts:_New_or_Edit');
+ }
}
diff --git a/administrator/components/com_contact/src/View/Contacts/HtmlView.php b/administrator/components/com_contact/src/View/Contacts/HtmlView.php
index a03a9a060d153..a7c190246e421 100644
--- a/administrator/components/com_contact/src/View/Contacts/HtmlView.php
+++ b/administrator/components/com_contact/src/View/Contacts/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Preprocess the list of items to find ordering divisions.
- // @todo: Complete the ordering stuff with nested sets
- foreach ($this->items as &$item)
- {
- $item->order_up = true;
- $item->order_dn = true;
- }
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- else
- {
- // In article associations modal we need to remove language filter if forcing a language.
- // We also need to change the category filter to show show categories with All or the forced language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
- $languageXml = new \SimpleXMLElement(' ');
- $this->filterForm->setField($languageXml, 'filter', true);
-
- // Also, unset the active language filter so the search tools is not open by default with this filter.
- unset($this->activeFilters['language']);
-
- // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
- $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_contact', 'category', $this->state->get('filter.category_id'));
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_CONTACT_MANAGER_CONTACTS'), 'address-book contact');
-
- if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0)
- {
- $toolbar->addNew('contact.add');
- }
-
- if (!$this->isEmptyState && $canDo->get('core.edit.state'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('contacts.publish')->listCheck(true);
-
- $childBar->unpublish('contacts.unpublish')->listCheck(true);
-
- $childBar->standardButton('featured')
- ->text('JFEATURE')
- ->task('contacts.featured')
- ->listCheck(true);
- $childBar->standardButton('unfeatured')
- ->text('JUNFEATURE')
- ->task('contacts.unfeatured')
- ->listCheck(true);
-
- $childBar->archive('contacts.archive')->listCheck(true);
-
- if ($user->authorise('core.admin'))
- {
- $childBar->checkin('contacts.checkin')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') != -2)
- {
- $childBar->trash('contacts.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_contact')
- && $user->authorise('core.edit', 'com_contact')
- && $user->authorise('core.edit.state', 'com_contact'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('contacts.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($user->authorise('core.admin', 'com_contact') || $user->authorise('core.options', 'com_contact'))
- {
- $toolbar->preferences('com_contact');
- }
-
- $toolbar->help('Contacts');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Preprocess the list of items to find ordering divisions.
+ // @todo: Complete the ordering stuff with nested sets
+ foreach ($this->items as &$item) {
+ $item->order_up = true;
+ $item->order_dn = true;
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // In article associations modal we need to remove language filter if forcing a language.
+ // We also need to change the category filter to show show categories with All or the forced language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement(' ');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+
+ // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
+ $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_contact', 'category', $this->state->get('filter.category_id'));
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_CONTACT_MANAGER_CONTACTS'), 'address-book contact');
+
+ if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0) {
+ $toolbar->addNew('contact.add');
+ }
+
+ if (!$this->isEmptyState && $canDo->get('core.edit.state')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('contacts.publish')->listCheck(true);
+
+ $childBar->unpublish('contacts.unpublish')->listCheck(true);
+
+ $childBar->standardButton('featured')
+ ->text('JFEATURE')
+ ->task('contacts.featured')
+ ->listCheck(true);
+ $childBar->standardButton('unfeatured')
+ ->text('JUNFEATURE')
+ ->task('contacts.unfeatured')
+ ->listCheck(true);
+
+ $childBar->archive('contacts.archive')->listCheck(true);
+
+ if ($user->authorise('core.admin')) {
+ $childBar->checkin('contacts.checkin')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') != -2) {
+ $childBar->trash('contacts.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_contact')
+ && $user->authorise('core.edit', 'com_contact')
+ && $user->authorise('core.edit.state', 'com_contact')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('contacts.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin', 'com_contact') || $user->authorise('core.options', 'com_contact')) {
+ $toolbar->preferences('com_contact');
+ }
+
+ $toolbar->help('Contacts');
+ }
}
diff --git a/administrator/components/com_contact/tmpl/contact/edit.php b/administrator/components/com_contact/tmpl/contact/edit.php
index 61f2b057ff60b..6798cc6e1cc2d 100644
--- a/administrator/components/com_contact/tmpl/contact/edit.php
+++ b/administrator/components/com_contact/tmpl/contact/edit.php
@@ -20,7 +20,7 @@
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$input = $app->input;
@@ -39,96 +39,96 @@
diff --git a/administrator/components/com_contact/tmpl/contact/modal.php b/administrator/components/com_contact/tmpl/contact/modal.php
index cf70ffd379321..04740bf4c752d 100644
--- a/administrator/components/com_contact/tmpl/contact/modal.php
+++ b/administrator/components/com_contact/tmpl/contact/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_contact/tmpl/contacts/default.php b/administrator/components/com_contact/tmpl/contacts/default.php
index e4f20893ee730..cae745e06d33d 100644
--- a/administrator/components/com_contact/tmpl/contacts/default.php
+++ b/administrator/components/com_contact/tmpl/contacts/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$userId = $user->get('id');
@@ -30,180 +31,180 @@
$saveOrder = $listOrder == 'a.ordering';
$assoc = Associations::isEnabled();
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_contact&task=contacts.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_contact&task=contacts.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_contact/tmpl/contacts/default_batch_body.php b/administrator/components/com_contact/tmpl/contacts/default_batch_body.php
index 4d5bcd3610f7e..101087e40c57d 100644
--- a/administrator/components/com_contact/tmpl/contacts/default_batch_body.php
+++ b/administrator/components/com_contact/tmpl/contacts/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Multilanguage;
@@ -16,37 +18,37 @@
?>
diff --git a/administrator/components/com_contact/tmpl/contacts/default_batch_footer.php b/administrator/components/com_contact/tmpl/contacts/default_batch_footer.php
index 3b64d567c3d67..440ed84e4008b 100644
--- a/administrator/components/com_contact/tmpl/contacts/default_batch_footer.php
+++ b/administrator/components/com_contact/tmpl/contacts/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_contact/tmpl/contacts/emptystate.php b/administrator/components/com_contact/tmpl/contacts/emptystate.php
index cf18d79722ea1..5cfb9038c043d 100644
--- a/administrator/components/com_contact/tmpl/contacts/emptystate.php
+++ b/administrator/components/com_contact/tmpl/contacts/emptystate.php
@@ -1,4 +1,5 @@
'COM_CONTACT',
- 'formURL' => 'index.php?option=com_contact',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Contacts',
- 'icon' => 'icon-address-book contact',
+ 'textPrefix' => 'COM_CONTACT',
+ 'formURL' => 'index.php?option=com_contact',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Contacts',
+ 'icon' => 'icon-address-book contact',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_contact') || count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_contact&task=contact.add';
+if ($user->authorise('core.create', 'com_contact') || count($user->getAuthorisedCategories('com_contact', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_contact&task=contact.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_contact/tmpl/contacts/modal.php b/administrator/components/com_contact/tmpl/contacts/modal.php
index 72b644fe3ed06..6f5a11153c85c 100644
--- a/administrator/components/com_contact/tmpl/contacts/modal.php
+++ b/administrator/components/com_contact/tmpl/contacts/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if ($app->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
@@ -36,128 +36,120 @@
$onclick = $this->escape($function);
$multilang = Multilanguage::isEnabled();
-if (!empty($editor))
-{
- // This view is used also in com_menus. Load the xtd script only if the editor is set!
- $this->document->addScriptOptions('xtd-contacts', array('editor' => $editor));
- $onclick = "jSelectContact";
+if (!empty($editor)) {
+ // This view is used also in com_menus. Load the xtd script only if the editor is set!
+ $this->document->addScriptOptions('xtd-contacts', array('editor' => $editor));
+ $onclick = "jSelectContact";
}
?>
diff --git a/administrator/components/com_content/helpers/content.php b/administrator/components/com_content/helpers/content.php
index ffa37af26c2b6..97ee1a2113373 100644
--- a/administrator/components/com_content/helpers/content.php
+++ b/administrator/components/com_content/helpers/content.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Content component helper.
diff --git a/administrator/components/com_content/services/provider.php b/administrator/components/com_content/services/provider.php
index a3960c5b1cf93..72b91c17f3e80 100644
--- a/administrator/components/com_content/services/provider.php
+++ b/administrator/components/com_content/services/provider.php
@@ -1,4 +1,5 @@
set(AssociationExtensionInterface::class, new AssociationsHelper);
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(AssociationExtensionInterface::class, new AssociationsHelper());
- $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Content'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Content'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Content'));
+ $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Content'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Content'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Content'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new ContentComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new ContentComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
+ $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_content/src/Controller/AjaxController.php b/administrator/components/com_content/src/Controller/AjaxController.php
index ddbe1add4a159..36b7ce5510b2d 100644
--- a/administrator/components/com_content/src/Controller/AjaxController.php
+++ b/administrator/components/com_content/src/Controller/AjaxController.php
@@ -1,4 +1,5 @@
input->getInt('assocId', 0);
+ /**
+ * Method to fetch associations of an article
+ *
+ * The method assumes that the following http parameters are passed in an Ajax Get request:
+ * token: the form token
+ * assocId: the id of the article whose associations are to be returned
+ * excludeLang: the association for this language is to be excluded
+ *
+ * @return null
+ *
+ * @since 3.9.0
+ */
+ public function fetchAssociations()
+ {
+ if (!Session::checkToken('get')) {
+ echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
+ } else {
+ $assocId = $this->input->getInt('assocId', 0);
- if ($assocId == 0)
- {
- echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
+ if ($assocId == 0) {
+ echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
- return;
- }
+ return;
+ }
- $excludeLang = $this->input->get('excludeLang', '', 'STRING');
+ $excludeLang = $this->input->get('excludeLang', '', 'STRING');
- $associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', (int) $assocId);
+ $associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', (int) $assocId);
- unset($associations[$excludeLang]);
+ unset($associations[$excludeLang]);
- // Add the title to each of the associated records
- $contentTable = Table::getInstance('Content', 'JTable');
+ // Add the title to each of the associated records
+ $contentTable = Table::getInstance('Content', 'JTable');
- foreach ($associations as $lang => $association)
- {
- $contentTable->load($association->id);
- $associations[$lang]->title = $contentTable->title;
- }
+ foreach ($associations as $lang => $association) {
+ $contentTable->load($association->id);
+ $associations[$lang]->title = $contentTable->title;
+ }
- $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
+ $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
- if (count($associations) == 0)
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
- }
- elseif ($countContentLanguages > count($associations) + 2)
- {
- $tags = implode(', ', array_keys($associations));
- $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
- }
- else
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
- }
+ if (count($associations) == 0) {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
+ } elseif ($countContentLanguages > count($associations) + 2) {
+ $tags = implode(', ', array_keys($associations));
+ $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
+ } else {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
+ }
- echo new JsonResponse($associations, $message);
- }
- }
+ echo new JsonResponse($associations, $message);
+ }
+ }
}
diff --git a/administrator/components/com_content/src/Controller/ArticleController.php b/administrator/components/com_content/src/Controller/ArticleController.php
index 4147cb5b7de04..586bf30031ab9 100644
--- a/administrator/components/com_content/src/Controller/ArticleController.php
+++ b/administrator/components/com_content/src/Controller/ArticleController.php
@@ -1,4 +1,5 @@
input->get('return') == 'featured')
- {
- $this->view_list = 'featured';
- $this->view_item = 'article&return=featured';
- }
- }
-
- /**
- * Function that allows child controller access to model data
- * after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- if ($this->getTask() === 'save2menu')
- {
- $editState = [];
-
- $id = $model->getState('article.id');
-
- $link = 'index.php?option=com_content&view=article';
- $type = 'component';
-
- $editState['id'] = $id;
- $editState['link'] = $link;
- $editState['title'] = $model->getItem($id)->title;
- $editState['type'] = $type;
- $editState['request']['id'] = $id;
-
- $this->app->setUserState('com_menus.edit.item', array(
- 'data' => $editState,
- 'type' => $type,
- 'link' => $link)
- );
-
- $this->setRedirect(Route::_('index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit', false));
- }
- }
-
- /**
- * Method override to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowAdd($data = array())
- {
- $categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('filter_category_id'), 'int');
-
- if ($categoryId)
- {
- // If the category has been passed in the data or URL check it.
- return $this->app->getIdentity()->authorise('core.create', 'com_content.category.' . $categoryId);
- }
-
- // In the absence of better information, revert to the component permissions.
- return parent::allowAdd();
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- // Zero record (id:0), return component edit permission by calling parent controller method
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Check edit on the record asset (explicit or inherited)
- if ($user->authorise('core.edit', 'com_content.article.' . $recordId))
- {
- return true;
- }
-
- // Check edit own on the record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', 'com_content.article.' . $recordId))
- {
- // Existing record already has an owner, get it
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record))
- {
- return false;
- }
-
- // Grant if current user is owner of the record
- return $user->id == $record->created_by;
- }
-
- return false;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 1.6
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
- $model = $this->getModel('Article', 'Administrator', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_content&view=articles' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
+ use VersionableControllerTrait;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // An article edit form can come from the articles or featured view.
+ // Adjust the redirect view on the value of 'return' in the request.
+ if ($this->input->get('return') == 'featured') {
+ $this->view_list = 'featured';
+ $this->view_item = 'article&return=featured';
+ }
+ }
+
+ /**
+ * Function that allows child controller access to model data
+ * after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ if ($this->getTask() === 'save2menu') {
+ $editState = [];
+
+ $id = $model->getState('article.id');
+
+ $link = 'index.php?option=com_content&view=article';
+ $type = 'component';
+
+ $editState['id'] = $id;
+ $editState['link'] = $link;
+ $editState['title'] = $model->getItem($id)->title;
+ $editState['type'] = $type;
+ $editState['request']['id'] = $id;
+
+ $this->app->setUserState('com_menus.edit.item', array(
+ 'data' => $editState,
+ 'type' => $type,
+ 'link' => $link));
+
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit', false));
+ }
+ }
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('filter_category_id'), 'int');
+
+ if ($categoryId) {
+ // If the category has been passed in the data or URL check it.
+ return $this->app->getIdentity()->authorise('core.create', 'com_content.category.' . $categoryId);
+ }
+
+ // In the absence of better information, revert to the component permissions.
+ return parent::allowAdd();
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ // Zero record (id:0), return component edit permission by calling parent controller method
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Check edit on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit', 'com_content.article.' . $recordId)) {
+ return true;
+ }
+
+ // Check edit own on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', 'com_content.article.' . $recordId)) {
+ // Existing record already has an owner, get it
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record)) {
+ return false;
+ }
+
+ // Grant if current user is owner of the record
+ return $user->id == $record->created_by;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 1.6
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel('Article', 'Administrator', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_content&view=articles' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
}
diff --git a/administrator/components/com_content/src/Controller/ArticlesController.php b/administrator/components/com_content/src/Controller/ArticlesController.php
index b70165bedd53f..149f4900ba21d 100644
--- a/administrator/components/com_content/src/Controller/ArticlesController.php
+++ b/administrator/components/com_content/src/Controller/ArticlesController.php
@@ -1,4 +1,5 @@
input->get('view') == 'featured')
- {
- $this->view_list = 'featured';
- }
-
- $this->registerTask('unfeatured', 'featured');
- }
-
- /**
- * Method to toggle the featured setting of a list of articles.
- *
- * @return void
- *
- * @since 1.6
- */
- public function featured()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $user = $this->app->getIdentity();
- $ids = (array) $this->input->get('cid', array(), 'int');
- $values = array('featured' => 1, 'unfeatured' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($values, $task, 0, 'int');
- $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
-
- // Access checks.
- foreach ($ids as $i => $id)
- {
- // Remove zero value resulting from input filter
- if ($id === 0)
- {
- unset($ids[$i]);
-
- continue;
- }
-
- if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id))
- {
- // Prune items that you can't change.
- unset($ids[$i]);
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
- }
- }
-
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
-
- $this->setRedirect(Route::_($redirectUrl, false));
-
- return;
- }
-
- // Get the model.
- /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
- $model = $this->getModel();
-
- // Publish the items.
- if (!$model->featured($ids, $value))
- {
- $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
-
- return;
- }
-
- if ($value == 1)
- {
- $message = Text::plural('COM_CONTENT_N_ITEMS_FEATURED', count($ids));
- }
- else
- {
- $message = Text::plural('COM_CONTENT_N_ITEMS_UNFEATURED', count($ids));
- }
-
- $this->setRedirect(Route::_($redirectUrl, false), $message);
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config The array of possible config values. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
- *
- * @since 1.6
- */
- public function getModel($name = 'Article', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to get the JSON-encoded amount of published articles
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('articles');
-
- $model->setState('filter.published', 1);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_CONTENT_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_CONTENT_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Articles default form can come from the articles or featured view.
+ // Adjust the redirect view on the value of 'view' in the request.
+ if ($this->input->get('view') == 'featured') {
+ $this->view_list = 'featured';
+ }
+
+ $this->registerTask('unfeatured', 'featured');
+ }
+
+ /**
+ * Method to toggle the featured setting of a list of articles.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function featured()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $user = $this->app->getIdentity();
+ $ids = (array) $this->input->get('cid', array(), 'int');
+ $values = array('featured' => 1, 'unfeatured' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+ $redirectUrl = 'index.php?option=com_content&view=' . $this->view_list . $this->getRedirectToListAppend();
+
+ // Access checks.
+ foreach ($ids as $i => $id) {
+ // Remove zero value resulting from input filter
+ if ($id === 0) {
+ unset($ids[$i]);
+
+ continue;
+ }
+
+ if (!$user->authorise('core.edit.state', 'com_content.article.' . (int) $id)) {
+ // Prune items that you can't change.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'notice');
+ }
+ }
+
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
+
+ $this->setRedirect(Route::_($redirectUrl, false));
+
+ return;
+ }
+
+ // Get the model.
+ /** @var \Joomla\Component\Content\Administrator\Model\ArticleModel $model */
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->featured($ids, $value)) {
+ $this->setRedirect(Route::_($redirectUrl, false), $model->getError(), 'error');
+
+ return;
+ }
+
+ if ($value == 1) {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_FEATURED', count($ids));
+ } else {
+ $message = Text::plural('COM_CONTENT_N_ITEMS_UNFEATURED', count($ids));
+ }
+
+ $this->setRedirect(Route::_($redirectUrl, false), $message);
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Article', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to get the JSON-encoded amount of published articles
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('articles');
+
+ $model->setState('filter.published', 1);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_CONTENT_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_CONTENT_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
}
diff --git a/administrator/components/com_content/src/Controller/DisplayController.php b/administrator/components/com_content/src/Controller/DisplayController.php
index a36e2ec75ee16..b53d767e2aca6 100644
--- a/administrator/components/com_content/src/Controller/DisplayController.php
+++ b/administrator/components/com_content/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'articles');
- $layout = $this->input->get('layout', 'articles');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if ($view == 'article' && $layout == 'edit' && !$this->checkEditId('com_content.edit.article', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_content&view=articles', false));
-
- return false;
- }
-
- return parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'articles';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
+ *
+ * @return BaseController|boolean This object to support chaining.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view', 'articles');
+ $layout = $this->input->get('layout', 'articles');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if ($view == 'article' && $layout == 'edit' && !$this->checkEditId('com_content.edit.article', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_content&view=articles', false));
+
+ return false;
+ }
+
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_content/src/Controller/FeaturedController.php b/administrator/components/com_content/src/Controller/FeaturedController.php
index 4f47f55b3bca6..028ee9f511b83 100644
--- a/administrator/components/com_content/src/Controller/FeaturedController.php
+++ b/administrator/components/com_content/src/Controller/FeaturedController.php
@@ -1,4 +1,5 @@
checkToken();
+ /**
+ * Removes an item.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function delete()
+ {
+ // Check for request forgeries
+ $this->checkToken();
- $user = $this->app->getIdentity();
- $ids = (array) $this->input->get('cid', array(), 'int');
+ $user = $this->app->getIdentity();
+ $ids = (array) $this->input->get('cid', array(), 'int');
- // Access checks.
- foreach ($ids as $i => $id)
- {
- // Remove zero value resulting from input filter
- if ($id === 0)
- {
- unset($ids[$i]);
+ // Access checks.
+ foreach ($ids as $i => $id) {
+ // Remove zero value resulting from input filter
+ if ($id === 0) {
+ unset($ids[$i]);
- continue;
- }
+ continue;
+ }
- if (!$user->authorise('core.delete', 'com_content.article.' . (int) $id))
- {
- // Prune items that you can't delete.
- unset($ids[$i]);
- $this->app->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'notice');
- }
- }
+ if (!$user->authorise('core.delete', 'com_content.article.' . (int) $id)) {
+ // Prune items that you can't delete.
+ unset($ids[$i]);
+ $this->app->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'notice');
+ }
+ }
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
- }
- else
- {
- /** @var \Joomla\Component\Content\Administrator\Model\FeatureModel $model */
- $model = $this->getModel();
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), 'error');
+ } else {
+ /** @var \Joomla\Component\Content\Administrator\Model\FeatureModel $model */
+ $model = $this->getModel();
- // Remove the items.
- if (!$model->featured($ids, 0))
- {
- $this->app->enqueueMessage($model->getError(), 'error');
- }
- }
+ // Remove the items.
+ if (!$model->featured($ids, 0)) {
+ $this->app->enqueueMessage($model->getError(), 'error');
+ }
+ }
- $this->setRedirect('index.php?option=com_content&view=featured');
- }
+ $this->setRedirect('index.php?option=com_content&view=featured');
+ }
- /**
- * Method to publish a list of articles.
- *
- * @return void
- *
- * @since 1.0
- */
- public function publish()
- {
- parent::publish();
+ /**
+ * Method to publish a list of articles.
+ *
+ * @return void
+ *
+ * @since 1.0
+ */
+ public function publish()
+ {
+ parent::publish();
- $this->setRedirect('index.php?option=com_content&view=featured');
- }
+ $this->setRedirect('index.php?option=com_content&view=featured');
+ }
- /**
- * Method to get a model object, loading it if required.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Feature', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Feature', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_content/src/Event/Model/FeatureEvent.php b/administrator/components/com_content/src/Event/Model/FeatureEvent.php
index eddcede227c72..09f48206b6c09 100644
--- a/administrator/components/com_content/src/Event/Model/FeatureEvent.php
+++ b/administrator/components/com_content/src/Event/Model/FeatureEvent.php
@@ -1,4 +1,5 @@
name is required but has not been provided");
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 4.0.0
+ */
+ public function __construct($name, array $arguments = array())
+ {
+ if (!isset($arguments['extension'])) {
+ throw new BadMethodCallException("Argument 'extension' of event $this->name is required but has not been provided");
+ }
- if (!isset($arguments['extension']) || !is_string($arguments['extension']))
- {
- throw new BadMethodCallException("Argument 'extension' of event $this->name is not of type 'string'");
- }
+ if (!isset($arguments['extension']) || !is_string($arguments['extension'])) {
+ throw new BadMethodCallException("Argument 'extension' of event $this->name is not of type 'string'");
+ }
- if (strpos($arguments['extension'], '.') === false)
- {
- throw new BadMethodCallException("Argument 'extension' of event $this->name has wrong format. Valid format: 'component.section'");
- }
+ if (strpos($arguments['extension'], '.') === false) {
+ throw new BadMethodCallException("Argument 'extension' of event $this->name has wrong format. Valid format: 'component.section'");
+ }
- if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments))
- {
- $parts = explode('.', $arguments['extension']);
+ if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments)) {
+ $parts = explode('.', $arguments['extension']);
- $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
- $arguments['section'] = $arguments['section'] ?? $parts[1];
- }
+ $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
+ $arguments['section'] = $arguments['section'] ?? $parts[1];
+ }
- if (!isset($arguments['pks']) || !is_array($arguments['pks']))
- {
- throw new BadMethodCallException("Argument 'pks' of event $this->name is not of type 'array'");
- }
+ if (!isset($arguments['pks']) || !is_array($arguments['pks'])) {
+ throw new BadMethodCallException("Argument 'pks' of event $this->name is not of type 'array'");
+ }
- if (!isset($arguments['value']) || !is_numeric($arguments['value']))
- {
- throw new BadMethodCallException("Argument 'value' of event $this->name is not of type 'numeric'");
- }
+ if (!isset($arguments['value']) || !is_numeric($arguments['value'])) {
+ throw new BadMethodCallException("Argument 'value' of event $this->name is not of type 'numeric'");
+ }
- $arguments['value'] = (int) $arguments['value'];
+ $arguments['value'] = (int) $arguments['value'];
- if ($arguments['value'] !== 0 && $arguments['value'] !== 1)
- {
- throw new BadMethodCallException("Argument 'value' of event $this->name is not 0 or 1");
- }
+ if ($arguments['value'] !== 0 && $arguments['value'] !== 1) {
+ throw new BadMethodCallException("Argument 'value' of event $this->name is not 0 or 1");
+ }
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
- /**
- * Set used parameter to true
- *
- * @param bool $value The value to set
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setAbort(string $reason)
- {
- $this->arguments['abort'] = true;
- $this->arguments['abortReason'] = $reason;
- }
+ /**
+ * Set used parameter to true
+ *
+ * @param bool $value The value to set
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setAbort(string $reason)
+ {
+ $this->arguments['abort'] = true;
+ $this->arguments['abortReason'] = $reason;
+ }
}
diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php
index 3d2a03733eab8..e93bea1884ddd 100644
--- a/administrator/components/com_content/src/Extension/ContentComponent.php
+++ b/administrator/components/com_content/src/Extension/ContentComponent.php
@@ -1,4 +1,5 @@
true,
- 'core.state' => true,
- ];
-
- /**
- * The trashed condition
- *
- * @since 4.0.0
- */
- const CONDITION_NAMES = [
- self::CONDITION_PUBLISHED => 'JPUBLISHED',
- self::CONDITION_UNPUBLISHED => 'JUNPUBLISHED',
- self::CONDITION_ARCHIVED => 'JARCHIVED',
- self::CONDITION_TRASHED => 'JTRASHED',
- ];
-
- /**
- * The archived condition
- *
- * @since 4.0.0
- */
- const CONDITION_ARCHIVED = 2;
-
- /**
- * The published condition
- *
- * @since 4.0.0
- */
- const CONDITION_PUBLISHED = 1;
-
- /**
- * The unpublished condition
- *
- * @since 4.0.0
- */
- const CONDITION_UNPUBLISHED = 0;
-
- /**
- * The trashed condition
- *
- * @since 4.0.0
- */
- const CONDITION_TRASHED = -2;
-
- /**
- * Booting the extension. This is the function to set up the environment of the extension like
- * registering new class loaders, etc.
- *
- * If required, some initial set up can be done from services of the container, eg.
- * registering HTML services.
- *
- * @param ContainerInterface $container The container
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function boot(ContainerInterface $container)
- {
- $this->getRegistry()->register('contentadministrator', new AdministratorService);
- $this->getRegistry()->register('contenticon', new Icon);
-
- // The layout joomla.content.icons does need a general icon service
- $this->getRegistry()->register('icon', $this->getRegistry()->getService('contenticon'));
- }
-
- /**
- * Returns a valid section for the given section. If it is not valid then null
- * is returned.
- *
- * @param string $section The section to get the mapping for
- * @param object $item The item
- *
- * @return string|null The new section
- *
- * @since 4.0.0
- */
- public function validateSection($section, $item = null)
- {
- if (Factory::getApplication()->isClient('site'))
- {
- // On the front end we need to map some sections
- switch ($section)
- {
- // Editing an article
- case 'form':
-
- // Category list view
- case 'featured':
- case 'category':
- $section = 'article';
- }
- }
-
- if ($section != 'article')
- {
- // We don't know other sections
- return null;
- }
-
- return $section;
- }
-
- /**
- * Returns valid contexts
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getContexts(): array
- {
- Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
-
- $contexts = array(
- 'com_content.article' => Text::_('COM_CONTENT'),
- 'com_content.categories' => Text::_('JCATEGORY')
- );
-
- return $contexts;
- }
-
- /**
- * Returns valid contexts
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getWorkflowContexts(): array
- {
- Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
-
- $contexts = array(
- 'com_content.article' => Text::_('COM_CONTENT')
- );
-
- return $contexts;
- }
-
- /**
- * Returns the workflow context based on the given category section
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- public function getCategoryWorkflowContext(?string $section = null): string
- {
- $context = $this->getWorkflowContexts();
-
- return array_key_first($context);
- }
-
- /**
- * Returns the table for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getTableNameForSection(string $section = null)
- {
- return '#__content';
- }
-
- /**
- * Returns a table name for the state association
- *
- * @param string $section An optional section to separate different areas in the component
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getWorkflowTableBySection(?string $section = null): string
- {
- return '#__content';
- }
-
- /**
- * Returns the model name, based on the context
- *
- * @param string $context The context of the workflow
- *
- * @return string
- */
- public function getModelName($context): string
- {
- $parts = explode('.', $context);
-
- if (count($parts) < 2)
- {
- return '';
- }
-
- array_shift($parts);
-
- $modelname = array_shift($parts);
-
- if ($modelname === 'article' && Factory::getApplication()->isClient('site'))
- {
- return 'Form';
- }
- elseif ($modelname === 'featured' && Factory::getApplication()->isClient('administrator'))
- {
- return 'Article';
- }
-
- return ucfirst($modelname);
- }
-
- /**
- * Method to filter transitions by given id of state.
- *
- * @param array $transitions The Transitions to filter
- * @param int $pk Id of the state
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function filterTransitions(array $transitions, int $pk): array
- {
- return ContentHelper::filterTransitions($transitions, $pk);
- }
-
- /**
- * Adds Count Items for Category Manager.
- *
- * @param \stdClass[] $items The category objects
- * @param string $section The section
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function countItems(array $items, string $section)
- {
- $config = (object) array(
- 'related_tbl' => 'content',
- 'state_col' => 'state',
- 'group_col' => 'catid',
- 'relation_type' => 'category_or_group',
- 'uses_workflows' => true,
- 'workflows_component' => 'com_content'
- );
-
- LibraryContentHelper::countRelations($items, $config);
- }
-
- /**
- * Adds Count Items for Tag Manager.
- *
- * @param \stdClass[] $items The content objects
- * @param string $extension The name of the active view.
- *
- * @return void
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function countTagItems(array $items, string $extension)
- {
- $parts = explode('.', $extension);
- $section = count($parts) > 1 ? $parts[1] : null;
-
- $config = (object) array(
- 'related_tbl' => ($section === 'category' ? 'categories' : 'content'),
- 'state_col' => ($section === 'category' ? 'published' : 'state'),
- 'group_col' => 'tag_id',
- 'extension' => $extension,
- 'relation_type' => 'tag_assigments',
- );
-
- LibraryContentHelper::countRelations($items, $config);
- }
-
- /**
- * Prepares the category form
- *
- * @param Form $form The form to prepare
- * @param array|object $data The form data
- *
- * @return void
- */
- public function prepareForm(Form $form, $data)
- {
- ContentHelper::onPrepareForm($form, $data);
- }
+ use AssociationServiceTrait;
+ use RouterServiceTrait;
+ use HTMLRegistryAwareTrait;
+ use WorkflowServiceTrait;
+ use CategoryServiceTrait, TagServiceTrait {
+ CategoryServiceTrait::getTableNameForSection insteadof TagServiceTrait;
+ CategoryServiceTrait::getStateColumnForSection insteadof TagServiceTrait;
+ }
+
+ /** @var array Supported functionality */
+ protected $supportedFunctionality = [
+ 'core.featured' => true,
+ 'core.state' => true,
+ ];
+
+ /**
+ * The trashed condition
+ *
+ * @since 4.0.0
+ */
+ public const CONDITION_NAMES = [
+ self::CONDITION_PUBLISHED => 'JPUBLISHED',
+ self::CONDITION_UNPUBLISHED => 'JUNPUBLISHED',
+ self::CONDITION_ARCHIVED => 'JARCHIVED',
+ self::CONDITION_TRASHED => 'JTRASHED',
+ ];
+
+ /**
+ * The archived condition
+ *
+ * @since 4.0.0
+ */
+ public const CONDITION_ARCHIVED = 2;
+
+ /**
+ * The published condition
+ *
+ * @since 4.0.0
+ */
+ public const CONDITION_PUBLISHED = 1;
+
+ /**
+ * The unpublished condition
+ *
+ * @since 4.0.0
+ */
+ public const CONDITION_UNPUBLISHED = 0;
+
+ /**
+ * The trashed condition
+ *
+ * @since 4.0.0
+ */
+ public const CONDITION_TRASHED = -2;
+
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('contentadministrator', new AdministratorService());
+ $this->getRegistry()->register('contenticon', new Icon());
+
+ // The layout joomla.content.icons does need a general icon service
+ $this->getRegistry()->register('icon', $this->getRegistry()->getService('contenticon'));
+ }
+
+ /**
+ * Returns a valid section for the given section. If it is not valid then null
+ * is returned.
+ *
+ * @param string $section The section to get the mapping for
+ * @param object $item The item
+ *
+ * @return string|null The new section
+ *
+ * @since 4.0.0
+ */
+ public function validateSection($section, $item = null)
+ {
+ if (Factory::getApplication()->isClient('site')) {
+ // On the front end we need to map some sections
+ switch ($section) {
+ // Editing an article
+ case 'form':
+ // Category list view
+ case 'featured':
+ case 'category':
+ $section = 'article';
+ }
+ }
+
+ if ($section != 'article') {
+ // We don't know other sections
+ return null;
+ }
+
+ return $section;
+ }
+
+ /**
+ * Returns valid contexts
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getContexts(): array
+ {
+ Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
+
+ $contexts = array(
+ 'com_content.article' => Text::_('COM_CONTENT'),
+ 'com_content.categories' => Text::_('JCATEGORY')
+ );
+
+ return $contexts;
+ }
+
+ /**
+ * Returns valid contexts
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getWorkflowContexts(): array
+ {
+ Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
+
+ $contexts = array(
+ 'com_content.article' => Text::_('COM_CONTENT')
+ );
+
+ return $contexts;
+ }
+
+ /**
+ * Returns the workflow context based on the given category section
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ public function getCategoryWorkflowContext(?string $section = null): string
+ {
+ $context = $this->getWorkflowContexts();
+
+ return array_key_first($context);
+ }
+
+ /**
+ * Returns the table for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getTableNameForSection(string $section = null)
+ {
+ return '#__content';
+ }
+
+ /**
+ * Returns a table name for the state association
+ *
+ * @param string $section An optional section to separate different areas in the component
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getWorkflowTableBySection(?string $section = null): string
+ {
+ return '#__content';
+ }
+
+ /**
+ * Returns the model name, based on the context
+ *
+ * @param string $context The context of the workflow
+ *
+ * @return string
+ */
+ public function getModelName($context): string
+ {
+ $parts = explode('.', $context);
+
+ if (count($parts) < 2) {
+ return '';
+ }
+
+ array_shift($parts);
+
+ $modelname = array_shift($parts);
+
+ if ($modelname === 'article' && Factory::getApplication()->isClient('site')) {
+ return 'Form';
+ } elseif ($modelname === 'featured' && Factory::getApplication()->isClient('administrator')) {
+ return 'Article';
+ }
+
+ return ucfirst($modelname);
+ }
+
+ /**
+ * Method to filter transitions by given id of state.
+ *
+ * @param array $transitions The Transitions to filter
+ * @param int $pk Id of the state
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function filterTransitions(array $transitions, int $pk): array
+ {
+ return ContentHelper::filterTransitions($transitions, $pk);
+ }
+
+ /**
+ * Adds Count Items for Category Manager.
+ *
+ * @param \stdClass[] $items The category objects
+ * @param string $section The section
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function countItems(array $items, string $section)
+ {
+ $config = (object) array(
+ 'related_tbl' => 'content',
+ 'state_col' => 'state',
+ 'group_col' => 'catid',
+ 'relation_type' => 'category_or_group',
+ 'uses_workflows' => true,
+ 'workflows_component' => 'com_content'
+ );
+
+ LibraryContentHelper::countRelations($items, $config);
+ }
+
+ /**
+ * Adds Count Items for Tag Manager.
+ *
+ * @param \stdClass[] $items The content objects
+ * @param string $extension The name of the active view.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function countTagItems(array $items, string $extension)
+ {
+ $parts = explode('.', $extension);
+ $section = count($parts) > 1 ? $parts[1] : null;
+
+ $config = (object) array(
+ 'related_tbl' => ($section === 'category' ? 'categories' : 'content'),
+ 'state_col' => ($section === 'category' ? 'published' : 'state'),
+ 'group_col' => 'tag_id',
+ 'extension' => $extension,
+ 'relation_type' => 'tag_assigments',
+ );
+
+ LibraryContentHelper::countRelations($items, $config);
+ }
+
+ /**
+ * Prepares the category form
+ *
+ * @param Form $form The form to prepare
+ * @param array|object $data The form data
+ *
+ * @return void
+ */
+ public function prepareForm(Form $form, $data)
+ {
+ ContentHelper::onPrepareForm($form, $data);
+ }
}
diff --git a/administrator/components/com_content/src/Field/AssocField.php b/administrator/components/com_content/src/Field/AssocField.php
index 7746abeaa8f1b..0e85fa362c0e4 100644
--- a/administrator/components/com_content/src/Field/AssocField.php
+++ b/administrator/components/com_content/src/Field/AssocField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see AssocField::setup()
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- if (!Associations::isEnabled())
- {
- return false;
- }
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see AssocField::setup()
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ if (!Associations::isEnabled()) {
+ return false;
+ }
- return parent::setup($element, $value, $group);
- }
+ return parent::setup($element, $value, $group);
+ }
}
diff --git a/administrator/components/com_content/src/Field/Modal/ArticleField.php b/administrator/components/com_content/src/Field/Modal/ArticleField.php
index d37a1c3296a67..bf9f8dda4c6fe 100644
--- a/administrator/components/com_content/src/Field/Modal/ArticleField.php
+++ b/administrator/components/com_content/src/Field/Modal/ArticleField.php
@@ -1,4 +1,5 @@
element['new'] == 'true');
- $allowEdit = ((string) $this->element['edit'] == 'true');
- $allowClear = ((string) $this->element['clear'] != 'false');
- $allowSelect = ((string) $this->element['select'] != 'false');
- $allowPropagate = ((string) $this->element['propagate'] == 'true');
-
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- // Load language
- Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
-
- // The active article id field.
- $value = (int) $this->value ?: '';
-
- // Create the modal id.
- $modalId = 'Article_' . $this->id;
-
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
-
- // Add the modal field script to the document head.
- $wa->useScript('field.modal-fields');
-
- // Script to proxy the select modal function to the modal-fields.js file.
- if ($allowSelect)
- {
- static $scriptSelect = null;
-
- if (is_null($scriptSelect))
- {
- $scriptSelect = array();
- }
-
- if (!isset($scriptSelect[$this->id]))
- {
- $wa->addInlineScript("
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'Modal_Article';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $allowNew = ((string) $this->element['new'] == 'true');
+ $allowEdit = ((string) $this->element['edit'] == 'true');
+ $allowClear = ((string) $this->element['clear'] != 'false');
+ $allowSelect = ((string) $this->element['select'] != 'false');
+ $allowPropagate = ((string) $this->element['propagate'] == 'true');
+
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ // Load language
+ Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR);
+
+ // The active article id field.
+ $value = (int) $this->value ?: '';
+
+ // Create the modal id.
+ $modalId = 'Article_' . $this->id;
+
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+
+ // Add the modal field script to the document head.
+ $wa->useScript('field.modal-fields');
+
+ // Script to proxy the select modal function to the modal-fields.js file.
+ if ($allowSelect) {
+ static $scriptSelect = null;
+
+ if (is_null($scriptSelect)) {
+ $scriptSelect = array();
+ }
+
+ if (!isset($scriptSelect[$this->id])) {
+ $wa->addInlineScript(
+ "
window.jSelectArticle_" . $this->id . " = function (id, title, catid, object, url, language) {
window.processModalSelect('Article', '" . $this->id . "', id, title, catid, object, url, language);
}",
- [],
- ['type' => 'module']
- );
-
- Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
-
- $scriptSelect[$this->id] = true;
- }
- }
-
- // Setup variables for display.
- $linkArticles = 'index.php?option=com_content&view=articles&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
- $linkArticle = 'index.php?option=com_content&view=article&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
-
- if (isset($this->element['language']))
- {
- $linkArticles .= '&forcedLanguage=' . $this->element['language'];
- $linkArticle .= '&forcedLanguage=' . $this->element['language'];
- $modalTitle = Text::_('COM_CONTENT_SELECT_AN_ARTICLE') . ' — ' . $this->element['label'];
- }
- else
- {
- $modalTitle = Text::_('COM_CONTENT_SELECT_AN_ARTICLE');
- }
-
- $urlSelect = $linkArticles . '&function=jSelectArticle_' . $this->id;
- $urlEdit = $linkArticle . '&task=article.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
- $urlNew = $linkArticle . '&task=article.add';
-
- if ($value)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__content'))
- ->where($db->quoteName('id') . ' = :value')
- ->bind(':value', $value, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $title = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- $title = empty($title) ? Text::_('COM_CONTENT_SELECT_AN_ARTICLE') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
-
- // The current article display field.
- $html = '';
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= '';
- }
-
- $html .= ' ';
-
- // Select article button
- if ($allowSelect)
- {
- $html .= ''
- . ' ' . Text::_('JSELECT')
- . ' ';
- }
-
- // New article button
- if ($allowNew)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_CREATE')
- . ' ';
- }
-
- // Edit article button
- if ($allowEdit)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_EDIT')
- . ' ';
- }
-
- // Clear article button
- if ($allowClear)
- {
- $html .= ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
- }
-
- // Propagate article button
- if ($allowPropagate && count($languages) > 2)
- {
- // Strip off language tag at the end
- $tagLength = (int) strlen($this->element['language']);
- $callbackFunctionStem = substr("jSelectArticle_" . $this->id, 0, -$tagLength);
-
- $html .= ''
- . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
- . ' ';
- }
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= ' ';
- }
-
- // Select article modal
- if ($allowSelect)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalSelect' . $modalId,
- array(
- 'title' => $modalTitle,
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
- }
-
- // New article modal
- if ($allowNew)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalNew' . $modalId,
- array(
- 'title' => Text::_('COM_CONTENT_NEW_ARTICLE'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlNew,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Edit article modal
- if ($allowEdit)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalEdit' . $modalId,
- array(
- 'title' => Text::_('COM_CONTENT_EDIT_ARTICLE'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlEdit,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Note: class='required' for client side validation.
- $class = $this->required ? ' class="required modal-value"' : '';
-
- $html .= ' ';
-
- return $html;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 3.4
- */
- protected function getLabel()
- {
- return str_replace($this->id, $this->id . '_name', parent::getLabel());
- }
+ [],
+ ['type' => 'module']
+ );
+
+ Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
+
+ $scriptSelect[$this->id] = true;
+ }
+ }
+
+ // Setup variables for display.
+ $linkArticles = 'index.php?option=com_content&view=articles&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+ $linkArticle = 'index.php?option=com_content&view=article&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+
+ if (isset($this->element['language'])) {
+ $linkArticles .= '&forcedLanguage=' . $this->element['language'];
+ $linkArticle .= '&forcedLanguage=' . $this->element['language'];
+ $modalTitle = Text::_('COM_CONTENT_SELECT_AN_ARTICLE') . ' — ' . $this->element['label'];
+ } else {
+ $modalTitle = Text::_('COM_CONTENT_SELECT_AN_ARTICLE');
+ }
+
+ $urlSelect = $linkArticles . '&function=jSelectArticle_' . $this->id;
+ $urlEdit = $linkArticle . '&task=article.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
+ $urlNew = $linkArticle . '&task=article.add';
+
+ if ($value) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__content'))
+ ->where($db->quoteName('id') . ' = :value')
+ ->bind(':value', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $title = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $title = empty($title) ? Text::_('COM_CONTENT_SELECT_AN_ARTICLE') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
+
+ // The current article display field.
+ $html = '';
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= '';
+ }
+
+ $html .= ' ';
+
+ // Select article button
+ if ($allowSelect) {
+ $html .= ''
+ . ' ' . Text::_('JSELECT')
+ . ' ';
+ }
+
+ // New article button
+ if ($allowNew) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_CREATE')
+ . ' ';
+ }
+
+ // Edit article button
+ if ($allowEdit) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_EDIT')
+ . ' ';
+ }
+
+ // Clear article button
+ if ($allowClear) {
+ $html .= ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+ }
+
+ // Propagate article button
+ if ($allowPropagate && count($languages) > 2) {
+ // Strip off language tag at the end
+ $tagLength = (int) strlen($this->element['language']);
+ $callbackFunctionStem = substr("jSelectArticle_" . $this->id, 0, -$tagLength);
+
+ $html .= ''
+ . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
+ . ' ';
+ }
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= ' ';
+ }
+
+ // Select article modal
+ if ($allowSelect) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalSelect' . $modalId,
+ array(
+ 'title' => $modalTitle,
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+ }
+
+ // New article modal
+ if ($allowNew) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalNew' . $modalId,
+ array(
+ 'title' => Text::_('COM_CONTENT_NEW_ARTICLE'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlNew,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Edit article modal
+ if ($allowEdit) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalEdit' . $modalId,
+ array(
+ 'title' => Text::_('COM_CONTENT_EDIT_ARTICLE'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlEdit,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Note: class='required' for client side validation.
+ $class = $this->required ? ' class="required modal-value"' : '';
+
+ $html .= ' ';
+
+ return $html;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 3.4
+ */
+ protected function getLabel()
+ {
+ return str_replace($this->id, $this->id . '_name', parent::getLabel());
+ }
}
diff --git a/administrator/components/com_content/src/Field/VotelistField.php b/administrator/components/com_content/src/Field/VotelistField.php
index 5aa280b44cd76..3a3a0739de162 100644
--- a/administrator/components/com_content/src/Field/VotelistField.php
+++ b/administrator/components/com_content/src/Field/VotelistField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- // Requires vote plugin enabled
- if (!PluginHelper::isEnabled('content', 'vote'))
- {
- return false;
- }
+ /**
+ * Method to attach a form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ // Requires vote plugin enabled
+ if (!PluginHelper::isEnabled('content', 'vote')) {
+ return false;
+ }
- return parent::setup($element, $value, $group);
- }
+ return parent::setup($element, $value, $group);
+ }
}
diff --git a/administrator/components/com_content/src/Field/VoteradioField.php b/administrator/components/com_content/src/Field/VoteradioField.php
index c9e8bd696e9d3..caf521d86d30d 100644
--- a/administrator/components/com_content/src/Field/VoteradioField.php
+++ b/administrator/components/com_content/src/Field/VoteradioField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- // Requires vote plugin enabled
- if (!PluginHelper::isEnabled('content', 'vote'))
- {
- return false;
- }
+ /**
+ * Method to attach a form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ // Requires vote plugin enabled
+ if (!PluginHelper::isEnabled('content', 'vote')) {
+ return false;
+ }
- return parent::setup($element, $value, $group);
- }
+ return parent::setup($element, $value, $group);
+ }
}
diff --git a/administrator/components/com_content/src/Helper/AssociationsHelper.php b/administrator/components/com_content/src/Helper/AssociationsHelper.php
index 4640ae4aafba7..314dd56d509f4 100644
--- a/administrator/components/com_content/src/Helper/AssociationsHelper.php
+++ b/administrator/components/com_content/src/Helper/AssociationsHelper.php
@@ -1,4 +1,5 @@
getType($typeName);
-
- $context = $this->extension . '.item';
- $catidField = 'catid';
-
- if ($typeName === 'category')
- {
- $context = 'com_categories.item';
- $catidField = '';
- }
-
- // Get the associations.
- $associations = Associations::getAssociations(
- $this->extension,
- $type['tables']['a'],
- $context,
- $id,
- 'id',
- 'alias',
- $catidField
- );
-
- return $associations;
- }
-
- /**
- * Get item information
- *
- * @param string $typeName The item type
- * @param int $id The id of item for which we need the associated items
- *
- * @return Table|null
- *
- * @since 3.7.0
- */
- public function getItem($typeName, $id)
- {
- if (empty($id))
- {
- return null;
- }
-
- $table = null;
-
- switch ($typeName)
- {
- case 'article':
- $table = Table::getInstance('Content');
- break;
-
- case 'category':
- $table = Table::getInstance('Category');
- break;
- }
-
- if (is_null($table))
- {
- return null;
- }
-
- $table->load($id);
-
- return $table;
- }
-
- /**
- * Get information about the type
- *
- * @param string $typeName The item type
- *
- * @return array Array of item types
- *
- * @since 3.7.0
- */
- public function getType($typeName = '')
- {
- $fields = $this->getFieldsTemplate();
- $tables = array();
- $joins = array();
- $support = $this->getSupportTemplate();
- $title = '';
-
- if (in_array($typeName, $this->itemTypes))
- {
- switch ($typeName)
- {
- case 'article':
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['category'] = true;
- $support['save2copy'] = true;
-
- $tables = array(
- 'a' => '#__content'
- );
-
- $title = 'article';
- break;
-
- case 'category':
- $fields['created_user_id'] = 'a.created_user_id';
- $fields['ordering'] = 'a.lft';
- $fields['level'] = 'a.level';
- $fields['catid'] = '';
- $fields['state'] = 'a.published';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['level'] = true;
-
- $tables = array(
- 'a' => '#__categories'
- );
-
- $title = 'category';
- break;
- }
- }
-
- return array(
- 'fields' => $fields,
- 'support' => $support,
- 'tables' => $tables,
- 'joins' => $joins,
- 'title' => $title
- );
- }
+ /**
+ * The extension name
+ *
+ * @var array $extension
+ *
+ * @since 3.7.0
+ */
+ protected $extension = 'com_content';
+
+ /**
+ * Array of item types
+ *
+ * @var array $itemTypes
+ *
+ * @since 3.7.0
+ */
+ protected $itemTypes = array('article', 'category');
+
+ /**
+ * Has the extension association support
+ *
+ * @var boolean $associationsSupport
+ *
+ * @since 3.7.0
+ */
+ protected $associationsSupport = true;
+
+ /**
+ * Method to get the associations for a given item.
+ *
+ * @param integer $id Id of the item
+ * @param string $view Name of the view
+ *
+ * @return array Array of associations for the item
+ *
+ * @since 4.0.0
+ */
+ public function getAssociationsForItem($id = 0, $view = null)
+ {
+ return AssociationHelper::getAssociations($id, $view);
+ }
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public function getAssociations($typeName, $id)
+ {
+ $type = $this->getType($typeName);
+
+ $context = $this->extension . '.item';
+ $catidField = 'catid';
+
+ if ($typeName === 'category') {
+ $context = 'com_categories.item';
+ $catidField = '';
+ }
+
+ // Get the associations.
+ $associations = Associations::getAssociations(
+ $this->extension,
+ $type['tables']['a'],
+ $context,
+ $id,
+ 'id',
+ 'alias',
+ $catidField
+ );
+
+ return $associations;
+ }
+
+ /**
+ * Get item information
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return Table|null
+ *
+ * @since 3.7.0
+ */
+ public function getItem($typeName, $id)
+ {
+ if (empty($id)) {
+ return null;
+ }
+
+ $table = null;
+
+ switch ($typeName) {
+ case 'article':
+ $table = Table::getInstance('Content');
+ break;
+
+ case 'category':
+ $table = Table::getInstance('Category');
+ break;
+ }
+
+ if (is_null($table)) {
+ return null;
+ }
+
+ $table->load($id);
+
+ return $table;
+ }
+
+ /**
+ * Get information about the type
+ *
+ * @param string $typeName The item type
+ *
+ * @return array Array of item types
+ *
+ * @since 3.7.0
+ */
+ public function getType($typeName = '')
+ {
+ $fields = $this->getFieldsTemplate();
+ $tables = array();
+ $joins = array();
+ $support = $this->getSupportTemplate();
+ $title = '';
+
+ if (in_array($typeName, $this->itemTypes)) {
+ switch ($typeName) {
+ case 'article':
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['category'] = true;
+ $support['save2copy'] = true;
+
+ $tables = array(
+ 'a' => '#__content'
+ );
+
+ $title = 'article';
+ break;
+
+ case 'category':
+ $fields['created_user_id'] = 'a.created_user_id';
+ $fields['ordering'] = 'a.lft';
+ $fields['level'] = 'a.level';
+ $fields['catid'] = '';
+ $fields['state'] = 'a.published';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['level'] = true;
+
+ $tables = array(
+ 'a' => '#__categories'
+ );
+
+ $title = 'category';
+ break;
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'support' => $support,
+ 'tables' => $tables,
+ 'joins' => $joins,
+ 'title' => $title
+ );
+ }
}
diff --git a/administrator/components/com_content/src/Helper/ContentHelper.php b/administrator/components/com_content/src/Helper/ContentHelper.php
index 3d26fc915fc6d..dae43a804f228 100644
--- a/administrator/components/com_content/src/Helper/ContentHelper.php
+++ b/administrator/components/com_content/src/Helper/ContentHelper.php
@@ -1,4 +1,5 @@
getQuery(true);
-
- $query->select('id')
- ->from($db->quoteName('#__content'))
- ->where($db->quoteName('state') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $states = $db->loadResult();
-
- return empty($states);
- }
-
- /**
- * Method to filter transitions by given id of state
- *
- * @param array $transitions Array of transitions
- * @param int $pk Id of state
- * @param int $workflowId Id of the workflow
- *
- * @return array
- *
- * @since 4.0.0
- */
- public static function filterTransitions(array $transitions, int $pk, int $workflowId = 0): array
- {
- return array_values(
- array_filter(
- $transitions,
- function ($var) use ($pk, $workflowId)
- {
- return in_array($var['from_stage_id'], [-1, $pk]) && $workflowId == $var['workflow_id'];
- }
- )
- );
- }
-
- /**
- * Prepares a form
- *
- * @param Form $form The form to change
- * @param array|object $data The form data
- *
- * @return void
- */
- public static function onPrepareForm(Form $form, $data)
- {
- if ($form->getName() != 'com_categories.categorycom_content')
- {
- return;
- }
-
- $db = Factory::getDbo();
-
- $data = (array) $data;
-
- // Make workflows translatable
- Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
-
- $form->setFieldAttribute('workflow_id', 'default', 'inherit');
-
- $component = Factory::getApplication()->bootComponent('com_content');
-
- if (!$component instanceof WorkflowServiceInterface
- || !$component->isWorkflowActive('com_content.article'))
- {
- $form->removeField('workflow_id', 'params');
-
- return;
- }
-
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('title'))
- ->from($db->quoteName('#__workflows'))
- ->where(
- [
- $db->quoteName('default') . ' = 1',
- $db->quoteName('published') . ' = 1',
- $db->quoteName('extension') . ' = ' . $db->quote('com_content.article'),
- ]
- );
-
- $defaulttitle = $db->setQuery($query)->loadResult();
-
- $option = Text::_('COM_WORKFLOW_INHERIT_WORKFLOW_NEW');
-
- if (!empty($data['id']))
- {
- $category = new Category($db);
-
- $categories = $category->getPath((int) $data['id']);
-
- // Remove the current category, because we search for inheritance from parent.
- array_pop($categories);
-
- $option = Text::sprintf('COM_WORKFLOW_INHERIT_WORKFLOW', Text::_($defaulttitle));
-
- if (!empty($categories))
- {
- $categories = array_reverse($categories);
-
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('title'))
- ->from($db->quoteName('#__workflows'))
- ->where(
- [
- $db->quoteName('id') . ' = :workflowId',
- $db->quoteName('published') . ' = 1',
- $db->quoteName('extension') . ' = ' . $db->quote('com_content.article'),
- ]
- )
- ->bind(':workflowId', $workflow_id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- foreach ($categories as $cat)
- {
- $cat->params = new Registry($cat->params);
-
- $workflow_id = $cat->params->get('workflow_id');
-
- if ($workflow_id == 'inherit')
- {
- continue;
- }
- elseif ($workflow_id == 'use_default')
- {
- break;
- }
- elseif ($workflow_id = (int) $workflow_id)
- {
- $title = $db->loadResult();
-
- if (!is_null($title))
- {
- $option = Text::sprintf('COM_WORKFLOW_INHERIT_WORKFLOW', Text::_($title));
-
- break;
- }
- }
- }
- }
- }
+ /**
+ * Check if state can be deleted
+ *
+ * @param int $id Id of state to delete
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public static function canDeleteState(int $id): bool
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select('id')
+ ->from($db->quoteName('#__content'))
+ ->where($db->quoteName('state') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $states = $db->loadResult();
+
+ return empty($states);
+ }
+
+ /**
+ * Method to filter transitions by given id of state
+ *
+ * @param array $transitions Array of transitions
+ * @param int $pk Id of state
+ * @param int $workflowId Id of the workflow
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public static function filterTransitions(array $transitions, int $pk, int $workflowId = 0): array
+ {
+ return array_values(
+ array_filter(
+ $transitions,
+ function ($var) use ($pk, $workflowId) {
+ return in_array($var['from_stage_id'], [-1, $pk]) && $workflowId == $var['workflow_id'];
+ }
+ )
+ );
+ }
+
+ /**
+ * Prepares a form
+ *
+ * @param Form $form The form to change
+ * @param array|object $data The form data
+ *
+ * @return void
+ */
+ public static function onPrepareForm(Form $form, $data)
+ {
+ if ($form->getName() != 'com_categories.categorycom_content') {
+ return;
+ }
+
+ $db = Factory::getDbo();
+
+ $data = (array) $data;
+
+ // Make workflows translatable
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $form->setFieldAttribute('workflow_id', 'default', 'inherit');
+
+ $component = Factory::getApplication()->bootComponent('com_content');
+
+ if (
+ !$component instanceof WorkflowServiceInterface
+ || !$component->isWorkflowActive('com_content.article')
+ ) {
+ $form->removeField('workflow_id', 'params');
+
+ return;
+ }
+
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('title'))
+ ->from($db->quoteName('#__workflows'))
+ ->where(
+ [
+ $db->quoteName('default') . ' = 1',
+ $db->quoteName('published') . ' = 1',
+ $db->quoteName('extension') . ' = ' . $db->quote('com_content.article'),
+ ]
+ );
+
+ $defaulttitle = $db->setQuery($query)->loadResult();
+
+ $option = Text::_('COM_WORKFLOW_INHERIT_WORKFLOW_NEW');
+
+ if (!empty($data['id'])) {
+ $category = new Category($db);
+
+ $categories = $category->getPath((int) $data['id']);
+
+ // Remove the current category, because we search for inheritance from parent.
+ array_pop($categories);
+
+ $option = Text::sprintf('COM_WORKFLOW_INHERIT_WORKFLOW', Text::_($defaulttitle));
+
+ if (!empty($categories)) {
+ $categories = array_reverse($categories);
+
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('title'))
+ ->from($db->quoteName('#__workflows'))
+ ->where(
+ [
+ $db->quoteName('id') . ' = :workflowId',
+ $db->quoteName('published') . ' = 1',
+ $db->quoteName('extension') . ' = ' . $db->quote('com_content.article'),
+ ]
+ )
+ ->bind(':workflowId', $workflow_id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ foreach ($categories as $cat) {
+ $cat->params = new Registry($cat->params);
+
+ $workflow_id = $cat->params->get('workflow_id');
+
+ if ($workflow_id == 'inherit') {
+ continue;
+ } elseif ($workflow_id == 'use_default') {
+ break;
+ } elseif ($workflow_id = (int) $workflow_id) {
+ $title = $db->loadResult();
+
+ if (!is_null($title)) {
+ $option = Text::sprintf('COM_WORKFLOW_INHERIT_WORKFLOW', Text::_($title));
+
+ break;
+ }
+ }
+ }
+ }
+ }
- $field = $form->getField('workflow_id', 'params');
+ $field = $form->getField('workflow_id', 'params');
- $field->addOption($option, ['value' => 'inherit']);
+ $field->addOption($option, ['value' => 'inherit']);
- $field->addOption(Text::sprintf('COM_WORKFLOW_USE_DEFAULT_WORKFLOW', Text::_($defaulttitle)), ['value' => 'use_default']);
+ $field->addOption(Text::sprintf('COM_WORKFLOW_USE_DEFAULT_WORKFLOW', Text::_($defaulttitle)), ['value' => 'use_default']);
- $field->addOption('- ' . Text::_('COM_CONTENT_WORKFLOWS') . ' -', ['disabled' => 'true']);
- }
+ $field->addOption('- ' . Text::_('COM_CONTENT_WORKFLOWS') . ' -', ['disabled' => 'true']);
+ }
}
diff --git a/administrator/components/com_content/src/Model/ArticleModel.php b/administrator/components/com_content/src/Model/ArticleModel.php
index 99982f0875fd7..e47de4929cc27 100644
--- a/administrator/components/com_content/src/Model/ArticleModel.php
+++ b/administrator/components/com_content/src/Model/ArticleModel.php
@@ -1,4 +1,5 @@
'content'],
- $config['events_map']
- );
-
- parent::__construct($config, $factory, $formFactory);
-
- // Set the featured status change events
- $this->event_before_change_featured = $config['event_before_change_featured'] ?? $this->event_before_change_featured;
- $this->event_before_change_featured = $this->event_before_change_featured ?? 'onContentBeforeChangeFeatured';
- $this->event_after_change_featured = $config['event_after_change_featured'] ?? $this->event_after_change_featured;
- $this->event_after_change_featured = $this->event_after_change_featured ?? 'onContentAfterChangeFeatured';
-
- $this->setUpWorkflow('com_content.article');
- }
-
- /**
- * Function that can be overridden to do any data cleanup after batch copying data
- *
- * @param TableInterface $table The table object containing the newly created item
- * @param integer $newId The id of the new item
- * @param integer $oldId The original item id
- *
- * @return void
- *
- * @since 3.8.12
- */
- protected function cleanupPostBatchCopy(TableInterface $table, $newId, $oldId)
- {
- // Check if the article was featured and update the #__content_frontpage table
- if ($table->featured == 1)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('featured_up'),
- $db->quoteName('featured_down'),
- ]
- )
- ->from($db->quoteName('#__content_frontpage'))
- ->where($db->quoteName('content_id') . ' = :oldId')
- ->bind(':oldId', $oldId, ParameterType::INTEGER);
-
- $featured = $db->setQuery($query)->loadObject();
-
- if ($featured)
- {
- $query = $db->getQuery(true)
- ->insert($db->quoteName('#__content_frontpage'))
- ->values(':newId, 0, :featuredUp, :featuredDown')
- ->bind(':newId', $newId, ParameterType::INTEGER)
- ->bind(':featuredUp', $featured->featured_up, $featured->featured_up ? ParameterType::STRING : ParameterType::NULL)
- ->bind(':featuredDown', $featured->featured_down, $featured->featured_down ? ParameterType::STRING : ParameterType::NULL);
-
- $db->setQuery($query);
- $db->execute();
- }
- }
-
- $this->workflowCleanupBatchMove($oldId, $newId);
-
- $oldItem = $this->getTable();
- $oldItem->load($oldId);
- $fields = FieldsHelper::getFields('com_content.article', $oldItem, true);
-
- $fieldsData = array();
-
- if (!empty($fields))
- {
- $fieldsData['com_fields'] = array();
-
- foreach ($fields as $field)
- {
- $fieldsData['com_fields'][$field->name] = $field->rawvalue;
- }
- }
-
- Factory::getApplication()->triggerEvent('onContentAfterSave', array('com_content.article', &$this->table, false, $fieldsData));
- }
-
- /**
- * Batch move categories to a new category.
- *
- * @param integer $value The new category ID.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True on success.
- *
- * @since 3.8.6
- */
- protected function batchMove($value, $pks, $contexts)
- {
- if (empty($this->batchSet))
- {
- // Set some needed variables.
- $this->user = Factory::getUser();
- $this->table = $this->getTable();
- $this->tableClassName = get_class($this->table);
- $this->contentType = new UCMType;
- $this->type = $this->contentType->getTypeByTable($this->tableClassName);
- }
-
- $categoryId = (int) $value;
-
- if (!$this->checkCategoryId($categoryId))
- {
- return false;
- }
-
- PluginHelper::importPlugin('system');
-
- // Parent exists so we proceed
- foreach ($pks as $pk)
- {
- if (!$this->user->authorise('core.edit', $contexts[$pk]))
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
-
- return false;
- }
-
- // Check that the row actually exists
- if (!$this->table->load($pk))
- {
- if ($error = $this->table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Not fatal error
- $this->setError(Text::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
- continue;
- }
- }
-
- $fields = FieldsHelper::getFields('com_content.article', $this->table, true);
-
- $fieldsData = array();
-
- if (!empty($fields))
- {
- $fieldsData['com_fields'] = array();
-
- foreach ($fields as $field)
- {
- $fieldsData['com_fields'][$field->name] = $field->rawvalue;
- }
- }
-
- // Set the new category ID
- $this->table->catid = $categoryId;
-
- // We don't want to modify tags - so remove the associated tags helper
- if ($this->table instanceof TaggableTableInterface)
- {
- $this->table->clearTagsHelper();
- }
-
- // Check the row.
- if (!$this->table->check())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- // Store the row.
- if (!$this->table->store())
- {
- $this->setError($this->table->getError());
-
- return false;
- }
-
- // Run event for moved article
- Factory::getApplication()->triggerEvent('onContentAfterSave', array('com_content.article', &$this->table, false, $fieldsData));
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || ($record->state != -2))
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.delete', 'com_content.article.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state edited.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
-
- // Check for existing article.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', 'com_content.article.' . (int) $record->id);
- }
-
- // New article, so check against the category.
- if (!empty($record->catid))
- {
- return $user->authorise('core.edit.state', 'com_content.category.' . (int) $record->catid);
- }
-
- // Default to component settings if neither article nor category known.
- return parent::canEditState($record);
- }
-
- /**
- * Prepare and sanitise the table data prior to saving.
- *
- * @param \Joomla\CMS\Table\Table $table A Table object.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- // Set the publish date to now
- if ($table->state == Workflow::CONDITION_PUBLISHED && (int) $table->publish_up == 0)
- {
- $table->publish_up = Factory::getDate()->toSql();
- }
-
- if ($table->state == Workflow::CONDITION_PUBLISHED && intval($table->publish_down) == 0)
- {
- $table->publish_down = null;
- }
-
- // Increment the content version number.
- $table->version++;
-
- // Reorder the articles within the category so the new article is first
- if (empty($table->id))
- {
- $table->reorder('catid = ' . (int) $table->catid . ' AND state >= 0');
- }
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array &$pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function publish(&$pks, $value = 1)
- {
- $this->workflowBeforeStageChange();
-
- return parent::publish($pks, $value);
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- */
- public function getItem($pk = null)
- {
- if ($item = parent::getItem($pk))
- {
- // Convert the params field to an array.
- $registry = new Registry($item->attribs);
- $item->attribs = $registry->toArray();
-
- // Convert the metadata field to an array.
- $registry = new Registry($item->metadata);
- $item->metadata = $registry->toArray();
-
- // Convert the images field to an array.
- $registry = new Registry($item->images);
- $item->images = $registry->toArray();
-
- // Convert the urls field to an array.
- $registry = new Registry($item->urls);
- $item->urls = $registry->toArray();
-
- $item->articletext = ($item->fulltext !== null && trim($item->fulltext) != '') ? $item->introtext . ' ' . $item->fulltext : $item->introtext;
-
- if (!empty($item->id))
- {
- $item->tags = new TagsHelper;
- $item->tags->getTagIds($item->id, 'com_content.article');
-
- $item->featured_up = null;
- $item->featured_down = null;
-
- if ($item->featured)
- {
- // Get featured dates.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('featured_up'),
- $db->quoteName('featured_down'),
- ]
- )
- ->from($db->quoteName('#__content_frontpage'))
- ->where($db->quoteName('content_id') . ' = :id')
- ->bind(':id', $item->id, ParameterType::INTEGER);
-
- $featured = $db->setQuery($query)->loadObject();
-
- if ($featured)
- {
- $item->featured_up = $featured->featured_up;
- $item->featured_down = $featured->featured_down;
- }
- }
- }
- }
-
- // Load associated content items
- $assoc = Associations::isEnabled();
-
- if ($assoc)
- {
- $item->associations = array();
-
- if ($item->id != null)
- {
- $associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', $item->id);
-
- foreach ($associations as $tag => $association)
- {
- $item->associations[$tag] = $association->id;
- }
- }
- }
-
- return $item;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- $app = Factory::getApplication();
-
- // Get the form.
- $form = $this->loadForm('com_content.article', 'article', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Object uses for checking edit state permission of article
- $record = new \stdClass;
-
- // Get ID of the article from input, for frontend, we use a_id while backend uses id
- $articleIdFromInput = $app->isClient('site')
- ? $app->input->getInt('a_id', 0)
- : $app->input->getInt('id', 0);
-
- // On edit article, we get ID of article from article.id state, but on save, we use data from input
- $id = (int) $this->getState('article.id', $articleIdFromInput);
-
- $record->id = $id;
-
- // For new articles we load the potential state + associations
- if ($id == 0 && $formField = $form->getField('catid'))
- {
- $assignedCatids = $data['catid'] ?? $form->getValue('catid');
-
- $assignedCatids = is_array($assignedCatids)
- ? (int) reset($assignedCatids)
- : (int) $assignedCatids;
-
- // Try to get the category from the category field
- if (empty($assignedCatids))
- {
- $assignedCatids = $formField->getAttribute('default', null);
-
- if (!$assignedCatids)
- {
- // Choose the first category available
- $catOptions = $formField->options;
-
- if ($catOptions && !empty($catOptions[0]->value))
- {
- $assignedCatids = (int) $catOptions[0]->value;
- }
- }
- }
-
- // Activate the reload of the form when category is changed
- $form->setFieldAttribute('catid', 'refresh-enabled', true);
- $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids);
- $form->setFieldAttribute('catid', 'refresh-section', 'article');
-
- // Store ID of the category uses for edit state permission check
- $record->catid = $assignedCatids;
- }
- else
- {
- // Get the category which the article is being added to
- if (!empty($data['catid']))
- {
- $catId = (int) $data['catid'];
- }
- else
- {
- $catIds = $form->getValue('catid');
-
- $catId = is_array($catIds)
- ? (int) reset($catIds)
- : (int) $catIds;
-
- if (!$catId)
- {
- $catId = (int) $form->getFieldAttribute('catid', 'default', 0);
- }
- }
-
- $record->catid = $catId;
- }
-
- // Modify the form based on Edit State access controls.
- if (!$this->canEditState($record))
- {
- // Disable fields for display.
- $form->setFieldAttribute('featured', 'disabled', 'true');
- $form->setFieldAttribute('featured_up', 'disabled', 'true');
- $form->setFieldAttribute('featured_down', 'disabled', 'true');
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('publish_up', 'disabled', 'true');
- $form->setFieldAttribute('publish_down', 'disabled', 'true');
- $form->setFieldAttribute('state', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is an article you can edit.
- $form->setFieldAttribute('featured', 'filter', 'unset');
- $form->setFieldAttribute('featured_up', 'filter', 'unset');
- $form->setFieldAttribute('featured_down', 'filter', 'unset');
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('publish_up', 'filter', 'unset');
- $form->setFieldAttribute('publish_down', 'filter', 'unset');
- $form->setFieldAttribute('state', 'filter', 'unset');
- }
-
- // Don't allow to change the created_by user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_by', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $app = Factory::getApplication();
- $data = $app->getUserState('com_content.edit.article.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Pre-select some filters (Status, Category, Language, Access) in edit form if those have been selected in Article Manager: Articles
- if ($this->getState('article.id') == 0)
- {
- $filters = (array) $app->getUserState('com_content.articles.filter');
- $data->set(
- 'state',
- $app->input->getInt(
- 'state',
- ((isset($filters['published']) && $filters['published'] !== '') ? $filters['published'] : null)
- )
- );
- $data->set('catid', $app->input->getInt('catid', (!empty($filters['category_id']) ? $filters['category_id'] : null)));
-
- if ($app->isClient('administrator'))
- {
- $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
- }
-
- $data->set('access',
- $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
- );
- }
- }
-
- // If there are params fieldsets in the form it will fail with a registry object
- if (isset($data->params) && $data->params instanceof Registry)
- {
- $data->params = $data->params->toArray();
- }
-
- $this->preprocessData('com_content.article', $data);
-
- return $data;
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see \Joomla\CMS\Form\FormRule
- * @see JFilterInput
- * @since 3.7.0
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', 'com_content'))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $app = Factory::getApplication();
- $input = $app->input;
- $filter = InputFilter::getInstance();
-
- if (isset($data['metadata']) && isset($data['metadata']['author']))
- {
- $data['metadata']['author'] = $filter->clean($data['metadata']['author'], 'TRIM');
- }
-
- if (isset($data['created_by_alias']))
- {
- $data['created_by_alias'] = $filter->clean($data['created_by_alias'], 'TRIM');
- }
-
- if (isset($data['images']) && is_array($data['images']))
- {
- $registry = new Registry($data['images']);
-
- $data['images'] = (string) $registry;
- }
-
- $this->workflowBeforeSave();
-
- // Create new category, if needed.
- $createCategory = true;
-
- if (is_null($data['catid']))
- {
- // When there is no catid passed don't try to create one
- $createCategory = false;
- }
-
- // If category ID is provided, check if it's valid.
- if (is_numeric($data['catid']) && $data['catid'])
- {
- $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_content');
- }
-
- // Save New Category
- if ($createCategory && $this->canCreateCategory())
- {
- $category = [
- // Remove #new# prefix, if exists.
- 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
- 'parent_id' => 1,
- 'extension' => 'com_content',
- 'language' => $data['language'],
- 'published' => 1,
- ];
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
- $categoryModel = Factory::getApplication()->bootComponent('com_categories')
- ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
-
- // Create new category.
- if (!$categoryModel->save($category))
- {
- $this->setError($categoryModel->getError());
-
- return false;
- }
-
- // Get the Category ID.
- $data['catid'] = $categoryModel->getState('category.id');
- }
-
- if (isset($data['urls']) && is_array($data['urls']))
- {
- $check = $input->post->get('jform', array(), 'array');
-
- foreach ($data['urls'] as $i => $url)
- {
- if ($url != false && ($i == 'urla' || $i == 'urlb' || $i == 'urlc'))
- {
- if (preg_match('~^#[a-zA-Z]{1}[a-zA-Z0-9-_:.]*$~', $check['urls'][$i]) == 1)
- {
- $data['urls'][$i] = $check['urls'][$i];
- }
- else
- {
- $data['urls'][$i] = PunycodeHelper::urlToPunycode($url);
- }
- }
- }
-
- unset($check);
-
- $registry = new Registry($data['urls']);
-
- $data['urls'] = (string) $registry;
- }
-
- // Alter the title for save as copy
- if ($input->get('task') == 'save2copy')
- {
- $origTable = $this->getTable();
-
- if ($app->isClient('site'))
- {
- $origTable->load($input->getInt('a_id'));
-
- if ($origTable->title === $data['title'])
- {
- /**
- * If title of article is not changed, set alias to original article alias so that Joomla! will generate
- * new Title and Alias for the copied article
- */
- $data['alias'] = $origTable->alias;
- }
- else
- {
- $data['alias'] = '';
- }
- }
- else
- {
- $origTable->load($input->getInt('id'));
- }
-
- if ($data['title'] == $origTable->title)
- {
- list($title, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['title']);
- $data['title'] = $title;
- $data['alias'] = $alias;
- }
- elseif ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
- }
-
- // Automatic handling of alias for empty fields
- if (in_array($input->get('task'), array('apply', 'save', 'save2new')) && (!isset($data['id']) || (int) $data['id'] == 0))
- {
- if ($data['alias'] == null)
- {
- if ($app->get('unicodeslugs') == 1)
- {
- $data['alias'] = OutputFilter::stringUrlUnicodeSlug($data['title']);
- }
- else
- {
- $data['alias'] = OutputFilter::stringURLSafe($data['title']);
- }
-
- $table = $this->getTable();
-
- if ($table->load(array('alias' => $data['alias'], 'catid' => $data['catid'])))
- {
- $msg = Text::_('COM_CONTENT_SAVE_WARNING');
- }
-
- list($title, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['title']);
- $data['alias'] = $alias;
-
- if (isset($msg))
- {
- $app->enqueueMessage($msg, 'warning');
- }
- }
- }
-
- if (parent::save($data))
- {
- // Check if featured is set and if not managed by workflow
- if (isset($data['featured']) && !$this->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article'))
- {
- if (!$this->featured(
- $this->getState($this->getName() . '.id'),
- $data['featured'],
- $data['featured_up'] ?? null,
- $data['featured_down'] ?? null
- ))
- {
- return false;
- }
- }
-
- $this->workflowAfterSave($data);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Method to toggle the featured setting of articles.
- *
- * @param array $pks The ids of the items to toggle.
- * @param integer $value The value to toggle to.
- * @param string|Date $featuredUp The date which item featured up.
- * @param string|Date $featuredDown The date which item featured down.
- *
- * @return boolean True on success.
- */
- public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = null)
- {
- // Sanitize the ids.
- $pks = (array) $pks;
- $pks = ArrayHelper::toInteger($pks);
- $value = (int) $value;
- $context = $this->option . '.' . $this->name;
-
- $this->workflowBeforeStageChange();
-
- // Include the plugins for the change of state event.
- PluginHelper::importPlugin($this->events_map['featured']);
-
- // Convert empty strings to null for the query.
- if ($featuredUp === '')
- {
- $featuredUp = null;
- }
-
- if ($featuredDown === '')
- {
- $featuredDown = null;
- }
-
- if (empty($pks))
- {
- $this->setError(Text::_('COM_CONTENT_NO_ITEM_SELECTED'));
-
- return false;
- }
-
- $table = $this->getTable('Featured', 'Administrator');
-
- // Trigger the before change state event.
- $eventResult = Factory::getApplication()->getDispatcher()->dispatch(
- $this->event_before_change_featured,
- AbstractEvent::create(
- $this->event_before_change_featured,
- [
- 'eventClass' => 'Joomla\Component\Content\Administrator\Event\Model\FeatureEvent',
- 'subject' => $this,
- 'extension' => $context,
- 'pks' => $pks,
- 'value' => $value,
- ]
- )
- );
-
- if ($eventResult->getArgument('abort', false))
- {
- $this->setError(Text::_($eventResult->getArgument('abortReason')));
-
- return false;
- }
-
- try
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__content'))
- ->set($db->quoteName('featured') . ' = :featured')
- ->whereIn($db->quoteName('id'), $pks)
- ->bind(':featured', $value, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- if ($value === 0)
- {
- // Adjust the mapping table.
- // Clear the existing features settings.
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__content_frontpage'))
- ->whereIn($db->quoteName('content_id'), $pks);
- $db->setQuery($query);
- $db->execute();
- }
- else
- {
- // First, we find out which of our new featured articles are already featured.
- $query = $db->getQuery(true)
- ->select($db->quoteName('content_id'))
- ->from($db->quoteName('#__content_frontpage'))
- ->whereIn($db->quoteName('content_id'), $pks);
- $db->setQuery($query);
-
- $oldFeatured = $db->loadColumn();
-
- // Update old featured articles
- if (count($oldFeatured))
- {
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__content_frontpage'))
- ->set(
- [
- $db->quoteName('featured_up') . ' = :featuredUp',
- $db->quoteName('featured_down') . ' = :featuredDown',
- ]
- )
- ->whereIn($db->quoteName('content_id'), $oldFeatured)
- ->bind(':featuredUp', $featuredUp, $featuredUp ? ParameterType::STRING : ParameterType::NULL)
- ->bind(':featuredDown', $featuredDown, $featuredDown ? ParameterType::STRING : ParameterType::NULL);
- $db->setQuery($query);
- $db->execute();
- }
-
- // We diff the arrays to get a list of the articles that are newly featured
- $newFeatured = array_diff($pks, $oldFeatured);
-
- // Featuring.
- if ($newFeatured)
- {
- $query = $db->getQuery(true)
- ->insert($db->quoteName('#__content_frontpage'))
- ->columns(
- [
- $db->quoteName('content_id'),
- $db->quoteName('ordering'),
- $db->quoteName('featured_up'),
- $db->quoteName('featured_down'),
- ]
- );
-
- $dataTypes = [
- ParameterType::INTEGER,
- ParameterType::INTEGER,
- $featuredUp ? ParameterType::STRING : ParameterType::NULL,
- $featuredDown ? ParameterType::STRING : ParameterType::NULL,
- ];
-
- foreach ($newFeatured as $pk)
- {
- $query->values(implode(',', $query->bindArray([$pk, 0, $featuredUp, $featuredDown], $dataTypes)));
- }
-
- $db->setQuery($query);
- $db->execute();
- }
- }
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $table->reorder();
-
- // Trigger the change state event.
- Factory::getApplication()->getDispatcher()->dispatch(
- $this->event_after_change_featured,
- AbstractEvent::create(
- $this->event_after_change_featured,
- [
- 'eventClass' => 'Joomla\Component\Content\Administrator\Event\Model\FeatureEvent',
- 'subject' => $this,
- 'extension' => $context,
- 'pks' => $pks,
- 'value' => $value,
- ]
- )
- );
-
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- return [
- $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
- ];
- }
-
- /**
- * Allows preprocessing of the Form object.
- *
- * @param Form $form The form object
- * @param array $data The data to be merged into the form object
- * @param string $group The plugin group to be executed
- *
- * @return void
- *
- * @since 3.0
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- if ($this->canCreateCategory())
- {
- $form->setFieldAttribute('catid', 'allowAdd', 'true');
-
- // Add a prefix for categories created on the fly.
- $form->setFieldAttribute('catid', 'customPrefix', '#new#');
- }
-
- // Association content items
- if (Associations::isEnabled())
- {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (count($languages) > 1)
- {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language)
- {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_article');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
- }
-
- $this->workflowPreprocessForm($form, $data);
-
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Custom clean the cache of com_content and content modules
- *
- * @param string $group The cache group
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_content');
- parent::cleanCache('mod_articles_archive');
- parent::cleanCache('mod_articles_categories');
- parent::cleanCache('mod_articles_category');
- parent::cleanCache('mod_articles_latest');
- parent::cleanCache('mod_articles_news');
- parent::cleanCache('mod_articles_popular');
- }
-
- /**
- * Void hit function for pagebreak when editing content from frontend
- *
- * @return void
- *
- * @since 3.6.0
- */
- public function hit()
- {
- }
-
- /**
- * Is the user allowed to create an on the fly category?
- *
- * @return boolean
- *
- * @since 3.6.1
- */
- private function canCreateCategory()
- {
- return Factory::getUser()->authorise('core.create', 'com_content');
- }
-
- /**
- * Delete #__content_frontpage items if the deleted articles was featured
- *
- * @param object $pks The primary key related to the contents that was deleted.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public function delete(&$pks)
- {
- $return = parent::delete($pks);
-
- if ($return)
- {
- // Now check to see if this articles was featured if so delete it from the #__content_frontpage table
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__content_frontpage'))
- ->whereIn($db->quoteName('content_id'), $pks);
- $db->setQuery($query);
- $db->execute();
-
- $this->workflow->deleteAssociation($pks);
- }
-
- return $return;
- }
+ use WorkflowBehaviorTrait;
+ use VersionableModelTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_CONTENT';
+
+ /**
+ * The type alias for this content type (for example, 'com_content.article').
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_content.article';
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_content.item';
+
+ /**
+ * The event to trigger before changing featured status one or more items.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $event_before_change_featured = null;
+
+ /**
+ * The event to trigger after changing featured status one or more items.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $event_after_change_featured = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param FormFactoryInterface $formFactory The form factory.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null)
+ {
+ $config['events_map'] = $config['events_map'] ?? [];
+
+ $config['events_map'] = array_merge(
+ ['featured' => 'content'],
+ $config['events_map']
+ );
+
+ parent::__construct($config, $factory, $formFactory);
+
+ // Set the featured status change events
+ $this->event_before_change_featured = $config['event_before_change_featured'] ?? $this->event_before_change_featured;
+ $this->event_before_change_featured = $this->event_before_change_featured ?? 'onContentBeforeChangeFeatured';
+ $this->event_after_change_featured = $config['event_after_change_featured'] ?? $this->event_after_change_featured;
+ $this->event_after_change_featured = $this->event_after_change_featured ?? 'onContentAfterChangeFeatured';
+
+ $this->setUpWorkflow('com_content.article');
+ }
+
+ /**
+ * Function that can be overridden to do any data cleanup after batch copying data
+ *
+ * @param TableInterface $table The table object containing the newly created item
+ * @param integer $newId The id of the new item
+ * @param integer $oldId The original item id
+ *
+ * @return void
+ *
+ * @since 3.8.12
+ */
+ protected function cleanupPostBatchCopy(TableInterface $table, $newId, $oldId)
+ {
+ // Check if the article was featured and update the #__content_frontpage table
+ if ($table->featured == 1) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('featured_up'),
+ $db->quoteName('featured_down'),
+ ]
+ )
+ ->from($db->quoteName('#__content_frontpage'))
+ ->where($db->quoteName('content_id') . ' = :oldId')
+ ->bind(':oldId', $oldId, ParameterType::INTEGER);
+
+ $featured = $db->setQuery($query)->loadObject();
+
+ if ($featured) {
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__content_frontpage'))
+ ->values(':newId, 0, :featuredUp, :featuredDown')
+ ->bind(':newId', $newId, ParameterType::INTEGER)
+ ->bind(':featuredUp', $featured->featured_up, $featured->featured_up ? ParameterType::STRING : ParameterType::NULL)
+ ->bind(':featuredDown', $featured->featured_down, $featured->featured_down ? ParameterType::STRING : ParameterType::NULL);
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ }
+
+ $this->workflowCleanupBatchMove($oldId, $newId);
+
+ $oldItem = $this->getTable();
+ $oldItem->load($oldId);
+ $fields = FieldsHelper::getFields('com_content.article', $oldItem, true);
+
+ $fieldsData = array();
+
+ if (!empty($fields)) {
+ $fieldsData['com_fields'] = array();
+
+ foreach ($fields as $field) {
+ $fieldsData['com_fields'][$field->name] = $field->rawvalue;
+ }
+ }
+
+ Factory::getApplication()->triggerEvent('onContentAfterSave', array('com_content.article', &$this->table, false, $fieldsData));
+ }
+
+ /**
+ * Batch move categories to a new category.
+ *
+ * @param integer $value The new category ID.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.8.6
+ */
+ protected function batchMove($value, $pks, $contexts)
+ {
+ if (empty($this->batchSet)) {
+ // Set some needed variables.
+ $this->user = Factory::getUser();
+ $this->table = $this->getTable();
+ $this->tableClassName = get_class($this->table);
+ $this->contentType = new UCMType();
+ $this->type = $this->contentType->getTypeByTable($this->tableClassName);
+ }
+
+ $categoryId = (int) $value;
+
+ if (!$this->checkCategoryId($categoryId)) {
+ return false;
+ }
+
+ PluginHelper::importPlugin('system');
+
+ // Parent exists so we proceed
+ foreach ($pks as $pk) {
+ if (!$this->user->authorise('core.edit', $contexts[$pk])) {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+
+ // Check that the row actually exists
+ if (!$this->table->load($pk)) {
+ if ($error = $this->table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Not fatal error
+ $this->setError(Text::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ continue;
+ }
+ }
+
+ $fields = FieldsHelper::getFields('com_content.article', $this->table, true);
+
+ $fieldsData = array();
+
+ if (!empty($fields)) {
+ $fieldsData['com_fields'] = array();
+
+ foreach ($fields as $field) {
+ $fieldsData['com_fields'][$field->name] = $field->rawvalue;
+ }
+ }
+
+ // Set the new category ID
+ $this->table->catid = $categoryId;
+
+ // We don't want to modify tags - so remove the associated tags helper
+ if ($this->table instanceof TaggableTableInterface) {
+ $this->table->clearTagsHelper();
+ }
+
+ // Check the row.
+ if (!$this->table->check()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ // Store the row.
+ if (!$this->table->store()) {
+ $this->setError($this->table->getError());
+
+ return false;
+ }
+
+ // Run event for moved article
+ Factory::getApplication()->triggerEvent('onContentAfterSave', array('com_content.article', &$this->table, false, $fieldsData));
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || ($record->state != -2)) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.delete', 'com_content.article.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state edited.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+
+ // Check for existing article.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', 'com_content.article.' . (int) $record->id);
+ }
+
+ // New article, so check against the category.
+ if (!empty($record->catid)) {
+ return $user->authorise('core.edit.state', 'com_content.category.' . (int) $record->catid);
+ }
+
+ // Default to component settings if neither article nor category known.
+ return parent::canEditState($record);
+ }
+
+ /**
+ * Prepare and sanitise the table data prior to saving.
+ *
+ * @param \Joomla\CMS\Table\Table $table A Table object.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ // Set the publish date to now
+ if ($table->state == Workflow::CONDITION_PUBLISHED && (int) $table->publish_up == 0) {
+ $table->publish_up = Factory::getDate()->toSql();
+ }
+
+ if ($table->state == Workflow::CONDITION_PUBLISHED && intval($table->publish_down) == 0) {
+ $table->publish_down = null;
+ }
+
+ // Increment the content version number.
+ $table->version++;
+
+ // Reorder the articles within the category so the new article is first
+ if (empty($table->id)) {
+ $table->reorder('catid = ' . (int) $table->catid . ' AND state >= 0');
+ }
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array &$pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $this->workflowBeforeStageChange();
+
+ return parent::publish($pks, $value);
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ */
+ public function getItem($pk = null)
+ {
+ if ($item = parent::getItem($pk)) {
+ // Convert the params field to an array.
+ $registry = new Registry($item->attribs);
+ $item->attribs = $registry->toArray();
+
+ // Convert the metadata field to an array.
+ $registry = new Registry($item->metadata);
+ $item->metadata = $registry->toArray();
+
+ // Convert the images field to an array.
+ $registry = new Registry($item->images);
+ $item->images = $registry->toArray();
+
+ // Convert the urls field to an array.
+ $registry = new Registry($item->urls);
+ $item->urls = $registry->toArray();
+
+ $item->articletext = ($item->fulltext !== null && trim($item->fulltext) != '') ? $item->introtext . ' ' . $item->fulltext : $item->introtext;
+
+ if (!empty($item->id)) {
+ $item->tags = new TagsHelper();
+ $item->tags->getTagIds($item->id, 'com_content.article');
+
+ $item->featured_up = null;
+ $item->featured_down = null;
+
+ if ($item->featured) {
+ // Get featured dates.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('featured_up'),
+ $db->quoteName('featured_down'),
+ ]
+ )
+ ->from($db->quoteName('#__content_frontpage'))
+ ->where($db->quoteName('content_id') . ' = :id')
+ ->bind(':id', $item->id, ParameterType::INTEGER);
+
+ $featured = $db->setQuery($query)->loadObject();
+
+ if ($featured) {
+ $item->featured_up = $featured->featured_up;
+ $item->featured_down = $featured->featured_down;
+ }
+ }
+ }
+ }
+
+ // Load associated content items
+ $assoc = Associations::isEnabled();
+
+ if ($assoc) {
+ $item->associations = array();
+
+ if ($item->id != null) {
+ $associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', $item->id);
+
+ foreach ($associations as $tag => $association) {
+ $item->associations[$tag] = $association->id;
+ }
+ }
+ }
+
+ return $item;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $app = Factory::getApplication();
+
+ // Get the form.
+ $form = $this->loadForm('com_content.article', 'article', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Object uses for checking edit state permission of article
+ $record = new \stdClass();
+
+ // Get ID of the article from input, for frontend, we use a_id while backend uses id
+ $articleIdFromInput = $app->isClient('site')
+ ? $app->input->getInt('a_id', 0)
+ : $app->input->getInt('id', 0);
+
+ // On edit article, we get ID of article from article.id state, but on save, we use data from input
+ $id = (int) $this->getState('article.id', $articleIdFromInput);
+
+ $record->id = $id;
+
+ // For new articles we load the potential state + associations
+ if ($id == 0 && $formField = $form->getField('catid')) {
+ $assignedCatids = $data['catid'] ?? $form->getValue('catid');
+
+ $assignedCatids = is_array($assignedCatids)
+ ? (int) reset($assignedCatids)
+ : (int) $assignedCatids;
+
+ // Try to get the category from the category field
+ if (empty($assignedCatids)) {
+ $assignedCatids = $formField->getAttribute('default', null);
+
+ if (!$assignedCatids) {
+ // Choose the first category available
+ $catOptions = $formField->options;
+
+ if ($catOptions && !empty($catOptions[0]->value)) {
+ $assignedCatids = (int) $catOptions[0]->value;
+ }
+ }
+ }
+
+ // Activate the reload of the form when category is changed
+ $form->setFieldAttribute('catid', 'refresh-enabled', true);
+ $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids);
+ $form->setFieldAttribute('catid', 'refresh-section', 'article');
+
+ // Store ID of the category uses for edit state permission check
+ $record->catid = $assignedCatids;
+ } else {
+ // Get the category which the article is being added to
+ if (!empty($data['catid'])) {
+ $catId = (int) $data['catid'];
+ } else {
+ $catIds = $form->getValue('catid');
+
+ $catId = is_array($catIds)
+ ? (int) reset($catIds)
+ : (int) $catIds;
+
+ if (!$catId) {
+ $catId = (int) $form->getFieldAttribute('catid', 'default', 0);
+ }
+ }
+
+ $record->catid = $catId;
+ }
+
+ // Modify the form based on Edit State access controls.
+ if (!$this->canEditState($record)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('featured', 'disabled', 'true');
+ $form->setFieldAttribute('featured_up', 'disabled', 'true');
+ $form->setFieldAttribute('featured_down', 'disabled', 'true');
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('publish_up', 'disabled', 'true');
+ $form->setFieldAttribute('publish_down', 'disabled', 'true');
+ $form->setFieldAttribute('state', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is an article you can edit.
+ $form->setFieldAttribute('featured', 'filter', 'unset');
+ $form->setFieldAttribute('featured_up', 'filter', 'unset');
+ $form->setFieldAttribute('featured_down', 'filter', 'unset');
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('publish_up', 'filter', 'unset');
+ $form->setFieldAttribute('publish_down', 'filter', 'unset');
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $app = Factory::getApplication();
+ $data = $app->getUserState('com_content.edit.article.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Pre-select some filters (Status, Category, Language, Access) in edit form if those have been selected in Article Manager: Articles
+ if ($this->getState('article.id') == 0) {
+ $filters = (array) $app->getUserState('com_content.articles.filter');
+ $data->set(
+ 'state',
+ $app->input->getInt(
+ 'state',
+ ((isset($filters['published']) && $filters['published'] !== '') ? $filters['published'] : null)
+ )
+ );
+ $data->set('catid', $app->input->getInt('catid', (!empty($filters['category_id']) ? $filters['category_id'] : null)));
+
+ if ($app->isClient('administrator')) {
+ $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
+ }
+
+ $data->set(
+ 'access',
+ $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
+ );
+ }
+ }
+
+ // If there are params fieldsets in the form it will fail with a registry object
+ if (isset($data->params) && $data->params instanceof Registry) {
+ $data->params = $data->params->toArray();
+ }
+
+ $this->preprocessData('com_content.article', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see \Joomla\CMS\Form\FormRule
+ * @see JFilterInput
+ * @since 3.7.0
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', 'com_content')) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $app = Factory::getApplication();
+ $input = $app->input;
+ $filter = InputFilter::getInstance();
+
+ if (isset($data['metadata']) && isset($data['metadata']['author'])) {
+ $data['metadata']['author'] = $filter->clean($data['metadata']['author'], 'TRIM');
+ }
+
+ if (isset($data['created_by_alias'])) {
+ $data['created_by_alias'] = $filter->clean($data['created_by_alias'], 'TRIM');
+ }
+
+ if (isset($data['images']) && is_array($data['images'])) {
+ $registry = new Registry($data['images']);
+
+ $data['images'] = (string) $registry;
+ }
+
+ $this->workflowBeforeSave();
+
+ // Create new category, if needed.
+ $createCategory = true;
+
+ if (is_null($data['catid'])) {
+ // When there is no catid passed don't try to create one
+ $createCategory = false;
+ }
+
+ // If category ID is provided, check if it's valid.
+ if (is_numeric($data['catid']) && $data['catid']) {
+ $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_content');
+ }
+
+ // Save New Category
+ if ($createCategory && $this->canCreateCategory()) {
+ $category = [
+ // Remove #new# prefix, if exists.
+ 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
+ 'parent_id' => 1,
+ 'extension' => 'com_content',
+ 'language' => $data['language'],
+ 'published' => 1,
+ ];
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
+ $categoryModel = Factory::getApplication()->bootComponent('com_categories')
+ ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
+
+ // Create new category.
+ if (!$categoryModel->save($category)) {
+ $this->setError($categoryModel->getError());
+
+ return false;
+ }
+
+ // Get the Category ID.
+ $data['catid'] = $categoryModel->getState('category.id');
+ }
+
+ if (isset($data['urls']) && is_array($data['urls'])) {
+ $check = $input->post->get('jform', array(), 'array');
+
+ foreach ($data['urls'] as $i => $url) {
+ if ($url != false && ($i == 'urla' || $i == 'urlb' || $i == 'urlc')) {
+ if (preg_match('~^#[a-zA-Z]{1}[a-zA-Z0-9-_:.]*$~', $check['urls'][$i]) == 1) {
+ $data['urls'][$i] = $check['urls'][$i];
+ } else {
+ $data['urls'][$i] = PunycodeHelper::urlToPunycode($url);
+ }
+ }
+ }
+
+ unset($check);
+
+ $registry = new Registry($data['urls']);
+
+ $data['urls'] = (string) $registry;
+ }
+
+ // Alter the title for save as copy
+ if ($input->get('task') == 'save2copy') {
+ $origTable = $this->getTable();
+
+ if ($app->isClient('site')) {
+ $origTable->load($input->getInt('a_id'));
+
+ if ($origTable->title === $data['title']) {
+ /**
+ * If title of article is not changed, set alias to original article alias so that Joomla! will generate
+ * new Title and Alias for the copied article
+ */
+ $data['alias'] = $origTable->alias;
+ } else {
+ $data['alias'] = '';
+ }
+ } else {
+ $origTable->load($input->getInt('id'));
+ }
+
+ if ($data['title'] == $origTable->title) {
+ list($title, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['title']);
+ $data['title'] = $title;
+ $data['alias'] = $alias;
+ } elseif ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+ }
+
+ // Automatic handling of alias for empty fields
+ if (in_array($input->get('task'), array('apply', 'save', 'save2new')) && (!isset($data['id']) || (int) $data['id'] == 0)) {
+ if ($data['alias'] == null) {
+ if ($app->get('unicodeslugs') == 1) {
+ $data['alias'] = OutputFilter::stringUrlUnicodeSlug($data['title']);
+ } else {
+ $data['alias'] = OutputFilter::stringURLSafe($data['title']);
+ }
+
+ $table = $this->getTable();
+
+ if ($table->load(array('alias' => $data['alias'], 'catid' => $data['catid']))) {
+ $msg = Text::_('COM_CONTENT_SAVE_WARNING');
+ }
+
+ list($title, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['title']);
+ $data['alias'] = $alias;
+
+ if (isset($msg)) {
+ $app->enqueueMessage($msg, 'warning');
+ }
+ }
+ }
+
+ if (parent::save($data)) {
+ // Check if featured is set and if not managed by workflow
+ if (isset($data['featured']) && !$this->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article')) {
+ if (
+ !$this->featured(
+ $this->getState($this->getName() . '.id'),
+ $data['featured'],
+ $data['featured_up'] ?? null,
+ $data['featured_down'] ?? null
+ )
+ ) {
+ return false;
+ }
+ }
+
+ $this->workflowAfterSave($data);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to toggle the featured setting of articles.
+ *
+ * @param array $pks The ids of the items to toggle.
+ * @param integer $value The value to toggle to.
+ * @param string|Date $featuredUp The date which item featured up.
+ * @param string|Date $featuredDown The date which item featured down.
+ *
+ * @return boolean True on success.
+ */
+ public function featured($pks, $value = 0, $featuredUp = null, $featuredDown = null)
+ {
+ // Sanitize the ids.
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+ $value = (int) $value;
+ $context = $this->option . '.' . $this->name;
+
+ $this->workflowBeforeStageChange();
+
+ // Include the plugins for the change of state event.
+ PluginHelper::importPlugin($this->events_map['featured']);
+
+ // Convert empty strings to null for the query.
+ if ($featuredUp === '') {
+ $featuredUp = null;
+ }
+
+ if ($featuredDown === '') {
+ $featuredDown = null;
+ }
+
+ if (empty($pks)) {
+ $this->setError(Text::_('COM_CONTENT_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ $table = $this->getTable('Featured', 'Administrator');
+
+ // Trigger the before change state event.
+ $eventResult = Factory::getApplication()->getDispatcher()->dispatch(
+ $this->event_before_change_featured,
+ AbstractEvent::create(
+ $this->event_before_change_featured,
+ [
+ 'eventClass' => 'Joomla\Component\Content\Administrator\Event\Model\FeatureEvent',
+ 'subject' => $this,
+ 'extension' => $context,
+ 'pks' => $pks,
+ 'value' => $value,
+ ]
+ )
+ );
+
+ if ($eventResult->getArgument('abort', false)) {
+ $this->setError(Text::_($eventResult->getArgument('abortReason')));
+
+ return false;
+ }
+
+ try {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__content'))
+ ->set($db->quoteName('featured') . ' = :featured')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->bind(':featured', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ if ($value === 0) {
+ // Adjust the mapping table.
+ // Clear the existing features settings.
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__content_frontpage'))
+ ->whereIn($db->quoteName('content_id'), $pks);
+ $db->setQuery($query);
+ $db->execute();
+ } else {
+ // First, we find out which of our new featured articles are already featured.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('content_id'))
+ ->from($db->quoteName('#__content_frontpage'))
+ ->whereIn($db->quoteName('content_id'), $pks);
+ $db->setQuery($query);
+
+ $oldFeatured = $db->loadColumn();
+
+ // Update old featured articles
+ if (count($oldFeatured)) {
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__content_frontpage'))
+ ->set(
+ [
+ $db->quoteName('featured_up') . ' = :featuredUp',
+ $db->quoteName('featured_down') . ' = :featuredDown',
+ ]
+ )
+ ->whereIn($db->quoteName('content_id'), $oldFeatured)
+ ->bind(':featuredUp', $featuredUp, $featuredUp ? ParameterType::STRING : ParameterType::NULL)
+ ->bind(':featuredDown', $featuredDown, $featuredDown ? ParameterType::STRING : ParameterType::NULL);
+ $db->setQuery($query);
+ $db->execute();
+ }
+
+ // We diff the arrays to get a list of the articles that are newly featured
+ $newFeatured = array_diff($pks, $oldFeatured);
+
+ // Featuring.
+ if ($newFeatured) {
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__content_frontpage'))
+ ->columns(
+ [
+ $db->quoteName('content_id'),
+ $db->quoteName('ordering'),
+ $db->quoteName('featured_up'),
+ $db->quoteName('featured_down'),
+ ]
+ );
+
+ $dataTypes = [
+ ParameterType::INTEGER,
+ ParameterType::INTEGER,
+ $featuredUp ? ParameterType::STRING : ParameterType::NULL,
+ $featuredDown ? ParameterType::STRING : ParameterType::NULL,
+ ];
+
+ foreach ($newFeatured as $pk) {
+ $query->values(implode(',', $query->bindArray([$pk, 0, $featuredUp, $featuredDown], $dataTypes)));
+ }
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ }
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $table->reorder();
+
+ // Trigger the change state event.
+ Factory::getApplication()->getDispatcher()->dispatch(
+ $this->event_after_change_featured,
+ AbstractEvent::create(
+ $this->event_after_change_featured,
+ [
+ 'eventClass' => 'Joomla\Component\Content\Administrator\Event\Model\FeatureEvent',
+ 'subject' => $this,
+ 'extension' => $context,
+ 'pks' => $pks,
+ 'value' => $value,
+ ]
+ )
+ );
+
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ return [
+ $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
+ ];
+ }
+
+ /**
+ * Allows preprocessing of the Form object.
+ *
+ * @param Form $form The form object
+ * @param array $data The data to be merged into the form object
+ * @param string $group The plugin group to be executed
+ *
+ * @return void
+ *
+ * @since 3.0
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ if ($this->canCreateCategory()) {
+ $form->setFieldAttribute('catid', 'allowAdd', 'true');
+
+ // Add a prefix for categories created on the fly.
+ $form->setFieldAttribute('catid', 'customPrefix', '#new#');
+ }
+
+ // Association content items
+ if (Associations::isEnabled()) {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (count($languages) > 1) {
+ $addform = new \SimpleXMLElement('');
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('type', 'modal_article');
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+ }
+
+ $form->load($addform, false);
+ }
+ }
+
+ $this->workflowPreprocessForm($form, $data);
+
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Custom clean the cache of com_content and content modules
+ *
+ * @param string $group The cache group
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_content');
+ parent::cleanCache('mod_articles_archive');
+ parent::cleanCache('mod_articles_categories');
+ parent::cleanCache('mod_articles_category');
+ parent::cleanCache('mod_articles_latest');
+ parent::cleanCache('mod_articles_news');
+ parent::cleanCache('mod_articles_popular');
+ }
+
+ /**
+ * Void hit function for pagebreak when editing content from frontend
+ *
+ * @return void
+ *
+ * @since 3.6.0
+ */
+ public function hit()
+ {
+ }
+
+ /**
+ * Is the user allowed to create an on the fly category?
+ *
+ * @return boolean
+ *
+ * @since 3.6.1
+ */
+ private function canCreateCategory()
+ {
+ return Factory::getUser()->authorise('core.create', 'com_content');
+ }
+
+ /**
+ * Delete #__content_frontpage items if the deleted articles was featured
+ *
+ * @param object $pks The primary key related to the contents that was deleted.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public function delete(&$pks)
+ {
+ $return = parent::delete($pks);
+
+ if ($return) {
+ // Now check to see if this articles was featured if so delete it from the #__content_frontpage table
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__content_frontpage'))
+ ->whereIn($db->quoteName('content_id'), $pks);
+ $db->setQuery($query);
+ $db->execute();
+
+ $this->workflow->deleteAssociation($pks);
+ }
+
+ return $return;
+ }
}
diff --git a/administrator/components/com_content/src/Model/ArticlesModel.php b/administrator/components/com_content/src/Model/ArticlesModel.php
index 33cd5215e9120..d7b325ff152c5 100644
--- a/administrator/components/com_content/src/Model/ArticlesModel.php
+++ b/administrator/components/com_content/src/Model/ArticlesModel.php
@@ -1,4 +1,5 @@
get('workflow_enabled'))
- {
- $form->removeField('stage', 'filter');
- }
- else
- {
- $ordering = $form->getField('fullordering', 'list');
-
- $ordering->addOption('JSTAGE_ASC', ['value' => 'ws.title ASC']);
- $ordering->addOption('JSTAGE_DESC', ['value' => 'ws.title DESC']);
- }
-
- return $form;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.id', $direction = 'desc')
- {
- $app = Factory::getApplication();
-
- $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- $search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
- $this->setState('filter.search', $search);
-
- $featured = $this->getUserStateFromRequest($this->context . '.filter.featured', 'filter_featured', '');
- $this->setState('filter.featured', $featured);
-
- $published = $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '');
- $this->setState('filter.published', $published);
-
- $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
- $this->setState('filter.level', $level);
-
- $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
- $this->setState('filter.language', $language);
-
- $formSubmitted = $app->input->post->get('form_submitted');
-
- // Gets the value of a user state variable and sets it in the session
- $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
- $this->getUserStateFromRequest($this->context . '.filter.author_id', 'filter_author_id');
- $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id');
- $this->getUserStateFromRequest($this->context . '.filter.tag', 'filter_tag', '');
-
- if ($formSubmitted)
- {
- $access = $app->input->post->get('access');
- $this->setState('filter.access', $access);
-
- $authorId = $app->input->post->get('author_id');
- $this->setState('filter.author_id', $authorId);
-
- $categoryId = $app->input->post->get('category_id');
- $this->setState('filter.category_id', $categoryId);
-
- $tag = $app->input->post->get('tag');
- $this->setState('filter.tag', $tag);
- }
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language
- if (!empty($forcedLanguage))
- {
- $this->setState('filter.language', $forcedLanguage);
- $this->setState('filter.forcedLanguage', $forcedLanguage);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . serialize($this->getState('filter.access'));
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . serialize($this->getState('filter.category_id'));
- $id .= ':' . serialize($this->getState('filter.author_id'));
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . serialize($this->getState('filter.tag'));
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- $params = ComponentHelper::getParams('com_content');
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.asset_id'),
- $db->quoteName('a.title'),
- $db->quoteName('a.alias'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.catid'),
- $db->quoteName('a.state'),
- $db->quoteName('a.access'),
- $db->quoteName('a.created'),
- $db->quoteName('a.created_by'),
- $db->quoteName('a.created_by_alias'),
- $db->quoteName('a.modified'),
- $db->quoteName('a.ordering'),
- $db->quoteName('a.featured'),
- $db->quoteName('a.language'),
- $db->quoteName('a.hits'),
- $db->quoteName('a.publish_up'),
- $db->quoteName('a.publish_down'),
- $db->quoteName('a.introtext'),
- $db->quoteName('a.fulltext'),
- $db->quoteName('a.note'),
- $db->quoteName('a.images'),
- $db->quoteName('a.metakey'),
- $db->quoteName('a.metadesc'),
- $db->quoteName('a.metadata'),
- $db->quoteName('a.version'),
- ]
- )
- )
- ->select(
- [
- $db->quoteName('fp.featured_up'),
- $db->quoteName('fp.featured_down'),
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- $db->quoteName('uc.name', 'editor'),
- $db->quoteName('ag.title', 'access_level'),
- $db->quoteName('c.title', 'category_title'),
- $db->quoteName('c.created_user_id', 'category_uid'),
- $db->quoteName('c.level', 'category_level'),
- $db->quoteName('c.published', 'category_published'),
- $db->quoteName('parent.title', 'parent_category_title'),
- $db->quoteName('parent.id', 'parent_category_id'),
- $db->quoteName('parent.created_user_id', 'parent_category_uid'),
- $db->quoteName('parent.level', 'parent_category_level'),
- $db->quoteName('ua.name', 'author_name'),
- $db->quoteName('wa.stage_id', 'stage_id'),
- $db->quoteName('ws.title', 'stage_title'),
- $db->quoteName('ws.workflow_id', 'workflow_id'),
- $db->quoteName('w.title', 'workflow_title'),
- ]
- )
- ->from($db->quoteName('#__content', 'a'))
- ->where($db->quoteName('wa.extension') . ' = ' . $db->quote('com_content.article'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
- ->join('LEFT', $db->quoteName('#__content_frontpage', 'fp'), $db->quoteName('fp.content_id') . ' = ' . $db->quoteName('a.id'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
- ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'))
- ->join('LEFT', $db->quoteName('#__categories', 'parent'), $db->quoteName('parent.id') . ' = ' . $db->quoteName('c.parent_id'))
- ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by'))
- ->join('INNER', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id'))
- ->join('INNER', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id'))
- ->join('INNER', $db->quoteName('#__workflows', 'w'), $db->quoteName('w.id') . ' = ' . $db->quoteName('ws.workflow_id'));
-
- if (PluginHelper::isEnabled('content', 'vote'))
- {
- $query->select(
- [
- 'COALESCE(NULLIF(ROUND(' . $db->quoteName('v.rating_sum') . ' / ' . $db->quoteName('v.rating_count') . ', 0), 0), 0)'
- . ' AS ' . $db->quoteName('rating'),
- 'COALESCE(NULLIF(' . $db->quoteName('v.rating_count') . ', 0), 0) AS ' . $db->quoteName('rating_count'),
- ]
- )
- ->join('LEFT', $db->quoteName('#__content_rating', 'v'), $db->quoteName('a.id') . ' = ' . $db->quoteName('v.content_id'));
- }
-
- // Join over the associations.
- if (Associations::isEnabled())
- {
- $subQuery = $db->getQuery(true)
- ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
- ->from($db->quoteName('#__associations', 'asso1'))
- ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
- ->where(
- [
- $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
- $db->quoteName('asso1.context') . ' = ' . $db->quote('com_content.item'),
- ]
- );
-
- $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
- }
-
- // Filter by access level.
- $access = $this->getState('filter.access');
-
- if (is_numeric($access))
- {
- $access = (int) $access;
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
- elseif (is_array($access))
- {
- $access = ArrayHelper::toInteger($access);
- $query->whereIn($db->quoteName('a.access'), $access);
- }
-
- // Filter by featured.
- $featured = (string) $this->getState('filter.featured');
-
- if (\in_array($featured, ['0','1']))
- {
- $featured = (int) $featured;
- $query->where($db->quoteName('a.featured') . ' = :featured')
- ->bind(':featured', $featured, ParameterType::INTEGER);
- }
-
- // Filter by access level on categories.
- if (!$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- $query->whereIn($db->quoteName('c.access'), $groups);
- }
-
- // Filter by published state
- $workflowStage = (string) $this->getState('filter.stage');
-
- if ($params->get('workflow_enabled') && is_numeric($workflowStage))
- {
- $workflowStage = (int) $workflowStage;
- $query->where($db->quoteName('wa.stage_id') . ' = :stage')
- ->bind(':stage', $workflowStage, ParameterType::INTEGER);
- }
-
- $published = (string) $this->getState('filter.published');
-
- if ($published !== '*')
- {
- if (is_numeric($published))
- {
- $state = (int) $published;
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif (!is_numeric($workflowStage))
- {
- $query->whereIn(
- $db->quoteName('a.state'),
- [
- ContentComponent::CONDITION_PUBLISHED,
- ContentComponent::CONDITION_UNPUBLISHED,
- ]
- );
- }
- }
-
- // Filter by categories and by level
- $categoryId = $this->getState('filter.category_id', array());
- $level = (int) $this->getState('filter.level');
-
- if (!is_array($categoryId))
- {
- $categoryId = $categoryId ? array($categoryId) : array();
- }
-
- // Case: Using both categories filter and by level filter
- if (count($categoryId))
- {
- $categoryId = ArrayHelper::toInteger($categoryId);
- $categoryTable = Table::getInstance('Category', 'JTable');
- $subCatItemsWhere = array();
-
- foreach ($categoryId as $key => $filter_catid)
- {
- $categoryTable->load($filter_catid);
-
- // Because values to $query->bind() are passed by reference, using $query->bindArray() here instead to prevent overwriting.
- $valuesToBind = [$categoryTable->lft, $categoryTable->rgt];
-
- if ($level)
- {
- $valuesToBind[] = $level + $categoryTable->level - 1;
- }
-
- // Bind values and get parameter names.
- $bounded = $query->bindArray($valuesToBind);
-
- $categoryWhere = $db->quoteName('c.lft') . ' >= ' . $bounded[0] . ' AND ' . $db->quoteName('c.rgt') . ' <= ' . $bounded[1];
-
- if ($level)
- {
- $categoryWhere .= ' AND ' . $db->quoteName('c.level') . ' <= ' . $bounded[2];
- }
-
- $subCatItemsWhere[] = '(' . $categoryWhere . ')';
- }
-
- $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')');
- }
-
- // Case: Using only the by level filter
- elseif ($level = (int) $level)
- {
- $query->where($db->quoteName('c.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter by author
- $authorId = $this->getState('filter.author_id');
-
- if (is_numeric($authorId))
- {
- $authorId = (int) $authorId;
- $type = $this->getState('filter.author_id.include', true) ? ' = ' : ' <> ';
- $query->where($db->quoteName('a.created_by') . $type . ':authorId')
- ->bind(':authorId', $authorId, ParameterType::INTEGER);
- }
- elseif (is_array($authorId))
- {
- // Check to see if by_me is in the array
- if (\in_array('by_me', $authorId))
-
- // Replace by_me with the current user id in the array
- {
- $authorId['by_me'] = $user->id;
- }
-
- $authorId = ArrayHelper::toInteger($authorId);
- $query->whereIn($db->quoteName('a.created_by'), $authorId);
- }
-
- // Filter by search in title.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'author:') === 0)
- {
- $search = '%' . substr($search, 7) . '%';
- $query->where('(' . $db->quoteName('ua.name') . ' LIKE :search1 OR ' . $db->quoteName('ua.username') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
- elseif (stripos($search, 'content:') === 0)
- {
- $search = '%' . substr($search, 8) . '%';
- $query->where('(' . $db->quoteName('a.introtext') . ' LIKE :search1 OR ' . $db->quoteName('a.fulltext') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where(
- '(' . $db->quoteName('a.title') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2'
- . ' OR ' . $db->quoteName('a.note') . ' LIKE :search3)'
- )
- ->bind([':search1', ':search2', ':search3'], $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
-
- // Filter by a single or group of tags.
- $tag = $this->getState('filter.tag');
-
- // Run simplified query when filtering by one tag.
- if (\is_array($tag) && \count($tag) === 1)
- {
- $tag = $tag[0];
- }
-
- if ($tag && \is_array($tag))
- {
- $tag = ArrayHelper::toInteger($tag);
-
- $subQuery = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('content_item_id'))
- ->from($db->quoteName('#__contentitem_tag_map'))
- ->where(
- [
- $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
- $db->quoteName('type_alias') . ' = ' . $db->quote('com_content.article'),
- ]
- );
-
- $query->join(
- 'INNER',
- '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- );
- }
- elseif ($tag = (int) $tag)
- {
- $query->join(
- 'INNER',
- $db->quoteName('#__contentitem_tag_map', 'tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- )
- ->where(
- [
- $db->quoteName('tagmap.tag_id') . ' = :tag',
- $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_content.article'),
- ]
- )
- ->bind(':tag', $tag, ParameterType::INTEGER);
- }
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 'a.id');
- $orderDirn = $this->state->get('list.direction', 'DESC');
-
- if ($orderCol === 'a.ordering' || $orderCol === 'category_title')
- {
- $ordering = [
- $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
- $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
- ];
- }
- else
- {
- $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
- }
-
- $query->order($ordering);
-
- return $query;
- }
-
- /**
- * Method to get all transitions at once for all articles
- *
- * @return array|boolean
- *
- * @since 4.0.0
- */
- public function getTransitions()
- {
- // Get a storage key.
- $store = $this->getStoreId('getTransitions');
-
- // Try to load the data from internal storage.
- if (isset($this->cache[$store]))
- {
- return $this->cache[$store];
- }
-
- $db = $this->getDatabase();
- $user = Factory::getUser();
-
- $items = $this->getItems();
-
- if ($items === false)
- {
- return false;
- }
-
- $stage_ids = ArrayHelper::getColumn($items, 'stage_id');
- $stage_ids = ArrayHelper::toInteger($stage_ids);
- $stage_ids = array_values(array_unique(array_filter($stage_ids)));
-
- $workflow_ids = ArrayHelper::getColumn($items, 'workflow_id');
- $workflow_ids = ArrayHelper::toInteger($workflow_ids);
- $workflow_ids = array_values(array_unique(array_filter($workflow_ids)));
-
- $this->cache[$store] = array();
-
- try
- {
- if (count($stage_ids) || count($workflow_ids))
- {
- Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
-
- $query = $db->getQuery(true);
-
- $query ->select(
- [
- $db->quoteName('t.id', 'value'),
- $db->quoteName('t.title', 'text'),
- $db->quoteName('t.from_stage_id'),
- $db->quoteName('t.to_stage_id'),
- $db->quoteName('s.id', 'stage_id'),
- $db->quoteName('s.title', 'stage_title'),
- $db->quoteName('t.workflow_id'),
- ]
- )
- ->from($db->quoteName('#__workflow_transitions', 't'))
- ->innerJoin(
- $db->quoteName('#__workflow_stages', 's'),
- $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id')
- )
- ->where(
- [
- $db->quoteName('t.published') . ' = 1',
- $db->quoteName('s.published') . ' = 1',
- ]
- )
- ->order($db->quoteName('t.ordering'));
-
- $where = [];
-
- if (count($stage_ids))
- {
- $where[] = $db->quoteName('t.from_stage_id') . ' IN (' . implode(',', $query->bindArray($stage_ids)) . ')';
- }
-
- if (count($workflow_ids))
- {
- $where[] = '(' . $db->quoteName('t.from_stage_id') . ' = -1 AND ' . $db->quoteName('t.workflow_id') . ' IN (' . implode(',', $query->bindArray($workflow_ids)) . '))';
- }
-
- $query->where('((' . implode(') OR (', $where) . '))');
-
- $transitions = $db->setQuery($query)->loadAssocList();
-
- foreach ($transitions as $key => $transition)
- {
- if (!$user->authorise('core.execute.transition', 'com_content.transition.' . (int) $transition['value']))
- {
- unset($transitions[$key]);
- }
-
- $transitions[$key]['text'] = Text::_($transition['text']);
- }
-
- $this->cache[$store] = $transitions;
- }
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $this->cache[$store];
- }
-
- /**
- * Method to get a list of articles.
- * Overridden to add item type alias.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 4.0.0
- */
- public function getItems()
- {
- $items = parent::getItems();
-
- foreach ($items as $item)
- {
- $item->typeAlias = 'com_content.article';
-
- if (isset($item->metadata))
- {
- $registry = new Registry($item->metadata);
- $item->metadata = $registry->toArray();
- }
- }
-
- return $items;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ * @see \Joomla\CMS\MVC\Controller\BaseController
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'alias', 'a.alias',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'catid', 'a.catid', 'category_title',
+ 'state', 'a.state',
+ 'access', 'a.access', 'access_level',
+ 'created', 'a.created',
+ 'modified', 'a.modified',
+ 'created_by', 'a.created_by',
+ 'created_by_alias', 'a.created_by_alias',
+ 'ordering', 'a.ordering',
+ 'featured', 'a.featured',
+ 'featured_up', 'fp.featured_up',
+ 'featured_down', 'fp.featured_down',
+ 'language', 'a.language',
+ 'hits', 'a.hits',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'published', 'a.published',
+ 'author_id',
+ 'category_id',
+ 'level',
+ 'tag',
+ 'rating_count', 'rating',
+ 'stage', 'wa.stage_id',
+ 'ws.title'
+ );
+
+ if (Associations::isEnabled()) {
+ $config['filter_fields'][] = 'association';
+ }
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return \Joomla\CMS\Form\Form|null The Form object or null if the form can't be found
+ *
+ * @since 3.2
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ $params = ComponentHelper::getParams('com_content');
+
+ if (!$params->get('workflow_enabled')) {
+ $form->removeField('stage', 'filter');
+ } else {
+ $ordering = $form->getField('fullordering', 'list');
+
+ $ordering->addOption('JSTAGE_ASC', ['value' => 'ws.title ASC']);
+ $ordering->addOption('JSTAGE_DESC', ['value' => 'ws.title DESC']);
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.id', $direction = 'desc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ $search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
+ $this->setState('filter.search', $search);
+
+ $featured = $this->getUserStateFromRequest($this->context . '.filter.featured', 'filter_featured', '');
+ $this->setState('filter.featured', $featured);
+
+ $published = $this->getUserStateFromRequest($this->context . '.filter.published', 'filter_published', '');
+ $this->setState('filter.published', $published);
+
+ $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
+ $this->setState('filter.level', $level);
+
+ $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
+ $this->setState('filter.language', $language);
+
+ $formSubmitted = $app->input->post->get('form_submitted');
+
+ // Gets the value of a user state variable and sets it in the session
+ $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
+ $this->getUserStateFromRequest($this->context . '.filter.author_id', 'filter_author_id');
+ $this->getUserStateFromRequest($this->context . '.filter.category_id', 'filter_category_id');
+ $this->getUserStateFromRequest($this->context . '.filter.tag', 'filter_tag', '');
+
+ if ($formSubmitted) {
+ $access = $app->input->post->get('access');
+ $this->setState('filter.access', $access);
+
+ $authorId = $app->input->post->get('author_id');
+ $this->setState('filter.author_id', $authorId);
+
+ $categoryId = $app->input->post->get('category_id');
+ $this->setState('filter.category_id', $categoryId);
+
+ $tag = $app->input->post->get('tag');
+ $this->setState('filter.tag', $tag);
+ }
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language
+ if (!empty($forcedLanguage)) {
+ $this->setState('filter.language', $forcedLanguage);
+ $this->setState('filter.forcedLanguage', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . serialize($this->getState('filter.access'));
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . serialize($this->getState('filter.category_id'));
+ $id .= ':' . serialize($this->getState('filter.author_id'));
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . serialize($this->getState('filter.tag'));
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ $params = ComponentHelper::getParams('com_content');
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.asset_id'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.alias'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.catid'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.access'),
+ $db->quoteName('a.created'),
+ $db->quoteName('a.created_by'),
+ $db->quoteName('a.created_by_alias'),
+ $db->quoteName('a.modified'),
+ $db->quoteName('a.ordering'),
+ $db->quoteName('a.featured'),
+ $db->quoteName('a.language'),
+ $db->quoteName('a.hits'),
+ $db->quoteName('a.publish_up'),
+ $db->quoteName('a.publish_down'),
+ $db->quoteName('a.introtext'),
+ $db->quoteName('a.fulltext'),
+ $db->quoteName('a.note'),
+ $db->quoteName('a.images'),
+ $db->quoteName('a.metakey'),
+ $db->quoteName('a.metadesc'),
+ $db->quoteName('a.metadata'),
+ $db->quoteName('a.version'),
+ ]
+ )
+ )
+ ->select(
+ [
+ $db->quoteName('fp.featured_up'),
+ $db->quoteName('fp.featured_down'),
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ $db->quoteName('uc.name', 'editor'),
+ $db->quoteName('ag.title', 'access_level'),
+ $db->quoteName('c.title', 'category_title'),
+ $db->quoteName('c.created_user_id', 'category_uid'),
+ $db->quoteName('c.level', 'category_level'),
+ $db->quoteName('c.published', 'category_published'),
+ $db->quoteName('parent.title', 'parent_category_title'),
+ $db->quoteName('parent.id', 'parent_category_id'),
+ $db->quoteName('parent.created_user_id', 'parent_category_uid'),
+ $db->quoteName('parent.level', 'parent_category_level'),
+ $db->quoteName('ua.name', 'author_name'),
+ $db->quoteName('wa.stage_id', 'stage_id'),
+ $db->quoteName('ws.title', 'stage_title'),
+ $db->quoteName('ws.workflow_id', 'workflow_id'),
+ $db->quoteName('w.title', 'workflow_title'),
+ ]
+ )
+ ->from($db->quoteName('#__content', 'a'))
+ ->where($db->quoteName('wa.extension') . ' = ' . $db->quote('com_content.article'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
+ ->join('LEFT', $db->quoteName('#__content_frontpage', 'fp'), $db->quoteName('fp.content_id') . ' = ' . $db->quoteName('a.id'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
+ ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'))
+ ->join('LEFT', $db->quoteName('#__categories', 'parent'), $db->quoteName('parent.id') . ' = ' . $db->quoteName('c.parent_id'))
+ ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by'))
+ ->join('INNER', $db->quoteName('#__workflow_associations', 'wa'), $db->quoteName('wa.item_id') . ' = ' . $db->quoteName('a.id'))
+ ->join('INNER', $db->quoteName('#__workflow_stages', 'ws'), $db->quoteName('ws.id') . ' = ' . $db->quoteName('wa.stage_id'))
+ ->join('INNER', $db->quoteName('#__workflows', 'w'), $db->quoteName('w.id') . ' = ' . $db->quoteName('ws.workflow_id'));
+
+ if (PluginHelper::isEnabled('content', 'vote')) {
+ $query->select(
+ [
+ 'COALESCE(NULLIF(ROUND(' . $db->quoteName('v.rating_sum') . ' / ' . $db->quoteName('v.rating_count') . ', 0), 0), 0)'
+ . ' AS ' . $db->quoteName('rating'),
+ 'COALESCE(NULLIF(' . $db->quoteName('v.rating_count') . ', 0), 0) AS ' . $db->quoteName('rating_count'),
+ ]
+ )
+ ->join('LEFT', $db->quoteName('#__content_rating', 'v'), $db->quoteName('a.id') . ' = ' . $db->quoteName('v.content_id'));
+ }
+
+ // Join over the associations.
+ if (Associations::isEnabled()) {
+ $subQuery = $db->getQuery(true)
+ ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
+ ->from($db->quoteName('#__associations', 'asso1'))
+ ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
+ ->where(
+ [
+ $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
+ $db->quoteName('asso1.context') . ' = ' . $db->quote('com_content.item'),
+ ]
+ );
+
+ $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+ }
+
+ // Filter by access level.
+ $access = $this->getState('filter.access');
+
+ if (is_numeric($access)) {
+ $access = (int) $access;
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ } elseif (is_array($access)) {
+ $access = ArrayHelper::toInteger($access);
+ $query->whereIn($db->quoteName('a.access'), $access);
+ }
+
+ // Filter by featured.
+ $featured = (string) $this->getState('filter.featured');
+
+ if (\in_array($featured, ['0','1'])) {
+ $featured = (int) $featured;
+ $query->where($db->quoteName('a.featured') . ' = :featured')
+ ->bind(':featured', $featured, ParameterType::INTEGER);
+ }
+
+ // Filter by access level on categories.
+ if (!$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ $query->whereIn($db->quoteName('c.access'), $groups);
+ }
+
+ // Filter by published state
+ $workflowStage = (string) $this->getState('filter.stage');
+
+ if ($params->get('workflow_enabled') && is_numeric($workflowStage)) {
+ $workflowStage = (int) $workflowStage;
+ $query->where($db->quoteName('wa.stage_id') . ' = :stage')
+ ->bind(':stage', $workflowStage, ParameterType::INTEGER);
+ }
+
+ $published = (string) $this->getState('filter.published');
+
+ if ($published !== '*') {
+ if (is_numeric($published)) {
+ $state = (int) $published;
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif (!is_numeric($workflowStage)) {
+ $query->whereIn(
+ $db->quoteName('a.state'),
+ [
+ ContentComponent::CONDITION_PUBLISHED,
+ ContentComponent::CONDITION_UNPUBLISHED,
+ ]
+ );
+ }
+ }
+
+ // Filter by categories and by level
+ $categoryId = $this->getState('filter.category_id', array());
+ $level = (int) $this->getState('filter.level');
+
+ if (!is_array($categoryId)) {
+ $categoryId = $categoryId ? array($categoryId) : array();
+ }
+
+ // Case: Using both categories filter and by level filter
+ if (count($categoryId)) {
+ $categoryId = ArrayHelper::toInteger($categoryId);
+ $categoryTable = Table::getInstance('Category', 'JTable');
+ $subCatItemsWhere = array();
+
+ foreach ($categoryId as $key => $filter_catid) {
+ $categoryTable->load($filter_catid);
+
+ // Because values to $query->bind() are passed by reference, using $query->bindArray() here instead to prevent overwriting.
+ $valuesToBind = [$categoryTable->lft, $categoryTable->rgt];
+
+ if ($level) {
+ $valuesToBind[] = $level + $categoryTable->level - 1;
+ }
+
+ // Bind values and get parameter names.
+ $bounded = $query->bindArray($valuesToBind);
+
+ $categoryWhere = $db->quoteName('c.lft') . ' >= ' . $bounded[0] . ' AND ' . $db->quoteName('c.rgt') . ' <= ' . $bounded[1];
+
+ if ($level) {
+ $categoryWhere .= ' AND ' . $db->quoteName('c.level') . ' <= ' . $bounded[2];
+ }
+
+ $subCatItemsWhere[] = '(' . $categoryWhere . ')';
+ }
+
+ $query->where('(' . implode(' OR ', $subCatItemsWhere) . ')');
+ } elseif ($level = (int) $level) {
+ // Case: Using only the by level filter
+ $query->where($db->quoteName('c.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter by author
+ $authorId = $this->getState('filter.author_id');
+
+ if (is_numeric($authorId)) {
+ $authorId = (int) $authorId;
+ $type = $this->getState('filter.author_id.include', true) ? ' = ' : ' <> ';
+ $query->where($db->quoteName('a.created_by') . $type . ':authorId')
+ ->bind(':authorId', $authorId, ParameterType::INTEGER);
+ } elseif (is_array($authorId)) {
+ // Check to see if by_me is in the array
+ if (\in_array('by_me', $authorId)) {
+ // Replace by_me with the current user id in the array
+ $authorId['by_me'] = $user->id;
+ }
+
+ $authorId = ArrayHelper::toInteger($authorId);
+ $query->whereIn($db->quoteName('a.created_by'), $authorId);
+ }
+
+ // Filter by search in title.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } elseif (stripos($search, 'author:') === 0) {
+ $search = '%' . substr($search, 7) . '%';
+ $query->where('(' . $db->quoteName('ua.name') . ' LIKE :search1 OR ' . $db->quoteName('ua.username') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ } elseif (stripos($search, 'content:') === 0) {
+ $search = '%' . substr($search, 8) . '%';
+ $query->where('(' . $db->quoteName('a.introtext') . ' LIKE :search1 OR ' . $db->quoteName('a.fulltext') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where(
+ '(' . $db->quoteName('a.title') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2'
+ . ' OR ' . $db->quoteName('a.note') . ' LIKE :search3)'
+ )
+ ->bind([':search1', ':search2', ':search3'], $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Filter by a single or group of tags.
+ $tag = $this->getState('filter.tag');
+
+ // Run simplified query when filtering by one tag.
+ if (\is_array($tag) && \count($tag) === 1) {
+ $tag = $tag[0];
+ }
+
+ if ($tag && \is_array($tag)) {
+ $tag = ArrayHelper::toInteger($tag);
+
+ $subQuery = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('content_item_id'))
+ ->from($db->quoteName('#__contentitem_tag_map'))
+ ->where(
+ [
+ $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
+ $db->quoteName('type_alias') . ' = ' . $db->quote('com_content.article'),
+ ]
+ );
+
+ $query->join(
+ 'INNER',
+ '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ );
+ } elseif ($tag = (int) $tag) {
+ $query->join(
+ 'INNER',
+ $db->quoteName('#__contentitem_tag_map', 'tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ )
+ ->where(
+ [
+ $db->quoteName('tagmap.tag_id') . ' = :tag',
+ $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_content.article'),
+ ]
+ )
+ ->bind(':tag', $tag, ParameterType::INTEGER);
+ }
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 'a.id');
+ $orderDirn = $this->state->get('list.direction', 'DESC');
+
+ if ($orderCol === 'a.ordering' || $orderCol === 'category_title') {
+ $ordering = [
+ $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
+ $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
+ ];
+ } else {
+ $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
+ }
+
+ $query->order($ordering);
+
+ return $query;
+ }
+
+ /**
+ * Method to get all transitions at once for all articles
+ *
+ * @return array|boolean
+ *
+ * @since 4.0.0
+ */
+ public function getTransitions()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId('getTransitions');
+
+ // Try to load the data from internal storage.
+ if (isset($this->cache[$store])) {
+ return $this->cache[$store];
+ }
+
+ $db = $this->getDatabase();
+ $user = Factory::getUser();
+
+ $items = $this->getItems();
+
+ if ($items === false) {
+ return false;
+ }
+
+ $stage_ids = ArrayHelper::getColumn($items, 'stage_id');
+ $stage_ids = ArrayHelper::toInteger($stage_ids);
+ $stage_ids = array_values(array_unique(array_filter($stage_ids)));
+
+ $workflow_ids = ArrayHelper::getColumn($items, 'workflow_id');
+ $workflow_ids = ArrayHelper::toInteger($workflow_ids);
+ $workflow_ids = array_values(array_unique(array_filter($workflow_ids)));
+
+ $this->cache[$store] = array();
+
+ try {
+ if (count($stage_ids) || count($workflow_ids)) {
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $query = $db->getQuery(true);
+
+ $query ->select(
+ [
+ $db->quoteName('t.id', 'value'),
+ $db->quoteName('t.title', 'text'),
+ $db->quoteName('t.from_stage_id'),
+ $db->quoteName('t.to_stage_id'),
+ $db->quoteName('s.id', 'stage_id'),
+ $db->quoteName('s.title', 'stage_title'),
+ $db->quoteName('t.workflow_id'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->innerJoin(
+ $db->quoteName('#__workflow_stages', 's'),
+ $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id')
+ )
+ ->where(
+ [
+ $db->quoteName('t.published') . ' = 1',
+ $db->quoteName('s.published') . ' = 1',
+ ]
+ )
+ ->order($db->quoteName('t.ordering'));
+
+ $where = [];
+
+ if (count($stage_ids)) {
+ $where[] = $db->quoteName('t.from_stage_id') . ' IN (' . implode(',', $query->bindArray($stage_ids)) . ')';
+ }
+
+ if (count($workflow_ids)) {
+ $where[] = '(' . $db->quoteName('t.from_stage_id') . ' = -1 AND ' . $db->quoteName('t.workflow_id') . ' IN (' . implode(',', $query->bindArray($workflow_ids)) . '))';
+ }
+
+ $query->where('((' . implode(') OR (', $where) . '))');
+
+ $transitions = $db->setQuery($query)->loadAssocList();
+
+ foreach ($transitions as $key => $transition) {
+ if (!$user->authorise('core.execute.transition', 'com_content.transition.' . (int) $transition['value'])) {
+ unset($transitions[$key]);
+ }
+
+ $transitions[$key]['text'] = Text::_($transition['text']);
+ }
+
+ $this->cache[$store] = $transitions;
+ }
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $this->cache[$store];
+ }
+
+ /**
+ * Method to get a list of articles.
+ * Overridden to add item type alias.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 4.0.0
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ foreach ($items as $item) {
+ $item->typeAlias = 'com_content.article';
+
+ if (isset($item->metadata)) {
+ $registry = new Registry($item->metadata);
+ $item->metadata = $registry->toArray();
+ }
+ }
+
+ return $items;
+ }
}
diff --git a/administrator/components/com_content/src/Model/FeatureModel.php b/administrator/components/com_content/src/Model/FeatureModel.php
index 8eebd6ae4d32b..ecc1ffc04e6e6 100644
--- a/administrator/components/com_content/src/Model/FeatureModel.php
+++ b/administrator/components/com_content/src/Model/FeatureModel.php
@@ -1,4 +1,5 @@
setState('filter.featured', 1);
- }
+ // Filter by featured articles.
+ $this->setState('filter.featured', 1);
+ }
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 4.0.0
- */
- protected function getListQuery()
- {
- $query = parent::getListQuery();
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 4.0.0
+ */
+ protected function getListQuery()
+ {
+ $query = parent::getListQuery();
- $query->select($this->getDatabase()->quoteName('fp.ordering'));
+ $query->select($this->getDatabase()->quoteName('fp.ordering'));
- return $query;
- }
+ return $query;
+ }
}
diff --git a/administrator/components/com_content/src/Service/HTML/AdministratorService.php b/administrator/components/com_content/src/Service/HTML/AdministratorService.php
index 82691d820b8e9..408f0a226e939 100644
--- a/administrator/components/com_content/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_content/src/Service/HTML/AdministratorService.php
@@ -1,4 +1,5 @@
$associated)
- {
- $associations[$tag] = (int) $associated->id;
- }
+ // Get the associations
+ if ($associations = Associations::getAssociations('com_content', '#__content', 'com_content.item', $articleid)) {
+ foreach ($associations as $tag => $associated) {
+ $associations[$tag] = (int) $associated->id;
+ }
- // Get the associated menu items
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(
- [
- 'c.*',
- $db->quoteName('l.sef', 'lang_sef'),
- $db->quoteName('l.lang_code'),
- $db->quoteName('cat.title', 'category_title'),
- $db->quoteName('l.image'),
- $db->quoteName('l.title', 'language_title'),
- ]
- )
- ->from($db->quoteName('#__content', 'c'))
- ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
- ->whereIn($db->quoteName('c.id'), array_values($associations))
- ->where($db->quoteName('c.id') . ' != :articleId')
- ->bind(':articleId', $articleid, ParameterType::INTEGER);
+ // Get the associated menu items
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'c.*',
+ $db->quoteName('l.sef', 'lang_sef'),
+ $db->quoteName('l.lang_code'),
+ $db->quoteName('cat.title', 'category_title'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.title', 'language_title'),
+ ]
+ )
+ ->from($db->quoteName('#__content', 'c'))
+ ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
+ ->whereIn($db->quoteName('c.id'), array_values($associations))
+ ->where($db->quoteName('c.id') . ' != :articleId')
+ ->bind(':articleId', $articleid, ParameterType::INTEGER);
- $db->setQuery($query);
+ $db->setQuery($query);
- try
- {
- $items = $db->loadObjectList('id');
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500, $e);
- }
+ try {
+ $items = $db->loadObjectList('id');
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500, $e);
+ }
- if ($items)
- {
- $languages = LanguageHelper::getContentLanguages(array(0, 1));
- $content_languages = array_column($languages, 'lang_code');
+ if ($items) {
+ $languages = LanguageHelper::getContentLanguages(array(0, 1));
+ $content_languages = array_column($languages, 'lang_code');
- foreach ($items as &$item)
- {
- if (in_array($item->lang_code, $content_languages))
- {
- $text = $item->lang_code;
- $url = Route::_('index.php?option=com_content&task=article.edit&id=' . (int) $item->id);
- $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
- $classes = 'badge bg-secondary';
+ foreach ($items as &$item) {
+ if (in_array($item->lang_code, $content_languages)) {
+ $text = $item->lang_code;
+ $url = Route::_('index.php?option=com_content&task=article.edit&id=' . (int) $item->id);
+ $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
+ $classes = 'badge bg-secondary';
- $item->link = '' . $text . ' '
- . '' . $tooltip . '
';
- }
- else
- {
- // Display warning if Content Language is trashed or deleted
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
- }
- }
- }
+ $item->link = '' . $text . ' '
+ . '' . $tooltip . '
';
+ } else {
+ // Display warning if Content Language is trashed or deleted
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
+ }
+ }
+ }
- $html = LayoutHelper::render('joomla.content.associations', $items);
- }
+ $html = LayoutHelper::render('joomla.content.associations', $items);
+ }
- return $html;
- }
+ return $html;
+ }
}
diff --git a/administrator/components/com_content/src/Service/HTML/Icon.php b/administrator/components/com_content/src/Service/HTML/Icon.php
index b70cbaf279d4d..c069d8c054ccd 100644
--- a/administrator/components/com_content/src/Service/HTML/Icon.php
+++ b/administrator/components/com_content/src/Service/HTML/Icon.php
@@ -1,4 +1,5 @@
id;
-
- $text = '';
-
- if ($params->get('show_icons'))
- {
- $text .= ' ';
- }
-
- $text .= Text::_('COM_CONTENT_NEW_ARTICLE');
-
- // Add the button classes to the attribs array
- if (isset($attribs['class']))
- {
- $attribs['class'] .= ' btn btn-primary';
- }
- else
- {
- $attribs['class'] = 'btn btn-primary';
- }
-
- $button = HTMLHelper::_('link', Route::_($url), $text, $attribs);
-
- return $button;
- }
-
- /**
- * Display an edit icon for the article.
- *
- * This icon will not display in a popup window, nor if the article is trashed.
- * Edit access checks must be performed in the calling code.
- *
- * @param object $article The article information
- * @param Registry $params The item parameters
- * @param array $attribs Optional attributes for the link
- * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
- *
- * @return string The HTML for the article edit icon.
- *
- * @since 4.0.0
- */
- public function edit($article, $params, $attribs = array(), $legacy = false)
- {
- $user = Factory::getUser();
- $uri = Uri::getInstance();
-
- // Ignore if in a popup window.
- if ($params && $params->get('popup'))
- {
- return '';
- }
-
- // Ignore if the state is negative (trashed).
- if (!in_array($article->state, [Workflow::CONDITION_UNPUBLISHED, Workflow::CONDITION_PUBLISHED]))
- {
- return '';
- }
-
- // Show checked_out icon if the article is checked out by a different user
- if (property_exists($article, 'checked_out')
- && property_exists($article, 'checked_out_time')
- && !is_null($article->checked_out)
- && $article->checked_out != $user->get('id'))
- {
- $checkoutUser = Factory::getUser($article->checked_out);
- $date = HTMLHelper::_('date', $article->checked_out_time);
- $tooltip = Text::sprintf('COM_CONTENT_CHECKED_OUT_BY', $checkoutUser->name)
- . ' ' . $date;
-
- $text = LayoutHelper::render('joomla.content.icons.edit_lock', array('article' => $article, 'tooltip' => $tooltip, 'legacy' => $legacy));
-
- $attribs['aria-describedby'] = 'editarticle-' . (int) $article->id;
- $output = HTMLHelper::_('link', '#', $text, $attribs);
-
- return $output;
- }
-
- $contentUrl = RouteHelper::getArticleRoute($article->slug, $article->catid, $article->language);
- $url = $contentUrl . '&task=article.edit&a_id=' . $article->id . '&return=' . base64_encode($uri);
-
- if ($article->state == Workflow::CONDITION_UNPUBLISHED)
- {
- $tooltip = Text::_('COM_CONTENT_EDIT_UNPUBLISHED_ARTICLE');
- }
- else
- {
- $tooltip = Text::_('COM_CONTENT_EDIT_PUBLISHED_ARTICLE');
- }
-
- $text = LayoutHelper::render('joomla.content.icons.edit', array('article' => $article, 'tooltip' => $tooltip, 'legacy' => $legacy));
-
- $attribs['aria-describedby'] = 'editarticle-' . (int) $article->id;
- $output = HTMLHelper::_('link', Route::_($url), $text, $attribs);
-
- return $output;
- }
-
- /**
- * Method to generate a link to print an article
- *
- * @param Registry $params The item parameters
- * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
- *
- * @return string The HTML markup for the popup link
- *
- * @since 4.0.0
- */
- public function print_screen($params, $legacy = false)
- {
- $text = LayoutHelper::render('joomla.content.icons.print_screen', array('params' => $params, 'legacy' => $legacy));
-
- return '' . $text . ' ';
- }
+ /**
+ * Method to generate a link to the create item page for the given category
+ *
+ * @param object $category The category information
+ * @param Registry $params The item parameters
+ * @param array $attribs Optional attributes for the link
+ * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
+ *
+ * @return string The HTML markup for the create item link
+ *
+ * @since 4.0.0
+ */
+ public function create($category, $params, $attribs = array(), $legacy = false)
+ {
+ $uri = Uri::getInstance();
+
+ $url = 'index.php?option=com_content&task=article.add&return=' . base64_encode($uri) . '&a_id=0&catid=' . $category->id;
+
+ $text = '';
+
+ if ($params->get('show_icons')) {
+ $text .= ' ';
+ }
+
+ $text .= Text::_('COM_CONTENT_NEW_ARTICLE');
+
+ // Add the button classes to the attribs array
+ if (isset($attribs['class'])) {
+ $attribs['class'] .= ' btn btn-primary';
+ } else {
+ $attribs['class'] = 'btn btn-primary';
+ }
+
+ $button = HTMLHelper::_('link', Route::_($url), $text, $attribs);
+
+ return $button;
+ }
+
+ /**
+ * Display an edit icon for the article.
+ *
+ * This icon will not display in a popup window, nor if the article is trashed.
+ * Edit access checks must be performed in the calling code.
+ *
+ * @param object $article The article information
+ * @param Registry $params The item parameters
+ * @param array $attribs Optional attributes for the link
+ * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
+ *
+ * @return string The HTML for the article edit icon.
+ *
+ * @since 4.0.0
+ */
+ public function edit($article, $params, $attribs = array(), $legacy = false)
+ {
+ $user = Factory::getUser();
+ $uri = Uri::getInstance();
+
+ // Ignore if in a popup window.
+ if ($params && $params->get('popup')) {
+ return '';
+ }
+
+ // Ignore if the state is negative (trashed).
+ if (!in_array($article->state, [Workflow::CONDITION_UNPUBLISHED, Workflow::CONDITION_PUBLISHED])) {
+ return '';
+ }
+
+ // Show checked_out icon if the article is checked out by a different user
+ if (
+ property_exists($article, 'checked_out')
+ && property_exists($article, 'checked_out_time')
+ && !is_null($article->checked_out)
+ && $article->checked_out != $user->get('id')
+ ) {
+ $checkoutUser = Factory::getUser($article->checked_out);
+ $date = HTMLHelper::_('date', $article->checked_out_time);
+ $tooltip = Text::sprintf('COM_CONTENT_CHECKED_OUT_BY', $checkoutUser->name)
+ . ' ' . $date;
+
+ $text = LayoutHelper::render('joomla.content.icons.edit_lock', array('article' => $article, 'tooltip' => $tooltip, 'legacy' => $legacy));
+
+ $attribs['aria-describedby'] = 'editarticle-' . (int) $article->id;
+ $output = HTMLHelper::_('link', '#', $text, $attribs);
+
+ return $output;
+ }
+
+ $contentUrl = RouteHelper::getArticleRoute($article->slug, $article->catid, $article->language);
+ $url = $contentUrl . '&task=article.edit&a_id=' . $article->id . '&return=' . base64_encode($uri);
+
+ if ($article->state == Workflow::CONDITION_UNPUBLISHED) {
+ $tooltip = Text::_('COM_CONTENT_EDIT_UNPUBLISHED_ARTICLE');
+ } else {
+ $tooltip = Text::_('COM_CONTENT_EDIT_PUBLISHED_ARTICLE');
+ }
+
+ $text = LayoutHelper::render('joomla.content.icons.edit', array('article' => $article, 'tooltip' => $tooltip, 'legacy' => $legacy));
+
+ $attribs['aria-describedby'] = 'editarticle-' . (int) $article->id;
+ $output = HTMLHelper::_('link', Route::_($url), $text, $attribs);
+
+ return $output;
+ }
+
+ /**
+ * Method to generate a link to print an article
+ *
+ * @param Registry $params The item parameters
+ * @param boolean $legacy True to use legacy images, false to use icomoon based graphic
+ *
+ * @return string The HTML markup for the popup link
+ *
+ * @since 4.0.0
+ */
+ public function print_screen($params, $legacy = false)
+ {
+ $text = LayoutHelper::render('joomla.content.icons.print_screen', array('params' => $params, 'legacy' => $legacy));
+
+ return '' . $text . ' ';
+ }
}
diff --git a/administrator/components/com_content/src/Table/ArticleTable.php b/administrator/components/com_content/src/Table/ArticleTable.php
index 35d8f589f7254..ceec80ce8e7dc 100644
--- a/administrator/components/com_content/src/Table/ArticleTable.php
+++ b/administrator/components/com_content/src/Table/ArticleTable.php
@@ -1,4 +1,5 @@
getLayout() == 'pagebreak')
- {
- parent::display($tpl);
-
- return;
- }
-
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
- $this->canDo = ContentHelper::getActions('com_content', 'article', $this->item->id);
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // If we are forcing a language in modal (used for associations).
- if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
- {
- // Set the language field to the forcedLanguage and disable changing it.
- $this->form->setValue('language', null, $forcedLanguage);
- $this->form->setFieldAttribute('language', 'readonly', 'true');
-
- // Only allow to select categories with All language or with the forced language.
- $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
-
- // Only allow to select tags with All language or with the forced language.
- $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- *
- * @throws \Exception
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Built the actions for new and existing records.
- $canDo = $this->canDo;
-
- $toolbar = Toolbar::getInstance();
-
- ToolbarHelper::title(
- Text::_('COM_CONTENT_PAGE_' . ($checkedOut ? 'VIEW_ARTICLE' : ($isNew ? 'ADD_ARTICLE' : 'EDIT_ARTICLE'))),
- 'pencil-alt article-add'
- );
-
- // For new records, check the create permission.
- if ($isNew && (count($user->getAuthorisedCategories('com_content', 'core.create')) > 0))
- {
- $toolbar->apply('article.apply');
-
- $saveGroup = $toolbar->dropdownButton('save-group');
-
- $saveGroup->configure(
- function (Toolbar $childBar) use ($user)
- {
- $childBar->save('article.save');
-
- if ($user->authorise('core.create', 'com_menus.menu'))
- {
- $childBar->save('article.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
- }
-
- $childBar->save2new('article.save2new');
- }
- );
-
- $toolbar->cancel('article.cancel', 'JTOOLBAR_CANCEL');
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- if (!$checkedOut && $itemEditable)
- {
- $toolbar->apply('article.apply');
- }
-
- $saveGroup = $toolbar->dropdownButton('save-group');
-
- $saveGroup->configure(
- function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user)
- {
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- $childBar->save('article.save');
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $childBar->save2new('article.save2new');
- }
- }
-
- // If checked out, we can still save2menu
- if ($user->authorise('core.create', 'com_menus.menu'))
- {
- $childBar->save('article.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
- }
-
- // If checked out, we can still save
- if ($canDo->get('core.create'))
- {
- $childBar->save2copy('article.save2copy');
- }
- }
- );
-
- $toolbar->cancel('article.cancel', 'JTOOLBAR_CLOSE');
-
- if (!$isNew)
- {
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable)
- {
- $toolbar->versions('com_content.article', $this->item->id);
- }
-
- $url = RouteHelper::getArticleRoute($this->item->id . ':' . $this->item->alias, $this->item->catid, $this->item->language);
-
- $toolbar->preview(Route::link('site', $url, true), 'JGLOBAL_PREVIEW')
- ->bodyHeight(80)
- ->modalWidth(90);
-
- if (PluginHelper::isEnabled('system', 'jooa11y'))
- {
- $toolbar->jooa11y(Route::link('site', $url . '&jooa11y=1', true), 'JGLOBAL_JOOA11Y')
- ->bodyHeight(80)
- ->modalWidth(90);
- }
-
- if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations'))
- {
- $toolbar->standardButton('contract')
- ->text('JTOOLBAR_ASSOCIATIONS')
- ->task('article.editAssociations');
- }
- }
- }
-
- $toolbar->divider();
-
- ToolbarHelper::inlinehelp();
-
- $toolbar->help('Articles:_Edit');
- }
+ /**
+ * The \JForm object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var object
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $canDo;
+
+ /**
+ * Pagebreak TOC alias
+ *
+ * @var string
+ */
+ protected $eName;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ if ($this->getLayout() == 'pagebreak') {
+ parent::display($tpl);
+
+ return;
+ }
+
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $this->canDo = ContentHelper::getActions('com_content', 'article', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
+
+ // Only allow to select tags with All language or with the forced language.
+ $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Built the actions for new and existing records.
+ $canDo = $this->canDo;
+
+ $toolbar = Toolbar::getInstance();
+
+ ToolbarHelper::title(
+ Text::_('COM_CONTENT_PAGE_' . ($checkedOut ? 'VIEW_ARTICLE' : ($isNew ? 'ADD_ARTICLE' : 'EDIT_ARTICLE'))),
+ 'pencil-alt article-add'
+ );
+
+ // For new records, check the create permission.
+ if ($isNew && (count($user->getAuthorisedCategories('com_content', 'core.create')) > 0)) {
+ $toolbar->apply('article.apply');
+
+ $saveGroup = $toolbar->dropdownButton('save-group');
+
+ $saveGroup->configure(
+ function (Toolbar $childBar) use ($user) {
+ $childBar->save('article.save');
+
+ if ($user->authorise('core.create', 'com_menus.menu')) {
+ $childBar->save('article.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
+ }
+
+ $childBar->save2new('article.save2new');
+ }
+ );
+
+ $toolbar->cancel('article.cancel', 'JTOOLBAR_CANCEL');
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ if (!$checkedOut && $itemEditable) {
+ $toolbar->apply('article.apply');
+ }
+
+ $saveGroup = $toolbar->dropdownButton('save-group');
+
+ $saveGroup->configure(
+ function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user) {
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ $childBar->save('article.save');
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $childBar->save2new('article.save2new');
+ }
+ }
+
+ // If checked out, we can still save2menu
+ if ($user->authorise('core.create', 'com_menus.menu')) {
+ $childBar->save('article.save2menu', 'JTOOLBAR_SAVE_TO_MENU');
+ }
+
+ // If checked out, we can still save
+ if ($canDo->get('core.create')) {
+ $childBar->save2copy('article.save2copy');
+ }
+ }
+ );
+
+ $toolbar->cancel('article.cancel', 'JTOOLBAR_CLOSE');
+
+ if (!$isNew) {
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable) {
+ $toolbar->versions('com_content.article', $this->item->id);
+ }
+
+ $url = RouteHelper::getArticleRoute($this->item->id . ':' . $this->item->alias, $this->item->catid, $this->item->language);
+
+ $toolbar->preview(Route::link('site', $url, true), 'JGLOBAL_PREVIEW')
+ ->bodyHeight(80)
+ ->modalWidth(90);
+
+ if (PluginHelper::isEnabled('system', 'jooa11y')) {
+ $toolbar->jooa11y(Route::link('site', $url . '&jooa11y=1', true), 'JGLOBAL_JOOA11Y')
+ ->bodyHeight(80)
+ ->modalWidth(90);
+ }
+
+ if (Associations::isEnabled() && ComponentHelper::isEnabled('com_associations')) {
+ $toolbar->standardButton('contract')
+ ->text('JTOOLBAR_ASSOCIATIONS')
+ ->task('article.editAssociations');
+ }
+ }
+ }
+
+ $toolbar->divider();
+
+ ToolbarHelper::inlinehelp();
+
+ $toolbar->help('Articles:_Edit');
+ }
}
diff --git a/administrator/components/com_content/src/View/Articles/HtmlView.php b/administrator/components/com_content/src/View/Articles/HtmlView.php
index 8461c2980cb3d..aea5ba8d2fe43 100644
--- a/administrator/components/com_content/src/View/Articles/HtmlView.php
+++ b/administrator/components/com_content/src/View/Articles/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->vote = PluginHelper::isEnabled('content', 'vote');
- $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1);
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- if (ComponentHelper::getParams('com_content')->get('workflow_enabled'))
- {
- PluginHelper::importPlugin('workflow');
-
- $this->transitions = $this->get('Transitions');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')) || $this->transitions === false)
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- else
- {
- // In article associations modal we need to remove language filter if forcing a language.
- // We also need to change the category filter to show show categories with All or the forced language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
- $languageXml = new \SimpleXMLElement(' ');
- $this->filterForm->setField($languageXml, 'filter', true);
-
- // Also, unset the active language filter so the search tools is not open by default with this filter.
- unset($this->activeFilters['language']);
-
- // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
- $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_content', 'category', $this->state->get('filter.category_id'));
- $user = $this->getCurrentUser();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_CONTENT_ARTICLES_TITLE'), 'copy article');
-
- if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_content', 'core.create')) > 0)
- {
- $toolbar->addNew('article.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || \count($this->transitions)))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if (\count($this->transitions))
- {
- $childBar->separatorButton('transition-headline')
- ->text('COM_CONTENT_RUN_TRANSITIONS')
- ->buttonClass('text-center py-2 h3');
-
- $cmd = "Joomla.submitbutton('articles.runTransition');";
- $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}";
- $alert = 'Joomla.renderMessages(' . $messages . ')';
- $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }';
-
- foreach ($this->transitions as $transition)
- {
- $childBar->standardButton('transition')
- ->text($transition['text'])
- ->buttonClass('transition-' . (int) $transition['value'])
- ->icon('icon-project-diagram')
- ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd);
- }
-
- $childBar->separatorButton('transition-separator');
- }
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('articles.publish')->listCheck(true);
-
- $childBar->unpublish('articles.unpublish')->listCheck(true);
-
- $childBar->standardButton('featured')
- ->text('JFEATURE')
- ->task('articles.featured')
- ->listCheck(true);
-
- $childBar->standardButton('unfeatured')
- ->text('JUNFEATURE')
- ->task('articles.unfeatured')
- ->listCheck(true);
-
- $childBar->archive('articles.archive')->listCheck(true);
-
- $childBar->checkin('articles.checkin')->listCheck(true);
-
- if ($this->state->get('filter.published') != ContentComponent::CONDITION_TRASHED)
- {
- $childBar->trash('articles.trash')->listCheck(true);
- }
- }
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_content')
- && $user->authorise('core.edit', 'com_content')
- && $user->authorise('core.execute.transition', 'com_content'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete'))
- {
- $toolbar->delete('articles.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content'))
- {
- $toolbar->preferences('com_content');
- }
-
- $toolbar->help('Articles');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * All transition, which can be executed of one if the items
+ *
+ * @var array
+ */
+ protected $transitions = [];
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->vote = PluginHelper::isEnabled('content', 'vote');
+ $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1);
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ if (ComponentHelper::getParams('com_content')->get('workflow_enabled')) {
+ PluginHelper::importPlugin('workflow');
+
+ $this->transitions = $this->get('Transitions');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors')) || $this->transitions === false) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // In article associations modal we need to remove language filter if forcing a language.
+ // We also need to change the category filter to show show categories with All or the forced language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement(' ');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+
+ // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
+ $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_content', 'category', $this->state->get('filter.category_id'));
+ $user = $this->getCurrentUser();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_CONTENT_ARTICLES_TITLE'), 'copy article');
+
+ if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_content', 'core.create')) > 0) {
+ $toolbar->addNew('article.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || \count($this->transitions))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if (\count($this->transitions)) {
+ $childBar->separatorButton('transition-headline')
+ ->text('COM_CONTENT_RUN_TRANSITIONS')
+ ->buttonClass('text-center py-2 h3');
+
+ $cmd = "Joomla.submitbutton('articles.runTransition');";
+ $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}";
+ $alert = 'Joomla.renderMessages(' . $messages . ')';
+ $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }';
+
+ foreach ($this->transitions as $transition) {
+ $childBar->standardButton('transition')
+ ->text($transition['text'])
+ ->buttonClass('transition-' . (int) $transition['value'])
+ ->icon('icon-project-diagram')
+ ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd);
+ }
+
+ $childBar->separatorButton('transition-separator');
+ }
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('articles.publish')->listCheck(true);
+
+ $childBar->unpublish('articles.unpublish')->listCheck(true);
+
+ $childBar->standardButton('featured')
+ ->text('JFEATURE')
+ ->task('articles.featured')
+ ->listCheck(true);
+
+ $childBar->standardButton('unfeatured')
+ ->text('JUNFEATURE')
+ ->task('articles.unfeatured')
+ ->listCheck(true);
+
+ $childBar->archive('articles.archive')->listCheck(true);
+
+ $childBar->checkin('articles.checkin')->listCheck(true);
+
+ if ($this->state->get('filter.published') != ContentComponent::CONDITION_TRASHED) {
+ $childBar->trash('articles.trash')->listCheck(true);
+ }
+ }
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_content')
+ && $user->authorise('core.edit', 'com_content')
+ && $user->authorise('core.execute.transition', 'com_content')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) {
+ $toolbar->delete('articles.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content')) {
+ $toolbar->preferences('com_content');
+ }
+
+ $toolbar->help('Articles');
+ }
}
diff --git a/administrator/components/com_content/src/View/Featured/HtmlView.php b/administrator/components/com_content/src/View/Featured/HtmlView.php
index ca034b3767112..e4aebfbbba9b6 100644
--- a/administrator/components/com_content/src/View/Featured/HtmlView.php
+++ b/administrator/components/com_content/src/View/Featured/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->vote = PluginHelper::isEnabled('content', 'vote');
- $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1);
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- if (ComponentHelper::getParams('com_content')->get('workflow_enabled'))
- {
- PluginHelper::importPlugin('workflow');
-
- $this->transitions = $this->get('Transitions');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_content', 'category', $this->state->get('filter.category_id'));
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_CONTENT_FEATURED_TITLE'), 'star featured');
-
- if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_content', 'core.create')) > 0)
- {
- $toolbar->addNew('article.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || \count($this->transitions)))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if (\count($this->transitions))
- {
- $childBar->separatorButton('transition-headline')
- ->text('COM_CONTENT_RUN_TRANSITIONS')
- ->buttonClass('text-center py-2 h3');
-
- $cmd = "Joomla.submitbutton('articles.runTransition');";
- $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}";
- $alert = 'Joomla.renderMessages(' . $messages . ')';
- $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }';
-
- foreach ($this->transitions as $transition)
- {
- $childBar->standardButton('transition')
- ->text($transition['text'])
- ->buttonClass('transition-' . (int) $transition['value'])
- ->icon('icon-project-diagram')
- ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd);
- }
-
- $childBar->separatorButton('transition-separator');
- }
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('articles.publish')->listCheck(true);
-
- $childBar->unpublish('articles.unpublish')->listCheck(true);
-
- $childBar->standardButton('unfeatured')
- ->text('JUNFEATURE')
- ->task('articles.unfeatured')
- ->listCheck(true);
-
- $childBar->archive('articles.archive')->listCheck(true);
-
- $childBar->checkin('articles.checkin')->listCheck(true);
-
- if (!$this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED)
- {
- $childBar->trash('articles.trash')->listCheck(true);
- }
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete'))
- {
- $toolbar->delete('articles.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content'))
- {
- $toolbar->preferences('com_content');
- }
-
- ToolbarHelper::help('Articles:_Featured');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * All transition, which can be executed of one if the items
+ *
+ * @var array
+ */
+ protected $transitions = [];
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->vote = PluginHelper::isEnabled('content', 'vote');
+ $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1);
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ if (ComponentHelper::getParams('com_content')->get('workflow_enabled')) {
+ PluginHelper::importPlugin('workflow');
+
+ $this->transitions = $this->get('Transitions');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_content', 'category', $this->state->get('filter.category_id'));
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_CONTENT_FEATURED_TITLE'), 'star featured');
+
+ if ($canDo->get('core.create') || \count($user->getAuthorisedCategories('com_content', 'core.create')) > 0) {
+ $toolbar->addNew('article.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || \count($this->transitions))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if (\count($this->transitions)) {
+ $childBar->separatorButton('transition-headline')
+ ->text('COM_CONTENT_RUN_TRANSITIONS')
+ ->buttonClass('text-center py-2 h3');
+
+ $cmd = "Joomla.submitbutton('articles.runTransition');";
+ $messages = "{error: [Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST')]}";
+ $alert = 'Joomla.renderMessages(' . $messages . ')';
+ $cmd = 'if (document.adminForm.boxchecked.value == 0) { ' . $alert . ' } else { ' . $cmd . ' }';
+
+ foreach ($this->transitions as $transition) {
+ $childBar->standardButton('transition')
+ ->text($transition['text'])
+ ->buttonClass('transition-' . (int) $transition['value'])
+ ->icon('icon-project-diagram')
+ ->onclick('document.adminForm.transition_id.value=' . (int) $transition['value'] . ';' . $cmd);
+ }
+
+ $childBar->separatorButton('transition-separator');
+ }
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('articles.publish')->listCheck(true);
+
+ $childBar->unpublish('articles.unpublish')->listCheck(true);
+
+ $childBar->standardButton('unfeatured')
+ ->text('JUNFEATURE')
+ ->task('articles.unfeatured')
+ ->listCheck(true);
+
+ $childBar->archive('articles.archive')->listCheck(true);
+
+ $childBar->checkin('articles.checkin')->listCheck(true);
+
+ if (!$this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED) {
+ $childBar->trash('articles.trash')->listCheck(true);
+ }
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == ContentComponent::CONDITION_TRASHED && $canDo->get('core.delete')) {
+ $toolbar->delete('articles.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin', 'com_content') || $user->authorise('core.options', 'com_content')) {
+ $toolbar->preferences('com_content');
+ }
+
+ ToolbarHelper::help('Articles:_Featured');
+ }
}
diff --git a/administrator/components/com_content/tmpl/article/edit.php b/administrator/components/com_content/tmpl/article/edit.php
index 29ce01e10f511..969455f185a16 100644
--- a/administrator/components/com_content/tmpl/article/edit.php
+++ b/administrator/components/com_content/tmpl/article/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('com_contenthistory');
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_contenthistory.admin-history-versions');
+ ->useScript('form.validate')
+ ->useScript('com_contenthistory.admin-history-versions');
$this->configFieldsets = array('editorConfig');
$this->hiddenFieldsets = array('basic-limited');
@@ -42,15 +43,13 @@
$assoc = Associations::isEnabled();
$showArticleOptions = $params->get('show_article_options', 1);
-if (!$assoc || !$showArticleOptions)
-{
- $this->ignore_fieldsets[] = 'frontendassociations';
+if (!$assoc || !$showArticleOptions) {
+ $this->ignore_fieldsets[] = 'frontendassociations';
}
-if (!$showArticleOptions)
-{
- // Ignore fieldsets inside Options tab
- $this->ignore_fieldsets = array_merge($this->ignore_fieldsets, ['attribs', 'basic', 'category', 'author', 'date', 'other']);
+if (!$showArticleOptions) {
+ // Ignore fieldsets inside Options tab
+ $this->ignore_fieldsets = array_merge($this->ignore_fieldsets, ['attribs', 'basic', 'category', 'author', 'date', 'other']);
}
// In case of modal
@@ -59,129 +58,129 @@
$tmpl = $isModal || $input->get('tmpl', '', 'cmd') === 'component' ? '&tmpl=component' : '';
?>
diff --git a/administrator/components/com_content/tmpl/article/modal.php b/administrator/components/com_content/tmpl/article/modal.php
index 9147d5b5ed403..f6d9da1f17513 100644
--- a/administrator/components/com_content/tmpl/article/modal.php
+++ b/administrator/components/com_content/tmpl/article/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_content/tmpl/article/pagebreak.php b/administrator/components/com_content/tmpl/article/pagebreak.php
index 53a8211f88bf3..4612825d783f3 100644
--- a/administrator/components/com_content/tmpl/article/pagebreak.php
+++ b/administrator/components/com_content/tmpl/article/pagebreak.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_content/tmpl/articles/default.php b/administrator/components/com_content/tmpl/articles/default.php
index cfbecfcc46a14..5645f3c3323ad 100644
--- a/administrator/components/com_content/tmpl/articles/default.php
+++ b/administrator/components/com_content/tmpl/articles/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$app = Factory::getApplication();
$user = Factory::getUser();
@@ -36,27 +37,19 @@
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = $listOrder == 'a.ordering';
-if (strpos($listOrder, 'publish_up') !== false)
-{
- $orderingColumn = 'publish_up';
-}
-elseif (strpos($listOrder, 'publish_down') !== false)
-{
- $orderingColumn = 'publish_down';
-}
-elseif (strpos($listOrder, 'modified') !== false)
-{
- $orderingColumn = 'modified';
-}
-else
-{
- $orderingColumn = 'created';
+if (strpos($listOrder, 'publish_up') !== false) {
+ $orderingColumn = 'publish_up';
+} elseif (strpos($listOrder, 'publish_down') !== false) {
+ $orderingColumn = 'publish_down';
+} elseif (strpos($listOrder, 'modified') !== false) {
+ $orderingColumn = 'modified';
+} else {
+ $orderingColumn = 'created';
}
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_content&task=articles.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_content&task=articles.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$workflow_enabled = ComponentHelper::getParams('com_content')->get('workflow_enabled');
@@ -64,9 +57,8 @@
$workflow_featured = false;
if ($workflow_enabled) :
-
// @todo move the script to a file
-$js = <<getRegistry()->addExtensionRegistryFile('com_workflow');
-$wa->useScript('com_workflow.admin-items-workflow-buttons')
- ->addInlineScript($js, [], ['type' => 'module']);
-
-$workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
-$workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
+ $wa->getRegistry()->addExtensionRegistryFile('com_workflow');
+ $wa->useScript('com_workflow.admin-items-workflow-buttons')
+ ->addInlineScript($js, [], ['type' => 'module']);
+ $workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
+ $workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
endif;
$assoc = Associations::isEnabled();
?>
diff --git a/administrator/components/com_content/tmpl/articles/default_batch_body.php b/administrator/components/com_content/tmpl/articles/default_batch_body.php
index eaa3e08bbea6a..0f838accf462c 100644
--- a/administrator/components/com_content/tmpl/articles/default_batch_body.php
+++ b/administrator/components/com_content/tmpl/articles/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper;
@@ -21,39 +23,39 @@
?>
-
-
- = 0) : ?>
-
-
-
- authorise('core.admin', 'com_content') && $params->get('workflow_enabled')) : ?>
-
-
-
+
+
+ = 0) : ?>
+
+
+
+ authorise('core.admin', 'com_content') && $params->get('workflow_enabled')) : ?>
+
+
+
diff --git a/administrator/components/com_content/tmpl/articles/default_batch_footer.php b/administrator/components/com_content/tmpl/articles/default_batch_footer.php
index 3fe481e8ec80c..fd221e578f496 100644
--- a/administrator/components/com_content/tmpl/articles/default_batch_footer.php
+++ b/administrator/components/com_content/tmpl/articles/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
@@ -16,8 +18,8 @@
?>
-
+
-
+
diff --git a/administrator/components/com_content/tmpl/articles/emptystate.php b/administrator/components/com_content/tmpl/articles/emptystate.php
index 222805bc2f92f..331fd161984ba 100644
--- a/administrator/components/com_content/tmpl/articles/emptystate.php
+++ b/administrator/components/com_content/tmpl/articles/emptystate.php
@@ -1,4 +1,5 @@
'COM_CONTENT',
- 'formURL' => 'index.php?option=com_content&view=articles',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Adding_a_new_article',
- 'icon' => 'icon-copy article',
+ 'textPrefix' => 'COM_CONTENT',
+ 'formURL' => 'index.php?option=com_content&view=articles',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Adding_a_new_article',
+ 'icon' => 'icon-copy article',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_content') || count($user->getAuthorisedCategories('com_content', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_content&task=article.add';
+if ($user->authorise('core.create', 'com_content') || count($user->getAuthorisedCategories('com_content', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_content&task=article.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_content/tmpl/articles/modal.php b/administrator/components/com_content/tmpl/articles/modal.php
index 2ed1cdc81c11d..d99d647c4641c 100644
--- a/administrator/components/com_content/tmpl/articles/modal.php
+++ b/administrator/components/com_content/tmpl/articles/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if ($app->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('core')
- ->useScript('multiselect')
- ->useScript('com_content.admin-articles-modal');
+ ->useScript('multiselect')
+ ->useScript('com_content.admin-articles-modal');
$function = $app->input->getCmd('function', 'jSelectArticle');
$editor = $app->input->getCmd('editor', '');
@@ -38,140 +38,132 @@
$onclick = $this->escape($function);
$multilang = Multilanguage::isEnabled();
-if (!empty($editor))
-{
- // This view is used also in com_menus. Load the xtd script only if the editor is set!
- $this->document->addScriptOptions('xtd-articles', array('editor' => $editor));
- $onclick = "jSelectArticle";
+if (!empty($editor)) {
+ // This view is used also in com_menus. Load the xtd script only if the editor is set!
+ $this->document->addScriptOptions('xtd-articles', array('editor' => $editor));
+ $onclick = "jSelectArticle";
}
?>
diff --git a/administrator/components/com_content/tmpl/featured/default.php b/administrator/components/com_content/tmpl/featured/default.php
index e719e334c5d2c..d0f9714e2494b 100644
--- a/administrator/components/com_content/tmpl/featured/default.php
+++ b/administrator/components/com_content/tmpl/featured/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$app = Factory::getApplication();
$user = Factory::getUser();
@@ -36,28 +37,20 @@
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = $listOrder == 'fp.ordering';
-if (strpos($listOrder, 'publish_up') !== false)
-{
- $orderingColumn = 'publish_up';
-}
-elseif (strpos($listOrder, 'publish_down') !== false)
-{
- $orderingColumn = 'publish_down';
-}
-elseif (strpos($listOrder, 'modified') !== false)
-{
- $orderingColumn = 'modified';
-}
-else
-{
- $orderingColumn = 'created';
+if (strpos($listOrder, 'publish_up') !== false) {
+ $orderingColumn = 'publish_up';
+} elseif (strpos($listOrder, 'publish_down') !== false) {
+ $orderingColumn = 'publish_down';
+} elseif (strpos($listOrder, 'modified') !== false) {
+ $orderingColumn = 'modified';
+} else {
+ $orderingColumn = 'created';
}
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_content&task=featured.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_content&task=featured.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$workflow_enabled = ComponentHelper::getParams('com_content')->get('workflow_enabled');
@@ -65,9 +58,8 @@
$workflow_featured = false;
if ($workflow_enabled) :
-
// @todo move the script to a file
-$js = <<getRegistry()->addExtensionRegistryFile('com_workflow');
-$wa->useScript('com_workflow.admin-items-workflow-buttons')
- ->addInlineScript($js, [], ['type' => 'module']);
-
-$workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
-$workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
+ $wa->getRegistry()->addExtensionRegistryFile('com_workflow');
+ $wa->useScript('com_workflow.admin-items-workflow-buttons')
+ ->addInlineScript($js, [], ['type' => 'module']);
+ $workflow_state = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.state', 'com_content.article');
+ $workflow_featured = Factory::getApplication()->bootComponent('com_content')->isFunctionalityUsed('core.featured', 'com_content.article');
endif;
$assoc = Associations::isEnabled();
@@ -95,332 +86,328 @@
?>
diff --git a/administrator/components/com_content/tmpl/featured/default_stage_body.php b/administrator/components/com_content/tmpl/featured/default_stage_body.php
index 3dadb99058ecc..d5f510e40a892 100644
--- a/administrator/components/com_content/tmpl/featured/default_stage_body.php
+++ b/administrator/components/com_content/tmpl/featured/default_stage_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
@@ -13,11 +15,11 @@
?>
diff --git a/administrator/components/com_content/tmpl/featured/default_stage_footer.php b/administrator/components/com_content/tmpl/featured/default_stage_footer.php
index 886538176cfc0..32ec9abcfb51f 100644
--- a/administrator/components/com_content/tmpl/featured/default_stage_footer.php
+++ b/administrator/components/com_content/tmpl/featured/default_stage_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
@@ -16,8 +18,8 @@
?>
-
+
-
+
diff --git a/administrator/components/com_content/tmpl/featured/emptystate.php b/administrator/components/com_content/tmpl/featured/emptystate.php
index 99bc733f48366..83748e92eacc7 100644
--- a/administrator/components/com_content/tmpl/featured/emptystate.php
+++ b/administrator/components/com_content/tmpl/featured/emptystate.php
@@ -1,4 +1,5 @@
'COM_CONTENT',
- 'formURL' => 'index.php?option=com_content&view=featured',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Adding_a_new_article',
+ 'textPrefix' => 'COM_CONTENT',
+ 'formURL' => 'index.php?option=com_content&view=featured',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Adding_a_new_article',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_content') || count($user->getAuthorisedCategories('com_content', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_content&task=article.add';
+if ($user->authorise('core.create', 'com_content') || count($user->getAuthorisedCategories('com_content', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_content&task=article.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_contenthistory/helpers/contenthistory.php b/administrator/components/com_contenthistory/helpers/contenthistory.php
index 1c4b548e8b989..0858654998ac9 100644
--- a/administrator/components/com_contenthistory/helpers/contenthistory.php
+++ b/administrator/components/com_contenthistory/helpers/contenthistory.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Categories helper.
diff --git a/administrator/components/com_contenthistory/services/provider.php b/administrator/components/com_contenthistory/services/provider.php
index 5840a2fcce394..b3c3ca13fa596 100644
--- a/administrator/components/com_contenthistory/services/provider.php
+++ b/administrator/components/com_contenthistory/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Contenthistory'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Contenthistory'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Contenthistory'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Contenthistory'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_contenthistory/src/Controller/DisplayController.php b/administrator/components/com_contenthistory/src/Controller/DisplayController.php
index a7fbefd572536..1ace8ff5c6755 100644
--- a/administrator/components/com_contenthistory/src/Controller/DisplayController.php
+++ b/administrator/components/com_contenthistory/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model
+ * @param string $prefix The prefix for the model
+ * @param array $config An additional array of parameters
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model
+ *
+ * @since 3.2
+ */
+ public function getModel($name = 'History', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
- /**
- * Toggles the keep forever value for one or more history rows. If it was Yes, changes to No. If No, changes to Yes.
- *
- * @return void
- *
- * @since 3.2
- */
- public function keep()
- {
- $this->checkToken();
+ /**
+ * Toggles the keep forever value for one or more history rows. If it was Yes, changes to No. If No, changes to Yes.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function keep()
+ {
+ $this->checkToken();
- // Get items to toggle keep forever from the request.
- $cid = (array) $this->input->get('cid', array(), 'int');
+ // Get items to toggle keep forever from the request.
+ $cid = (array) $this->input->get('cid', array(), 'int');
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
- if (empty($cid))
- {
- $this->app->enqueueMessage(Text::_('COM_CONTENTHISTORY_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
+ if (empty($cid)) {
+ $this->app->enqueueMessage(Text::_('COM_CONTENTHISTORY_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
- // Toggle keep forever status of the selected items.
- if ($model->keep($cid))
- {
- $this->setMessage(Text::plural('COM_CONTENTHISTORY_N_ITEMS_KEEP_TOGGLE', count($cid)));
- }
- else
- {
- $this->setMessage($model->getError(), 'error');
- }
- }
+ // Toggle keep forever status of the selected items.
+ if ($model->keep($cid)) {
+ $this->setMessage(Text::plural('COM_CONTENTHISTORY_N_ITEMS_KEEP_TOGGLE', count($cid)));
+ } else {
+ $this->setMessage($model->getError(), 'error');
+ }
+ }
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&item_id='
- . $this->input->getCmd('item_id') . '&' . Session::getFormToken() . '=1', false
- )
- );
- }
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&item_id='
+ . $this->input->getCmd('item_id') . '&' . Session::getFormToken() . '=1',
+ false
+ )
+ );
+ }
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- return '&layout=modal&tmpl=component&item_id=' . $this->input->get('item_id') . '&' . Session::getFormToken() . '=1';
- }
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ return '&layout=modal&tmpl=component&item_id=' . $this->input->get('item_id') . '&' . Session::getFormToken() . '=1';
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Controller/PreviewController.php b/administrator/components/com_contenthistory/src/Controller/PreviewController.php
index 9c9b14b3bdf07..7eb701c32d455 100644
--- a/administrator/components/com_contenthistory/src/Controller/PreviewController.php
+++ b/administrator/components/com_contenthistory/src/Controller/PreviewController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model
+ * @param string $prefix The prefix for the model
+ * @param array $config An additional array of parameters
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model
+ *
+ * @since 3.2
+ */
+ public function getModel($name = 'Preview', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Dispatcher/Dispatcher.php b/administrator/components/com_contenthistory/src/Dispatcher/Dispatcher.php
index 61d79a98c955b..0ad304d6c32ca 100644
--- a/administrator/components/com_contenthistory/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_contenthistory/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
app->getIdentity()->guest)
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ /**
+ * Method to check component access permission
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ *
+ * @throws \Exception|NotAllowed
+ */
+ protected function checkAccess()
+ {
+ // Check the user has permission to access this component if in the backend
+ if ($this->app->getIdentity()->guest) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php b/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
index 18ab0f48624a9..63052b94cdb3f 100644
--- a/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
+++ b/administrator/components/com_contenthistory/src/Helper/ContenthistoryHelper.php
@@ -1,4 +1,5 @@
$value)
- {
- $result[$name] = $value;
-
- if (is_object($value))
- {
- foreach ($value as $subName => $subValue)
- {
- $result[$subName] = $subValue;
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Method to decode JSON-encoded fields in a standard object. Used to unpack JSON strings in the content history data column.
- *
- * @param string $jsonString JSON String to convert to an object.
- *
- * @return \stdClass Object with any JSON-encoded fields unpacked.
- *
- * @since 3.2
- */
- public static function decodeFields($jsonString)
- {
- $object = json_decode($jsonString);
-
- if (is_object($object))
- {
- foreach ($object as $name => $value)
- {
- if ($subObject = json_decode($value))
- {
- $object->$name = $subObject;
- }
- }
- }
-
- return $object;
- }
-
- /**
- * Method to get field labels for the fields in the JSON-encoded object.
- * First we see if we can find translatable labels for the fields in the object.
- * We translate any we can find and return an array in the format object->name => label.
- *
- * @param \stdClass $object Standard class object in the format name->value.
- * @param ContentType $typesTable Table object with content history options.
- *
- * @return \stdClass Contains two associative arrays.
- * $formValues->labels in the format name => label (for example, 'id' => 'Article ID').
- * $formValues->values in the format name => value (for example, 'state' => 'Published'.
- * This translates the text from the selected option in the form.
- *
- * @since 3.2
- */
- public static function getFormValues($object, ContentType $typesTable)
- {
- $labels = array();
- $values = array();
- $expandedObjectArray = static::createObjectArray($object);
- static::loadLanguageFiles($typesTable->type_alias);
-
- if ($formFile = static::getFormFile($typesTable))
- {
- if ($xml = simplexml_load_file($formFile))
- {
- // Now we need to get all of the labels from the form
- $fieldArray = $xml->xpath('//field');
- $fieldArray = array_merge($fieldArray, $xml->xpath('//fields'));
-
- foreach ($fieldArray as $field)
- {
- if ($label = (string) $field->attributes()->label)
- {
- $labels[(string) $field->attributes()->name] = Text::_($label);
- }
- }
-
- // Get values for any list type fields
- $listFieldArray = $xml->xpath('//field[@type="list" or @type="radio"]');
-
- foreach ($listFieldArray as $field)
- {
- $name = (string) $field->attributes()->name;
-
- if (isset($expandedObjectArray[$name]))
- {
- $optionFieldArray = $field->xpath('option[@value="' . $expandedObjectArray[$name] . '"]');
-
- $valueText = null;
-
- if (is_array($optionFieldArray) && count($optionFieldArray))
- {
- $valueText = trim((string) $optionFieldArray[0]);
- }
-
- $values[(string) $field->attributes()->name] = Text::_($valueText);
- }
- }
- }
- }
-
- $result = new \stdClass;
- $result->labels = $labels;
- $result->values = $values;
-
- return $result;
- }
-
- /**
- * Method to get the XML form file for this component. Used to get translated field names for history preview.
- *
- * @param ContentType $typesTable Table object with content history options.
- *
- * @return mixed \JModel object if successful, false if no model found.
- *
- * @since 3.2
- */
- public static function getFormFile(ContentType $typesTable)
- {
- // First, see if we have a file name in the $typesTable
- $options = json_decode($typesTable->content_history_options);
-
- if (is_object($options) && isset($options->formFile) && File::exists(JPATH_ROOT . '/' . $options->formFile))
- {
- $result = JPATH_ROOT . '/' . $options->formFile;
- }
- else
- {
- $aliasArray = explode('.', $typesTable->type_alias);
- $component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
- $path = Folder::makeSafe(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/');
- array_shift($aliasArray);
- $file = File::makeSafe(implode('.', $aliasArray) . '.xml');
- $result = File::exists($path . $file) ? $path . $file : false;
- }
-
- return $result;
- }
-
- /**
- * Method to query the database using values from lookup objects.
- *
- * @param \stdClass $lookup The std object with the values needed to do the query.
- * @param mixed $value The value used to find the matching title or name. Typically the id.
- *
- * @return mixed Value from database (for example, name or title) on success, false on failure.
- *
- * @since 3.2
- */
- public static function getLookupValue($lookup, $value)
- {
- $result = false;
-
- if (isset($lookup->sourceColumn) && isset($lookup->targetTable) && isset($lookup->targetColumn) && isset($lookup->displayColumn))
- {
- $db = Factory::getDbo();
- $value = (int) $value;
- $query = $db->getQuery(true);
- $query->select($db->quoteName($lookup->displayColumn))
- ->from($db->quoteName($lookup->targetTable))
- ->where($db->quoteName($lookup->targetColumn) . ' = :value')
- ->bind(':value', $value, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $result = $db->loadResult();
- }
- catch (\Exception $e)
- {
- // Ignore any errors and just return false
- return false;
- }
- }
-
- return $result;
- }
-
- /**
- * Method to remove fields from the object based on values entered in the #__content_types table.
- *
- * @param \stdClass $object Object to be passed to view layout file.
- * @param ContentType $typeTable Table object with content history options.
- *
- * @return \stdClass object with hidden fields removed.
- *
- * @since 3.2
- */
- public static function hideFields($object, ContentType $typeTable)
- {
- if ($options = json_decode($typeTable->content_history_options))
- {
- if (isset($options->hideFields) && is_array($options->hideFields))
- {
- foreach ($options->hideFields as $field)
- {
- unset($object->$field);
- }
- }
- }
-
- return $object;
- }
-
- /**
- * Method to load the language files for the component whose history is being viewed.
- *
- * @param string $typeAlias The type alias, for example 'com_content.article'.
- *
- * @return void
- *
- * @since 3.2
- */
- public static function loadLanguageFiles($typeAlias)
- {
- $aliasArray = explode('.', $typeAlias);
-
- if (is_array($aliasArray) && count($aliasArray) == 2)
- {
- $component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
- $lang = Factory::getLanguage();
-
- /**
- * Loading language file from the administrator/language directory then
- * loading language file from the administrator/components/extension/language directory
- */
- $lang->load($component, JPATH_ADMINISTRATOR)
- || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
-
- // Force loading of backend global language file
- $lang->load('joomla', Path::clean(JPATH_ADMINISTRATOR));
- }
- }
-
- /**
- * Method to create object to pass to the layout. Format is as follows:
- * field is std object with name, value.
- *
- * Value can be a std object with name, value pairs.
- *
- * @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
- * @param \stdClass $formValues Standard class of label and value in an associative array.
- *
- * @return \stdClass Object with translated labels where available
- *
- * @since 3.2
- */
- public static function mergeLabels($object, $formValues)
- {
- $result = new \stdClass;
-
- if ($object === null)
- {
- return $result;
- }
-
- $labelsArray = $formValues->labels;
- $valuesArray = $formValues->values;
-
- foreach ($object as $name => $value)
- {
- $result->$name = new \stdClass;
- $result->$name->name = $name;
- $result->$name->value = $valuesArray[$name] ?? $value;
- $result->$name->label = $labelsArray[$name] ?? $name;
-
- if (is_object($value))
- {
- $subObject = new \stdClass;
-
- foreach ($value as $subName => $subValue)
- {
- $subObject->$subName = new \stdClass;
- $subObject->$subName->name = $subName;
- $subObject->$subName->value = $valuesArray[$subName] ?? $subValue;
- $subObject->$subName->label = $labelsArray[$subName] ?? $subName;
- $result->$name->value = $subObject;
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Method to prepare the object for the preview and compare views.
- *
- * @param ContentHistory $table Table object loaded with data.
- *
- * @return \stdClass Object ready for the views.
- *
- * @since 3.2
- */
- public static function prepareData(ContentHistory $table)
- {
- $object = static::decodeFields($table->version_data);
- $typesTable = Table::getInstance('ContentType', 'Joomla\\CMS\\Table\\');
- $typeAlias = explode('.', $table->item_id);
- array_pop($typeAlias);
- $typesTable->load(array('type_alias' => implode('.', $typeAlias)));
- $formValues = static::getFormValues($object, $typesTable);
- $object = static::mergeLabels($object, $formValues);
- $object = static::hideFields($object, $typesTable);
- $object = static::processLookupFields($object, $typesTable);
-
- return $object;
- }
-
- /**
- * Method to process any lookup values found in the content_history_options column for this table.
- * This allows category title and user name to be displayed instead of the id column.
- *
- * @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
- * @param ContentType $typesTable Table object loaded with data.
- *
- * @return \stdClass Object with lookup values inserted.
- *
- * @since 3.2
- */
- public static function processLookupFields($object, ContentType $typesTable)
- {
- if ($options = json_decode($typesTable->content_history_options))
- {
- if (isset($options->displayLookup) && is_array($options->displayLookup))
- {
- foreach ($options->displayLookup as $lookup)
- {
- $sourceColumn = $lookup->sourceColumn ?? false;
- $sourceValue = $object->$sourceColumn->value ?? false;
-
- if ($sourceColumn && $sourceValue && ($lookupValue = static::getLookupValue($lookup, $sourceValue)))
- {
- $object->$sourceColumn->value = $lookupValue;
- }
- }
- }
- }
-
- return $object;
- }
+ /**
+ * Method to put all field names, including nested ones, in a single array for easy lookup.
+ *
+ * @param \stdClass $object Standard class object that may contain one level of nested objects.
+ *
+ * @return array Associative array of all field names, including ones in a nested object.
+ *
+ * @since 3.2
+ */
+ public static function createObjectArray($object)
+ {
+ $result = array();
+
+ if ($object === null) {
+ return $result;
+ }
+
+ foreach ($object as $name => $value) {
+ $result[$name] = $value;
+
+ if (is_object($value)) {
+ foreach ($value as $subName => $subValue) {
+ $result[$subName] = $subValue;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to decode JSON-encoded fields in a standard object. Used to unpack JSON strings in the content history data column.
+ *
+ * @param string $jsonString JSON String to convert to an object.
+ *
+ * @return \stdClass Object with any JSON-encoded fields unpacked.
+ *
+ * @since 3.2
+ */
+ public static function decodeFields($jsonString)
+ {
+ $object = json_decode($jsonString);
+
+ if (is_object($object)) {
+ foreach ($object as $name => $value) {
+ if ($subObject = json_decode($value)) {
+ $object->$name = $subObject;
+ }
+ }
+ }
+
+ return $object;
+ }
+
+ /**
+ * Method to get field labels for the fields in the JSON-encoded object.
+ * First we see if we can find translatable labels for the fields in the object.
+ * We translate any we can find and return an array in the format object->name => label.
+ *
+ * @param \stdClass $object Standard class object in the format name->value.
+ * @param ContentType $typesTable Table object with content history options.
+ *
+ * @return \stdClass Contains two associative arrays.
+ * $formValues->labels in the format name => label (for example, 'id' => 'Article ID').
+ * $formValues->values in the format name => value (for example, 'state' => 'Published'.
+ * This translates the text from the selected option in the form.
+ *
+ * @since 3.2
+ */
+ public static function getFormValues($object, ContentType $typesTable)
+ {
+ $labels = array();
+ $values = array();
+ $expandedObjectArray = static::createObjectArray($object);
+ static::loadLanguageFiles($typesTable->type_alias);
+
+ if ($formFile = static::getFormFile($typesTable)) {
+ if ($xml = simplexml_load_file($formFile)) {
+ // Now we need to get all of the labels from the form
+ $fieldArray = $xml->xpath('//field');
+ $fieldArray = array_merge($fieldArray, $xml->xpath('//fields'));
+
+ foreach ($fieldArray as $field) {
+ if ($label = (string) $field->attributes()->label) {
+ $labels[(string) $field->attributes()->name] = Text::_($label);
+ }
+ }
+
+ // Get values for any list type fields
+ $listFieldArray = $xml->xpath('//field[@type="list" or @type="radio"]');
+
+ foreach ($listFieldArray as $field) {
+ $name = (string) $field->attributes()->name;
+
+ if (isset($expandedObjectArray[$name])) {
+ $optionFieldArray = $field->xpath('option[@value="' . $expandedObjectArray[$name] . '"]');
+
+ $valueText = null;
+
+ if (is_array($optionFieldArray) && count($optionFieldArray)) {
+ $valueText = trim((string) $optionFieldArray[0]);
+ }
+
+ $values[(string) $field->attributes()->name] = Text::_($valueText);
+ }
+ }
+ }
+ }
+
+ $result = new \stdClass();
+ $result->labels = $labels;
+ $result->values = $values;
+
+ return $result;
+ }
+
+ /**
+ * Method to get the XML form file for this component. Used to get translated field names for history preview.
+ *
+ * @param ContentType $typesTable Table object with content history options.
+ *
+ * @return mixed \JModel object if successful, false if no model found.
+ *
+ * @since 3.2
+ */
+ public static function getFormFile(ContentType $typesTable)
+ {
+ // First, see if we have a file name in the $typesTable
+ $options = json_decode($typesTable->content_history_options);
+
+ if (is_object($options) && isset($options->formFile) && File::exists(JPATH_ROOT . '/' . $options->formFile)) {
+ $result = JPATH_ROOT . '/' . $options->formFile;
+ } else {
+ $aliasArray = explode('.', $typesTable->type_alias);
+ $component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
+ $path = Folder::makeSafe(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/');
+ array_shift($aliasArray);
+ $file = File::makeSafe(implode('.', $aliasArray) . '.xml');
+ $result = File::exists($path . $file) ? $path . $file : false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to query the database using values from lookup objects.
+ *
+ * @param \stdClass $lookup The std object with the values needed to do the query.
+ * @param mixed $value The value used to find the matching title or name. Typically the id.
+ *
+ * @return mixed Value from database (for example, name or title) on success, false on failure.
+ *
+ * @since 3.2
+ */
+ public static function getLookupValue($lookup, $value)
+ {
+ $result = false;
+
+ if (isset($lookup->sourceColumn) && isset($lookup->targetTable) && isset($lookup->targetColumn) && isset($lookup->displayColumn)) {
+ $db = Factory::getDbo();
+ $value = (int) $value;
+ $query = $db->getQuery(true);
+ $query->select($db->quoteName($lookup->displayColumn))
+ ->from($db->quoteName($lookup->targetTable))
+ ->where($db->quoteName($lookup->targetColumn) . ' = :value')
+ ->bind(':value', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $result = $db->loadResult();
+ } catch (\Exception $e) {
+ // Ignore any errors and just return false
+ return false;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to remove fields from the object based on values entered in the #__content_types table.
+ *
+ * @param \stdClass $object Object to be passed to view layout file.
+ * @param ContentType $typeTable Table object with content history options.
+ *
+ * @return \stdClass object with hidden fields removed.
+ *
+ * @since 3.2
+ */
+ public static function hideFields($object, ContentType $typeTable)
+ {
+ if ($options = json_decode($typeTable->content_history_options)) {
+ if (isset($options->hideFields) && is_array($options->hideFields)) {
+ foreach ($options->hideFields as $field) {
+ unset($object->$field);
+ }
+ }
+ }
+
+ return $object;
+ }
+
+ /**
+ * Method to load the language files for the component whose history is being viewed.
+ *
+ * @param string $typeAlias The type alias, for example 'com_content.article'.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public static function loadLanguageFiles($typeAlias)
+ {
+ $aliasArray = explode('.', $typeAlias);
+
+ if (is_array($aliasArray) && count($aliasArray) == 2) {
+ $component = ($aliasArray[1] == 'category') ? 'com_categories' : $aliasArray[0];
+ $lang = Factory::getLanguage();
+
+ /**
+ * Loading language file from the administrator/language directory then
+ * loading language file from the administrator/components/extension/language directory
+ */
+ $lang->load($component, JPATH_ADMINISTRATOR)
+ || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
+
+ // Force loading of backend global language file
+ $lang->load('joomla', Path::clean(JPATH_ADMINISTRATOR));
+ }
+ }
+
+ /**
+ * Method to create object to pass to the layout. Format is as follows:
+ * field is std object with name, value.
+ *
+ * Value can be a std object with name, value pairs.
+ *
+ * @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
+ * @param \stdClass $formValues Standard class of label and value in an associative array.
+ *
+ * @return \stdClass Object with translated labels where available
+ *
+ * @since 3.2
+ */
+ public static function mergeLabels($object, $formValues)
+ {
+ $result = new \stdClass();
+
+ if ($object === null) {
+ return $result;
+ }
+
+ $labelsArray = $formValues->labels;
+ $valuesArray = $formValues->values;
+
+ foreach ($object as $name => $value) {
+ $result->$name = new \stdClass();
+ $result->$name->name = $name;
+ $result->$name->value = $valuesArray[$name] ?? $value;
+ $result->$name->label = $labelsArray[$name] ?? $name;
+
+ if (is_object($value)) {
+ $subObject = new \stdClass();
+
+ foreach ($value as $subName => $subValue) {
+ $subObject->$subName = new \stdClass();
+ $subObject->$subName->name = $subName;
+ $subObject->$subName->value = $valuesArray[$subName] ?? $subValue;
+ $subObject->$subName->label = $labelsArray[$subName] ?? $subName;
+ $result->$name->value = $subObject;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to prepare the object for the preview and compare views.
+ *
+ * @param ContentHistory $table Table object loaded with data.
+ *
+ * @return \stdClass Object ready for the views.
+ *
+ * @since 3.2
+ */
+ public static function prepareData(ContentHistory $table)
+ {
+ $object = static::decodeFields($table->version_data);
+ $typesTable = Table::getInstance('ContentType', 'Joomla\\CMS\\Table\\');
+ $typeAlias = explode('.', $table->item_id);
+ array_pop($typeAlias);
+ $typesTable->load(array('type_alias' => implode('.', $typeAlias)));
+ $formValues = static::getFormValues($object, $typesTable);
+ $object = static::mergeLabels($object, $formValues);
+ $object = static::hideFields($object, $typesTable);
+ $object = static::processLookupFields($object, $typesTable);
+
+ return $object;
+ }
+
+ /**
+ * Method to process any lookup values found in the content_history_options column for this table.
+ * This allows category title and user name to be displayed instead of the id column.
+ *
+ * @param \stdClass $object The std object from the JSON string. Can be nested 1 level deep.
+ * @param ContentType $typesTable Table object loaded with data.
+ *
+ * @return \stdClass Object with lookup values inserted.
+ *
+ * @since 3.2
+ */
+ public static function processLookupFields($object, ContentType $typesTable)
+ {
+ if ($options = json_decode($typesTable->content_history_options)) {
+ if (isset($options->displayLookup) && is_array($options->displayLookup)) {
+ foreach ($options->displayLookup as $lookup) {
+ $sourceColumn = $lookup->sourceColumn ?? false;
+ $sourceValue = $object->$sourceColumn->value ?? false;
+
+ if ($sourceColumn && $sourceValue && ($lookupValue = static::getLookupValue($lookup, $sourceValue))) {
+ $object->$sourceColumn->value = $lookupValue;
+ }
+ }
+ }
+ }
+
+ return $object;
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Model/CompareModel.php b/administrator/components/com_contenthistory/src/Model/CompareModel.php
index f9536f923ec05..4347401a2f8d3 100644
--- a/administrator/components/com_contenthistory/src/Model/CompareModel.php
+++ b/administrator/components/com_contenthistory/src/Model/CompareModel.php
@@ -1,4 +1,5 @@
input;
-
- /** @var ContentHistory $table1 */
- $table1 = $this->getTable('ContentHistory');
-
- /** @var ContentHistory $table2 */
- $table2 = $this->getTable('ContentHistory');
-
- $id1 = $input->getInt('id1');
- $id2 = $input->getInt('id2');
-
- if (!$id1 || \is_array($id1) || !$id2 || \is_array($id2))
- {
- $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_INVALID_ID'));
-
- return false;
- }
-
- $result = array();
-
- if (!$table1->load($id1) || !$table2->load($id2))
- {
- $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_VERSION_NOT_FOUND'));
-
- // Assume a failure to load the content means broken data, abort mission
- return false;
- }
-
- // Get the first history record's content type record so we can check ACL
- /** @var ContentType $contentTypeTable */
- $contentTypeTable = $this->getTable('ContentType');
- $typeAlias = explode('.', $table1->item_id);
- array_pop($typeAlias);
- $typeAlias = implode('.', $typeAlias);
-
- if (!$contentTypeTable->load(array('type_alias' => $typeAlias)))
- {
- $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_FAILED_LOADING_CONTENT_TYPE'));
-
- // Assume a failure to load the content type means broken data, abort mission
- return false;
- }
-
- $user = Factory::getUser();
-
- // Access check
- if (!$user->authorise('core.edit', $table1->item_id) && !$this->canEdit($table1))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $nullDate = $this->getDatabase()->getNullDate();
-
- foreach (array($table1, $table2) as $table)
- {
- $object = new \stdClass;
- $object->data = ContenthistoryHelper::prepareData($table);
- $object->version_note = $table->version_note;
-
- // Let's use custom calendars when present
- $object->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
-
- $dateProperties = array (
- 'modified_time',
- 'created_time',
- 'modified',
- 'created',
- 'checked_out_time',
- 'publish_up',
- 'publish_down',
- );
-
- foreach ($dateProperties as $dateProperty)
- {
- if (property_exists($object->data, $dateProperty)
- && $object->data->$dateProperty->value !== null
- && $object->data->$dateProperty->value !== $nullDate)
- {
- $object->data->$dateProperty->value = HTMLHelper::_(
- 'date',
- $object->data->$dateProperty->value,
- Text::_('DATE_FORMAT_LC6')
- );
- }
- }
-
- $result[] = $object;
- }
-
- return $result;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 3.2
- */
- public function getTable($type = 'Contenthistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
-
- /**
- * Method to test whether a record is editable
- *
- * @param ContentHistory $record A Table object.
- *
- * @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
- *
- * @since 3.6
- */
- protected function canEdit($record)
- {
- $result = false;
-
- if (!empty($record->item_id))
- {
- /**
- * Make sure user has edit privileges for this content item. Note that we use edit permissions
- * for the content item, not delete permissions for the content history row.
- */
- $user = Factory::getUser();
- $result = $user->authorise('core.edit', $record->item_id);
-
- // Finally try session (this catches edit.own case too)
- if (!$result)
- {
- /** @var ContentType $contentTypeTable */
- $contentTypeTable = $this->getTable('ContentType');
-
- $typeAlias = explode('.', $record->item_id);
- $id = array_pop($typeAlias);
- $typeAlias = implode('.', $typeAlias);
- $contentTypeTable->load(array('type_alias' => $typeAlias));
- $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
- $result = in_array((int) $id, $typeEditables);
- }
- }
-
- return $result;
- }
+ /**
+ * Method to get a version history row.
+ *
+ * @return array|boolean On success, array of populated tables. False on failure.
+ *
+ * @since 3.2
+ *
+ * @throws NotAllowed Thrown if not authorised to edit an item
+ */
+ public function getItems()
+ {
+ $input = Factory::getApplication()->input;
+
+ /** @var ContentHistory $table1 */
+ $table1 = $this->getTable('ContentHistory');
+
+ /** @var ContentHistory $table2 */
+ $table2 = $this->getTable('ContentHistory');
+
+ $id1 = $input->getInt('id1');
+ $id2 = $input->getInt('id2');
+
+ if (!$id1 || \is_array($id1) || !$id2 || \is_array($id2)) {
+ $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_INVALID_ID'));
+
+ return false;
+ }
+
+ $result = array();
+
+ if (!$table1->load($id1) || !$table2->load($id2)) {
+ $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_VERSION_NOT_FOUND'));
+
+ // Assume a failure to load the content means broken data, abort mission
+ return false;
+ }
+
+ // Get the first history record's content type record so we can check ACL
+ /** @var ContentType $contentTypeTable */
+ $contentTypeTable = $this->getTable('ContentType');
+ $typeAlias = explode('.', $table1->item_id);
+ array_pop($typeAlias);
+ $typeAlias = implode('.', $typeAlias);
+
+ if (!$contentTypeTable->load(array('type_alias' => $typeAlias))) {
+ $this->setError(Text::_('COM_CONTENTHISTORY_ERROR_FAILED_LOADING_CONTENT_TYPE'));
+
+ // Assume a failure to load the content type means broken data, abort mission
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ // Access check
+ if (!$user->authorise('core.edit', $table1->item_id) && !$this->canEdit($table1)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $nullDate = $this->getDatabase()->getNullDate();
+
+ foreach (array($table1, $table2) as $table) {
+ $object = new \stdClass();
+ $object->data = ContenthistoryHelper::prepareData($table);
+ $object->version_note = $table->version_note;
+
+ // Let's use custom calendars when present
+ $object->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
+
+ $dateProperties = array (
+ 'modified_time',
+ 'created_time',
+ 'modified',
+ 'created',
+ 'checked_out_time',
+ 'publish_up',
+ 'publish_down',
+ );
+
+ foreach ($dateProperties as $dateProperty) {
+ if (
+ property_exists($object->data, $dateProperty)
+ && $object->data->$dateProperty->value !== null
+ && $object->data->$dateProperty->value !== $nullDate
+ ) {
+ $object->data->$dateProperty->value = HTMLHelper::_(
+ 'date',
+ $object->data->$dateProperty->value,
+ Text::_('DATE_FORMAT_LC6')
+ );
+ }
+ }
+
+ $result[] = $object;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 3.2
+ */
+ public function getTable($type = 'Contenthistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+
+ /**
+ * Method to test whether a record is editable
+ *
+ * @param ContentHistory $record A Table object.
+ *
+ * @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
+ *
+ * @since 3.6
+ */
+ protected function canEdit($record)
+ {
+ $result = false;
+
+ if (!empty($record->item_id)) {
+ /**
+ * Make sure user has edit privileges for this content item. Note that we use edit permissions
+ * for the content item, not delete permissions for the content history row.
+ */
+ $user = Factory::getUser();
+ $result = $user->authorise('core.edit', $record->item_id);
+
+ // Finally try session (this catches edit.own case too)
+ if (!$result) {
+ /** @var ContentType $contentTypeTable */
+ $contentTypeTable = $this->getTable('ContentType');
+
+ $typeAlias = explode('.', $record->item_id);
+ $id = array_pop($typeAlias);
+ $typeAlias = implode('.', $typeAlias);
+ $contentTypeTable->load(array('type_alias' => $typeAlias));
+ $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
+ $result = in_array((int) $id, $typeEditables);
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Model/HistoryModel.php b/administrator/components/com_contenthistory/src/Model/HistoryModel.php
index ca2976b273d9f..d197182b3beea 100644
--- a/administrator/components/com_contenthistory/src/Model/HistoryModel.php
+++ b/administrator/components/com_contenthistory/src/Model/HistoryModel.php
@@ -1,4 +1,5 @@
item_id))
- {
- return false;
- }
-
- /**
- * Make sure user has edit privileges for this content item. Note that we use edit permissions
- * for the content item, not delete permissions for the content history row.
- */
- $user = Factory::getUser();
-
- if ($user->authorise('core.edit', $record->item_id))
- {
- return true;
- }
-
- // Finally try session (this catches edit.own case too)
- /** @var ContentType $contentTypeTable */
- $contentTypeTable = $this->getTable('ContentType');
-
- $typeAlias = explode('.', $record->item_id);
- $id = array_pop($typeAlias);
- $typeAlias = implode('.', $typeAlias);
- $contentTypeTable->load(array('type_alias' => $typeAlias));
- $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
- $result = in_array((int) $id, $typeEditables);
-
- return $result;
- }
-
- /**
- * Method to test whether a history record can be deleted. Note that we check whether we have edit permissions
- * for the content item row.
- *
- * @param ContentHistory $record A Table object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 3.6
- */
- protected function canDelete($record)
- {
- return $this->canEdit($record);
- }
-
- /**
- * Method to delete one or more records from content history table.
- *
- * @param array $pks An array of record primary keys.
- *
- * @return boolean True if successful, false if an error occurs.
- *
- * @since 3.2
- */
- public function delete(&$pks)
- {
- $pks = (array) $pks;
- $table = $this->getTable();
-
- // Iterate the items to delete each one.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- if ((int) $table->keep_forever === 1)
- {
- unset($pks[$i]);
- continue;
- }
-
- if ($this->canEdit($table))
- {
- if (!$table->delete($pk))
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- $error = $this->getError();
-
- if ($error)
- {
- try
- {
- Log::add($error, Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage($error, 'warning');
- }
-
- return false;
- }
- else
- {
- try
- {
- Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'warning');
- }
-
- return false;
- }
- }
- }
- else
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Clear the component's cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 3.4.5
- *
- * @throws NotAllowed Thrown if not authorised to edit an item
- */
- public function getItems()
- {
- $items = parent::getItems();
- $user = Factory::getUser();
-
- if ($items === false)
- {
- return false;
- }
-
- // This should be an array with at least one element
- if (!is_array($items) || !isset($items[0]))
- {
- return $items;
- }
-
- // Access check
- if (!$user->authorise('core.edit', $items[0]->item_id) && !$this->canEdit($items[0]))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- return $items;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 3.2
- */
- public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
- /**
- * Method to toggle on and off the keep forever value for one or more records from content history table.
- *
- * @param array $pks An array of record primary keys.
- *
- * @return boolean True if successful, false if an error occurs.
- *
- * @since 3.2
- */
- public function keep(&$pks)
- {
- $pks = (array) $pks;
- $table = $this->getTable();
-
- // Iterate the items to delete each one.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- if ($this->canEdit($table))
- {
- $table->keep_forever = $table->keep_forever ? 0 : 1;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- $error = $this->getError();
-
- if ($error)
- {
- try
- {
- Log::add($error, Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage($error, 'warning');
- }
-
- return false;
- }
- else
- {
- try
- {
- Log::add(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), 'warning');
- }
-
- return false;
- }
- }
- }
- else
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Clear the component's cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 3.2
- */
- protected function populateState($ordering = 'h.save_date', $direction = 'DESC')
- {
- $input = Factory::getApplication()->input;
- $itemId = $input->get('item_id', '', 'string');
-
- $this->setState('item_id', $itemId);
- $this->setState('sha1_hash', $this->getSha1Hash());
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_contenthistory');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 3.2
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $itemId = $this->getState('item_id');
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('h.version_id'),
- $db->quoteName('h.item_id'),
- $db->quoteName('h.version_note'),
- $db->quoteName('h.save_date'),
- $db->quoteName('h.editor_user_id'),
- $db->quoteName('h.character_count'),
- $db->quoteName('h.sha1_hash'),
- $db->quoteName('h.version_data'),
- $db->quoteName('h.keep_forever'),
- ]
- )
- )
- ->from($db->quoteName('#__history', 'h'))
- ->where($db->quoteName('h.item_id') . ' = :itemid')
- ->bind(':itemid', $itemId, ParameterType::STRING)
-
- // Join over the users for the editor
- ->select($db->quoteName('uc.name', 'editor'))
- ->join('LEFT',
- $db->quoteName('#__users', 'uc'),
- $db->quoteName('uc.id') . ' = ' . $db->quoteName('h.editor_user_id')
- );
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering');
- $orderDirn = $this->state->get('list.direction');
- $query->order($db->quoteName($orderCol) . $orderDirn);
-
- return $query;
- }
-
- /**
- * Get the sha1 hash value for the current item being edited.
- *
- * @return string sha1 hash of row data
- *
- * @since 3.2
- */
- protected function getSha1Hash()
- {
- $result = false;
- $item_id = Factory::getApplication()->input->getCmd('item_id', '');
- $typeAlias = explode('.', $item_id);
- Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $typeAlias[0] . '/tables');
- $typeTable = $this->getTable('ContentType');
- $typeTable->load(['type_alias' => $typeAlias[0] . '.' . $typeAlias[1]]);
- $contentTable = $typeTable->getContentTable();
-
- if ($contentTable && $contentTable->load($typeAlias[2]))
- {
- $helper = new CMSHelper;
-
- $dataObject = $helper->getDataObject($contentTable);
- $result = $this->getTable('ContentHistory')->getSha1(json_encode($dataObject), $typeTable);
- }
-
- return $result;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'version_id',
+ 'h.version_id',
+ 'version_note',
+ 'h.version_note',
+ 'save_date',
+ 'h.save_date',
+ 'editor_user_id',
+ 'h.editor_user_id',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to test whether a record is editable
+ *
+ * @param ContentHistory $record A Table object.
+ *
+ * @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
+ *
+ * @since 3.2
+ */
+ protected function canEdit($record)
+ {
+ if (empty($record->item_id)) {
+ return false;
+ }
+
+ /**
+ * Make sure user has edit privileges for this content item. Note that we use edit permissions
+ * for the content item, not delete permissions for the content history row.
+ */
+ $user = Factory::getUser();
+
+ if ($user->authorise('core.edit', $record->item_id)) {
+ return true;
+ }
+
+ // Finally try session (this catches edit.own case too)
+ /** @var ContentType $contentTypeTable */
+ $contentTypeTable = $this->getTable('ContentType');
+
+ $typeAlias = explode('.', $record->item_id);
+ $id = array_pop($typeAlias);
+ $typeAlias = implode('.', $typeAlias);
+ $contentTypeTable->load(array('type_alias' => $typeAlias));
+ $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
+ $result = in_array((int) $id, $typeEditables);
+
+ return $result;
+ }
+
+ /**
+ * Method to test whether a history record can be deleted. Note that we check whether we have edit permissions
+ * for the content item row.
+ *
+ * @param ContentHistory $record A Table object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 3.6
+ */
+ protected function canDelete($record)
+ {
+ return $this->canEdit($record);
+ }
+
+ /**
+ * Method to delete one or more records from content history table.
+ *
+ * @param array $pks An array of record primary keys.
+ *
+ * @return boolean True if successful, false if an error occurs.
+ *
+ * @since 3.2
+ */
+ public function delete(&$pks)
+ {
+ $pks = (array) $pks;
+ $table = $this->getTable();
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ if ((int) $table->keep_forever === 1) {
+ unset($pks[$i]);
+ continue;
+ }
+
+ if ($this->canEdit($table)) {
+ if (!$table->delete($pk)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ $error = $this->getError();
+
+ if ($error) {
+ try {
+ Log::add($error, Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage($error, 'warning');
+ }
+
+ return false;
+ } else {
+ try {
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'warning');
+ }
+
+ return false;
+ }
+ }
+ } else {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Clear the component's cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 3.4.5
+ *
+ * @throws NotAllowed Thrown if not authorised to edit an item
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+ $user = Factory::getUser();
+
+ if ($items === false) {
+ return false;
+ }
+
+ // This should be an array with at least one element
+ if (!is_array($items) || !isset($items[0])) {
+ return $items;
+ }
+
+ // Access check
+ if (!$user->authorise('core.edit', $items[0]->item_id) && !$this->canEdit($items[0])) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 3.2
+ */
+ public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+ /**
+ * Method to toggle on and off the keep forever value for one or more records from content history table.
+ *
+ * @param array $pks An array of record primary keys.
+ *
+ * @return boolean True if successful, false if an error occurs.
+ *
+ * @since 3.2
+ */
+ public function keep(&$pks)
+ {
+ $pks = (array) $pks;
+ $table = $this->getTable();
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ if ($this->canEdit($table)) {
+ $table->keep_forever = $table->keep_forever ? 0 : 1;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ $error = $this->getError();
+
+ if ($error) {
+ try {
+ Log::add($error, Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage($error, 'warning');
+ }
+
+ return false;
+ } else {
+ try {
+ Log::add(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_CONTENTHISTORY_ERROR_KEEP_NOT_PERMITTED'), 'warning');
+ }
+
+ return false;
+ }
+ }
+ } else {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Clear the component's cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function populateState($ordering = 'h.save_date', $direction = 'DESC')
+ {
+ $input = Factory::getApplication()->input;
+ $itemId = $input->get('item_id', '', 'string');
+
+ $this->setState('item_id', $itemId);
+ $this->setState('sha1_hash', $this->getSha1Hash());
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_contenthistory');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 3.2
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $itemId = $this->getState('item_id');
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('h.version_id'),
+ $db->quoteName('h.item_id'),
+ $db->quoteName('h.version_note'),
+ $db->quoteName('h.save_date'),
+ $db->quoteName('h.editor_user_id'),
+ $db->quoteName('h.character_count'),
+ $db->quoteName('h.sha1_hash'),
+ $db->quoteName('h.version_data'),
+ $db->quoteName('h.keep_forever'),
+ ]
+ )
+ )
+ ->from($db->quoteName('#__history', 'h'))
+ ->where($db->quoteName('h.item_id') . ' = :itemid')
+ ->bind(':itemid', $itemId, ParameterType::STRING)
+
+ // Join over the users for the editor
+ ->select($db->quoteName('uc.name', 'editor'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'uc'),
+ $db->quoteName('uc.id') . ' = ' . $db->quoteName('h.editor_user_id')
+ );
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering');
+ $orderDirn = $this->state->get('list.direction');
+ $query->order($db->quoteName($orderCol) . $orderDirn);
+
+ return $query;
+ }
+
+ /**
+ * Get the sha1 hash value for the current item being edited.
+ *
+ * @return string sha1 hash of row data
+ *
+ * @since 3.2
+ */
+ protected function getSha1Hash()
+ {
+ $result = false;
+ $item_id = Factory::getApplication()->input->getCmd('item_id', '');
+ $typeAlias = explode('.', $item_id);
+ Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $typeAlias[0] . '/tables');
+ $typeTable = $this->getTable('ContentType');
+ $typeTable->load(['type_alias' => $typeAlias[0] . '.' . $typeAlias[1]]);
+ $contentTable = $typeTable->getContentTable();
+
+ if ($contentTable && $contentTable->load($typeAlias[2])) {
+ $helper = new CMSHelper();
+
+ $dataObject = $helper->getDataObject($contentTable);
+ $result = $this->getTable('ContentHistory')->getSha1(json_encode($dataObject), $typeTable);
+ }
+
+ return $result;
+ }
}
diff --git a/administrator/components/com_contenthistory/src/Model/PreviewModel.php b/administrator/components/com_contenthistory/src/Model/PreviewModel.php
index 7b10ed902f3db..ea420eba5ec0e 100644
--- a/administrator/components/com_contenthistory/src/Model/PreviewModel.php
+++ b/administrator/components/com_contenthistory/src/Model/PreviewModel.php
@@ -1,4 +1,5 @@
getTable('ContentHistory');
- $versionId = Factory::getApplication()->input->getInt('version_id');
-
- if (!$versionId || \is_array($versionId) || !$table->load($versionId))
- {
- return false;
- }
-
- $user = Factory::getUser();
-
- // Access check
- if (!$user->authorise('core.edit', $table->item_id) && !$this->canEdit($table))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $result = new \stdClass;
- $result->version_note = $table->version_note;
- $result->data = ContenthistoryHelper::prepareData($table);
-
- // Let's use custom calendars when present
- $result->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
-
- $dateProperties = array (
- 'modified_time',
- 'created_time',
- 'modified',
- 'created',
- 'checked_out_time',
- 'publish_up',
- 'publish_down',
- );
-
- $nullDate = $this->getDatabase()->getNullDate();
-
- foreach ($dateProperties as $dateProperty)
- {
- if (property_exists($result->data, $dateProperty)
- && $result->data->$dateProperty->value !== null
- && $result->data->$dateProperty->value !== $nullDate)
- {
- $result->data->$dateProperty->value = HTMLHelper::_(
- 'date',
- $result->data->$dateProperty->value,
- Text::_('DATE_FORMAT_LC6')
- );
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 3.2
- */
- public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
-
- /**
- * Method to test whether a record is editable
- *
- * @param ContentHistory $record A Table object.
- *
- * @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
- *
- * @since 3.6
- */
- protected function canEdit($record)
- {
- $result = false;
-
- if (!empty($record->item_id))
- {
- /**
- * Make sure user has edit privileges for this content item. Note that we use edit permissions
- * for the content item, not delete permissions for the content history row.
- */
- $user = Factory::getUser();
- $result = $user->authorise('core.edit', $record->item_id);
-
- // Finally try session (this catches edit.own case too)
- if (!$result)
- {
- /** @var ContentType $contentTypeTable */
- $contentTypeTable = $this->getTable('ContentType');
-
- $typeAlias = explode('.', $record->item_id);
- $id = array_pop($typeAlias);
- $typeAlias = implode('.', $typeAlias);
- $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
- $result = in_array((int) $id, $typeEditables);
- }
- }
-
- return $result;
- }
+ /**
+ * Method to get a version history row.
+ *
+ * @param integer $pk The id of the item
+ *
+ * @return \stdClass|boolean On success, standard object with row data. False on failure.
+ *
+ * @since 3.2
+ *
+ * @throws NotAllowed Thrown if not authorised to edit an item
+ */
+ public function getItem($pk = null)
+ {
+ /** @var ContentHistory $table */
+ $table = $this->getTable('ContentHistory');
+ $versionId = Factory::getApplication()->input->getInt('version_id');
+
+ if (!$versionId || \is_array($versionId) || !$table->load($versionId)) {
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ // Access check
+ if (!$user->authorise('core.edit', $table->item_id) && !$this->canEdit($table)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $result = new \stdClass();
+ $result->version_note = $table->version_note;
+ $result->data = ContenthistoryHelper::prepareData($table);
+
+ // Let's use custom calendars when present
+ $result->save_date = HTMLHelper::_('date', $table->save_date, Text::_('DATE_FORMAT_LC6'));
+
+ $dateProperties = array (
+ 'modified_time',
+ 'created_time',
+ 'modified',
+ 'created',
+ 'checked_out_time',
+ 'publish_up',
+ 'publish_down',
+ );
+
+ $nullDate = $this->getDatabase()->getNullDate();
+
+ foreach ($dateProperties as $dateProperty) {
+ if (
+ property_exists($result->data, $dateProperty)
+ && $result->data->$dateProperty->value !== null
+ && $result->data->$dateProperty->value !== $nullDate
+ ) {
+ $result->data->$dateProperty->value = HTMLHelper::_(
+ 'date',
+ $result->data->$dateProperty->value,
+ Text::_('DATE_FORMAT_LC6')
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 3.2
+ */
+ public function getTable($type = 'ContentHistory', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+
+ /**
+ * Method to test whether a record is editable
+ *
+ * @param ContentHistory $record A Table object.
+ *
+ * @return boolean True if allowed to edit the record. Defaults to the permission set in the component.
+ *
+ * @since 3.6
+ */
+ protected function canEdit($record)
+ {
+ $result = false;
+
+ if (!empty($record->item_id)) {
+ /**
+ * Make sure user has edit privileges for this content item. Note that we use edit permissions
+ * for the content item, not delete permissions for the content history row.
+ */
+ $user = Factory::getUser();
+ $result = $user->authorise('core.edit', $record->item_id);
+
+ // Finally try session (this catches edit.own case too)
+ if (!$result) {
+ /** @var ContentType $contentTypeTable */
+ $contentTypeTable = $this->getTable('ContentType');
+
+ $typeAlias = explode('.', $record->item_id);
+ $id = array_pop($typeAlias);
+ $typeAlias = implode('.', $typeAlias);
+ $typeEditables = (array) Factory::getApplication()->getUserState(str_replace('.', '.edit.', $contentTypeTable->type_alias) . '.id');
+ $result = in_array((int) $id, $typeEditables);
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/administrator/components/com_contenthistory/src/View/Compare/HtmlView.php b/administrator/components/com_contenthistory/src/View/Compare/HtmlView.php
index 8759e6357c9d6..a91d2945f7502 100644
--- a/administrator/components/com_contenthistory/src/View/Compare/HtmlView.php
+++ b/administrator/components/com_contenthistory/src/View/Compare/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- parent::display($tpl);
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_contenthistory/src/View/History/HtmlView.php b/administrator/components/com_contenthistory/src/View/History/HtmlView.php
index 77bbc7eb2c289..beba710648a34 100644
--- a/administrator/components/com_contenthistory/src/View/History/HtmlView.php
+++ b/administrator/components/com_contenthistory/src/View/History/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->toolbar = $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page toolbar.
- *
- * @return Toolbar
- *
- * @since 4.0.0
- */
- protected function addToolbar(): Toolbar
- {
- /** @var Toolbar $toolbar */
- $toolbar = Factory::getContainer()->get(ToolbarFactoryInterface::class)->createToolbar('toolbar');
-
- // Cache a session token for reuse throughout.
- $token = Session::getFormToken();
-
- // Clean up input to ensure a clean url.
- $aliasArray = explode('.', $this->state->item_id);
- $option = $aliasArray[1] == 'category'
- ? 'com_categories&extension=' . implode('.', array_slice($aliasArray, 0, count($aliasArray) - 2))
- : $aliasArray[0];
- $filter = InputFilter::getInstance();
- $task = $filter->clean($aliasArray[1], 'cmd') . '.loadhistory';
-
- // Build the final urls.
- $loadUrl = Route::_('index.php?option=' . $filter->clean($option, 'cmd') . '&task=' . $task . '&' . $token . '=1');
- $previewUrl = Route::_('index.php?option=com_contenthistory&view=preview&layout=preview&tmpl=component&' . $token . '=1');
- $compareUrl = Route::_('index.php?option=com_contenthistory&view=compare&layout=compare&tmpl=component&' . $token . '=1');
-
- $toolbar->basicButton('load')
- ->attributes(['data-url' => $loadUrl])
- ->icon('icon-upload')
- ->buttonClass('btn btn-success')
- ->text('COM_CONTENTHISTORY_BUTTON_LOAD')
- ->listCheck(true);
-
- $toolbar->basicButton('preview')
- ->attributes(['data-url' => $previewUrl])
- ->icon('icon-search')
- ->text('COM_CONTENTHISTORY_BUTTON_PREVIEW')
- ->listCheck(true);
-
- $toolbar->basicButton('compare')
- ->attributes(['data-url' => $compareUrl])
- ->icon('icon-search-plus')
- ->text('COM_CONTENTHISTORY_BUTTON_COMPARE')
- ->listCheck(true);
-
- $toolbar->basicButton('keep')
- ->task('history.keep')
- ->buttonClass('btn btn-inverse')
- ->icon('icon-lock')
- ->text('COM_CONTENTHISTORY_BUTTON_KEEP')
- ->listCheck(true);
-
- $toolbar->basicButton('delete')
- ->task('history.delete')
- ->buttonClass('btn btn-danger')
- ->icon('icon-times')
- ->text('COM_CONTENTHISTORY_BUTTON_DELETE')
- ->listCheck(true);
-
- return $toolbar;
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The model state
+ *
+ * @var Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->toolbar = $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page toolbar.
+ *
+ * @return Toolbar
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar(): Toolbar
+ {
+ /** @var Toolbar $toolbar */
+ $toolbar = Factory::getContainer()->get(ToolbarFactoryInterface::class)->createToolbar('toolbar');
+
+ // Cache a session token for reuse throughout.
+ $token = Session::getFormToken();
+
+ // Clean up input to ensure a clean url.
+ $aliasArray = explode('.', $this->state->item_id);
+ $option = $aliasArray[1] == 'category'
+ ? 'com_categories&extension=' . implode('.', array_slice($aliasArray, 0, count($aliasArray) - 2))
+ : $aliasArray[0];
+ $filter = InputFilter::getInstance();
+ $task = $filter->clean($aliasArray[1], 'cmd') . '.loadhistory';
+
+ // Build the final urls.
+ $loadUrl = Route::_('index.php?option=' . $filter->clean($option, 'cmd') . '&task=' . $task . '&' . $token . '=1');
+ $previewUrl = Route::_('index.php?option=com_contenthistory&view=preview&layout=preview&tmpl=component&' . $token . '=1');
+ $compareUrl = Route::_('index.php?option=com_contenthistory&view=compare&layout=compare&tmpl=component&' . $token . '=1');
+
+ $toolbar->basicButton('load')
+ ->attributes(['data-url' => $loadUrl])
+ ->icon('icon-upload')
+ ->buttonClass('btn btn-success')
+ ->text('COM_CONTENTHISTORY_BUTTON_LOAD')
+ ->listCheck(true);
+
+ $toolbar->basicButton('preview')
+ ->attributes(['data-url' => $previewUrl])
+ ->icon('icon-search')
+ ->text('COM_CONTENTHISTORY_BUTTON_PREVIEW')
+ ->listCheck(true);
+
+ $toolbar->basicButton('compare')
+ ->attributes(['data-url' => $compareUrl])
+ ->icon('icon-search-plus')
+ ->text('COM_CONTENTHISTORY_BUTTON_COMPARE')
+ ->listCheck(true);
+
+ $toolbar->basicButton('keep')
+ ->task('history.keep')
+ ->buttonClass('btn btn-inverse')
+ ->icon('icon-lock')
+ ->text('COM_CONTENTHISTORY_BUTTON_KEEP')
+ ->listCheck(true);
+
+ $toolbar->basicButton('delete')
+ ->task('history.delete')
+ ->buttonClass('btn btn-danger')
+ ->icon('icon-times')
+ ->text('COM_CONTENTHISTORY_BUTTON_DELETE')
+ ->listCheck(true);
+
+ return $toolbar;
+ }
}
diff --git a/administrator/components/com_contenthistory/src/View/Preview/HtmlView.php b/administrator/components/com_contenthistory/src/View/Preview/HtmlView.php
index df61b6c29bac8..236a308df8172 100644
--- a/administrator/components/com_contenthistory/src/View/Preview/HtmlView.php
+++ b/administrator/components/com_contenthistory/src/View/Preview/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->item = $this->get('Item');
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->item = $this->get('Item');
- if (false === $this->item)
- {
- Factory::getLanguage()->load('com_content', JPATH_SITE, null, true);
+ if (false === $this->item) {
+ Factory::getLanguage()->load('com_content', JPATH_SITE, null, true);
- throw new \Exception(Text::_('COM_CONTENT_ERROR_ARTICLE_NOT_FOUND'), 404);
- }
+ throw new \Exception(Text::_('COM_CONTENT_ERROR_ARTICLE_NOT_FOUND'), 404);
+ }
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_contenthistory/tmpl/compare/compare.php b/administrator/components/com_contenthistory/tmpl/compare/compare.php
index 78e0a3fc2cd7f..c269fdde1fd74 100644
--- a/administrator/components/com_contenthistory/tmpl/compare/compare.php
+++ b/administrator/components/com_contenthistory/tmpl/compare/compare.php
@@ -1,4 +1,5 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- $value) : ?>
- value) && isset($object2->$name->value) && $value->value != $object2->$name->value) : ?>
- value)) : ?>
-
-
- label; ?>
-
-
- value as $subName => $subValue) : ?>
- $name->value->$subName->value ?? ''; ?>
- value || $newSubValue) : ?>
- value != $newSubValue) : ?>
-
- label; ?>
- value, ENT_COMPAT, 'UTF-8'); ?>
-
-
-
-
-
-
-
-
-
- label; ?>
-
- value); ?>
- $name->value = is_object($object2->$name->value) ? json_encode($object2->$name->value) : $object2->$name->value; ?>
- $name->value, ENT_COMPAT, 'UTF-8'); ?>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $value) : ?>
+ value) && isset($object2->$name->value) && $value->value != $object2->$name->value) : ?>
+ value)) : ?>
+
+
+ label; ?>
+
+
+ value as $subName => $subValue) : ?>
+ $name->value->$subName->value ?? ''; ?>
+ value || $newSubValue) : ?>
+ value != $newSubValue) : ?>
+
+ label; ?>
+ value, ENT_COMPAT, 'UTF-8'); ?>
+
+
+
+
+
+
+
+
+
+ label; ?>
+
+ value); ?>
+ $name->value = is_object($object2->$name->value) ? json_encode($object2->$name->value) : $object2->$name->value; ?>
+ $name->value, ENT_COMPAT, 'UTF-8'); ?>
+
+
+
+
+
+
+
diff --git a/administrator/components/com_contenthistory/tmpl/history/modal.php b/administrator/components/com_contenthistory/tmpl/history/modal.php
index 3f6f7cbf96d08..00dd1848e8751 100644
--- a/administrator/components/com_contenthistory/tmpl/history/modal.php
+++ b/administrator/components/com_contenthistory/tmpl/history/modal.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('multiselect')
- ->useScript('com_contenthistory.admin-history-modal');
+ ->useScript('com_contenthistory.admin-history-modal');
?>
diff --git a/administrator/components/com_contenthistory/tmpl/preview/preview.php b/administrator/components/com_contenthistory/tmpl/preview/preview.php
index a97b9f0457739..f6b6cc5ab9743 100644
--- a/administrator/components/com_contenthistory/tmpl/preview/preview.php
+++ b/administrator/components/com_contenthistory/tmpl/preview/preview.php
@@ -1,4 +1,5 @@
-
- item->save_date); ?>
-
- item->version_note) : ?>
-
- item->version_note); ?>
-
-
+
+ item->save_date); ?>
+
+ item->version_note) : ?>
+
+ item->version_note); ?>
+
+
-
-
-
-
-
-
-
-
-
-
-
- item->data as $name => $value) : ?>
- value)) : ?>
-
-
- label; ?>
-
-
- value as $subName => $subValue) : ?>
-
-
- label; ?>
- value; ?>
-
-
-
-
-
- label; ?>
- value; ?>
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ item->data as $name => $value) : ?>
+ value)) : ?>
+
+
+ label; ?>
+
+
+ value as $subName => $subValue) : ?>
+
+
+ label; ?>
+ value; ?>
+
+
+
+
+
+ label; ?>
+ value; ?>
+
+
+
+
+
diff --git a/administrator/components/com_cpanel/services/provider.php b/administrator/components/com_cpanel/services/provider.php
index 0b42c914f2c14..064bc2bdf3e80 100644
--- a/administrator/components/com_cpanel/services/provider.php
+++ b/administrator/components/com_cpanel/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Cpanel'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Cpanel'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Cpanel'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Cpanel'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_cpanel/src/Controller/DisplayController.php b/administrator/components/com_cpanel/src/Controller/DisplayController.php
index 9a68c3f4ef416..485f8f7520c37 100644
--- a/administrator/components/com_cpanel/src/Controller/DisplayController.php
+++ b/administrator/components/com_cpanel/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->set('tmpl', 'cpanel');
+ /**
+ * Typical view method for MVC based architecture
+ *
+ * This function is provide as a default implementation, in most cases
+ * you will need to override it in your own controllers.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static An instance of the current object to support chaining.
+ *
+ * @since 3.0
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ /*
+ * Set the template - this will display cpanel.php
+ * from the selected admin template.
+ */
+ $this->input->set('tmpl', 'cpanel');
- return parent::display($cachable, $urlparams);
- }
+ return parent::display($cachable, $urlparams);
+ }
- /**
- * Method to add a module to a dashboard
- *
- * @since 4.0.0
- *
- * @return void
- */
- public function addModule()
- {
- $position = $this->input->get('position', 'cpanel');
- $function = $this->input->get('function');
+ /**
+ * Method to add a module to a dashboard
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ */
+ public function addModule()
+ {
+ $position = $this->input->get('position', 'cpanel');
+ $function = $this->input->get('function');
- $appendLink = '';
+ $appendLink = '';
- if ($function)
- {
- $appendLink .= '&function=' . $function;
- }
+ if ($function) {
+ $appendLink .= '&function=' . $function;
+ }
- if (substr($position, 0, 6) != 'cpanel')
- {
- $position = 'cpanel';
- }
+ if (substr($position, 0, 6) != 'cpanel') {
+ $position = 'cpanel';
+ }
- $this->app->setUserState('com_modules.modules.filter.position', $position);
- $this->app->setUserState('com_modules.modules.client_id', '1');
+ $this->app->setUserState('com_modules.modules.filter.position', $position);
+ $this->app->setUserState('com_modules.modules.client_id', '1');
- $this->setRedirect(Route::_('index.php?option=com_modules&view=select&tmpl=component&layout=modal' . $appendLink, false));
- }
+ $this->setRedirect(Route::_('index.php?option=com_modules&view=select&tmpl=component&layout=modal' . $appendLink, false));
+ }
}
diff --git a/administrator/components/com_cpanel/src/Dispatcher/Dispatcher.php b/administrator/components/com_cpanel/src/Dispatcher/Dispatcher.php
index 2dd0c8c74b23e..089f3159e4f85 100644
--- a/administrator/components/com_cpanel/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_cpanel/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
input->getCmd('dashboard', '');
-
- $position = ApplicationHelper::stringURLSafe($dashboard);
-
- // Generate a title for the view cpanel
- if (!empty($dashboard))
- {
- $parts = explode('.', $dashboard);
- $component = $parts[0];
-
- if (strpos($component, 'com_') === false)
- {
- $component = 'com_' . $component;
- }
-
- // Need to load the language file
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE)
- || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
- $lang->load($component);
-
- // Lookup dashboard attributes from component manifest file
- $manifestFile = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml';
-
- if (is_file($manifestFile))
- {
- $manifest = simplexml_load_file($manifestFile);
-
- if ($dashboardManifests = $manifest->dashboards)
- {
- foreach ($dashboardManifests->children() as $dashboardManifest)
- {
- if ((string) $dashboardManifest === $dashboard)
- {
- $title = Text::_((string) $dashboardManifest->attributes()->title);
- $icon = (string) $dashboardManifest->attributes()->icon;
-
- break;
- }
- }
- }
- }
-
- if (empty($title))
- {
- // Try building a title
- $prefix = strtoupper($component) . '_DASHBOARD';
-
- $sectionkey = !empty($parts[1]) ? '_' . strtoupper($parts[1]) : '';
- $key = $prefix . $sectionkey . '_TITLE';
- $keyIcon = $prefix . $sectionkey . '_ICON';
-
- // Search for a component title
- if ($lang->hasKey($key))
- {
- $title = Text::_($key);
- }
- else
- {
- // Try with a string from CPanel
- $key = 'COM_CPANEL_DASHBOARD_' . $parts[0] . '_TITLE';
-
- if ($lang->hasKey($key))
- {
- $title = Text::_($key);
- }
- else
- {
- $title = Text::_('COM_CPANEL_DASHBOARD_BASE_TITLE');
- }
- }
-
- // Define the icon
- if (empty($parts[1]))
- {
- // Default core icons.
- if ($parts[0] === 'components')
- {
- $icon = 'icon-puzzle-piece';
- }
- elseif ($parts[0] === 'system')
- {
- $icon = 'icon-wrench';
- }
- elseif ($parts[0] === 'help')
- {
- $icon = 'icon-info-circle';
- }
- elseif ($lang->hasKey($keyIcon))
- {
- $icon = Text::_($keyIcon);
- }
- else
- {
- $icon = 'icon-home';
- }
- }
- elseif ($lang->hasKey($keyIcon))
- {
- $icon = Text::_($keyIcon);
- }
- }
- }
- else
- {
- // Home Dashboard
- $title = Text::_('COM_CPANEL_DASHBOARD_BASE_TITLE');
- $icon = 'icon-home';
- }
-
- // Set toolbar items for the page
- ToolbarHelper::title($title, $icon . ' cpanel');
- ToolbarHelper::help('screen.cpanel');
-
- // Display the cpanel modules
- $this->position = $position ? 'cpanel-' . $position : 'cpanel';
- $this->modules = ModuleHelper::getModules($this->position);
-
- $quickicons = $position ? 'icon-' . $position : 'icon';
- $this->quickicons = ModuleHelper::getModules($quickicons);
-
- parent::display($tpl);
- }
+ /**
+ * Array of cpanel modules
+ *
+ * @var array
+ */
+ protected $modules = null;
+
+ /**
+ * Array of cpanel modules
+ *
+ * @var array
+ */
+ protected $quickicons = null;
+
+ /**
+ * Moduleposition to load
+ *
+ * @var string
+ */
+ protected $position = null;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $app = Factory::getApplication();
+ $dashboard = $app->input->getCmd('dashboard', '');
+
+ $position = ApplicationHelper::stringURLSafe($dashboard);
+
+ // Generate a title for the view cpanel
+ if (!empty($dashboard)) {
+ $parts = explode('.', $dashboard);
+ $component = $parts[0];
+
+ if (strpos($component, 'com_') === false) {
+ $component = 'com_' . $component;
+ }
+
+ // Need to load the language file
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE)
+ || $lang->load($component, JPATH_ADMINISTRATOR . '/components/' . $component);
+ $lang->load($component);
+
+ // Lookup dashboard attributes from component manifest file
+ $manifestFile = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml';
+
+ if (is_file($manifestFile)) {
+ $manifest = simplexml_load_file($manifestFile);
+
+ if ($dashboardManifests = $manifest->dashboards) {
+ foreach ($dashboardManifests->children() as $dashboardManifest) {
+ if ((string) $dashboardManifest === $dashboard) {
+ $title = Text::_((string) $dashboardManifest->attributes()->title);
+ $icon = (string) $dashboardManifest->attributes()->icon;
+
+ break;
+ }
+ }
+ }
+ }
+
+ if (empty($title)) {
+ // Try building a title
+ $prefix = strtoupper($component) . '_DASHBOARD';
+
+ $sectionkey = !empty($parts[1]) ? '_' . strtoupper($parts[1]) : '';
+ $key = $prefix . $sectionkey . '_TITLE';
+ $keyIcon = $prefix . $sectionkey . '_ICON';
+
+ // Search for a component title
+ if ($lang->hasKey($key)) {
+ $title = Text::_($key);
+ } else {
+ // Try with a string from CPanel
+ $key = 'COM_CPANEL_DASHBOARD_' . $parts[0] . '_TITLE';
+
+ if ($lang->hasKey($key)) {
+ $title = Text::_($key);
+ } else {
+ $title = Text::_('COM_CPANEL_DASHBOARD_BASE_TITLE');
+ }
+ }
+
+ // Define the icon
+ if (empty($parts[1])) {
+ // Default core icons.
+ if ($parts[0] === 'components') {
+ $icon = 'icon-puzzle-piece';
+ } elseif ($parts[0] === 'system') {
+ $icon = 'icon-wrench';
+ } elseif ($parts[0] === 'help') {
+ $icon = 'icon-info-circle';
+ } elseif ($lang->hasKey($keyIcon)) {
+ $icon = Text::_($keyIcon);
+ } else {
+ $icon = 'icon-home';
+ }
+ } elseif ($lang->hasKey($keyIcon)) {
+ $icon = Text::_($keyIcon);
+ }
+ }
+ } else {
+ // Home Dashboard
+ $title = Text::_('COM_CPANEL_DASHBOARD_BASE_TITLE');
+ $icon = 'icon-home';
+ }
+
+ // Set toolbar items for the page
+ ToolbarHelper::title($title, $icon . ' cpanel');
+ ToolbarHelper::help('screen.cpanel');
+
+ // Display the cpanel modules
+ $this->position = $position ? 'cpanel-' . $position : 'cpanel';
+ $this->modules = ModuleHelper::getModules($this->position);
+
+ $quickicons = $position ? 'icon-' . $position : 'icon';
+ $this->quickicons = ModuleHelper::getModules($quickicons);
+
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_cpanel/tmpl/cpanel/default.php b/administrator/components/com_cpanel/tmpl/cpanel/default.php
index b11b265e6138c..20d1219de7c63 100644
--- a/administrator/components/com_cpanel/tmpl/cpanel/default.php
+++ b/administrator/components/com_cpanel/tmpl/cpanel/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('com_cpanel.admin-cpanel')
- ->useScript('com_cpanel.admin-addmodule');
+ ->useScript('com_cpanel.admin-addmodule');
$user = Factory::getUser();
// Set up the bootstrap modal that will be used for all module editors
echo HTMLHelper::_(
- 'bootstrap.renderModal',
- 'moduleDashboardAddModal',
- array(
- 'title' => Text::_('COM_CPANEL_ADD_MODULE_MODAL_TITLE'),
- 'backdrop' => 'static',
- 'url' => Route::_('index.php?option=com_cpanel&task=addModule&function=jSelectModuleType&position=' . $this->escape($this->position)),
- 'bodyHeight' => '70',
- 'modalWidth' => '80',
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' ',
- )
+ 'bootstrap.renderModal',
+ 'moduleDashboardAddModal',
+ array(
+ 'title' => Text::_('COM_CPANEL_ADD_MODULE_MODAL_TITLE'),
+ 'backdrop' => 'static',
+ 'url' => Route::_('index.php?option=com_cpanel&task=addModule&function=jSelectModuleType&position=' . $this->escape($this->position)),
+ 'bodyHeight' => '70',
+ 'modalWidth' => '80',
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' ',
+ )
);
?>
-
-
- quickicons) :
- foreach ($this->quickicons as $iconmodule)
- {
- echo ModuleHelper::renderModule($iconmodule, array('style' => 'well'));
- }
- endif;
- foreach ($this->modules as $module)
- {
- echo ModuleHelper::renderModule($module, array('style' => 'well'));
- }
- ?>
- authorise('core.create', 'com_modules')) : ?>
-
-
-
-
+
+
+ quickicons) :
+ foreach ($this->quickicons as $iconmodule) {
+ echo ModuleHelper::renderModule($iconmodule, array('style' => 'well'));
+ }
+ endif;
+ foreach ($this->modules as $module) {
+ echo ModuleHelper::renderModule($module, array('style' => 'well'));
+ }
+ ?>
+ authorise('core.create', 'com_modules')) : ?>
+
+
+
+
diff --git a/administrator/components/com_fields/helpers/fields.php b/administrator/components/com_fields/helpers/fields.php
index 81d9091d5f76a..bdb004a98a5b0 100644
--- a/administrator/components/com_fields/helpers/fields.php
+++ b/administrator/components/com_fields/helpers/fields.php
@@ -1,12 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
+
/**
* FieldsHelper
diff --git a/administrator/components/com_fields/services/provider.php b/administrator/components/com_fields/services/provider.php
index 1628402593709..bb7fc5dce97b2 100644
--- a/administrator/components/com_fields/services/provider.php
+++ b/administrator/components/com_fields/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Fields'));
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Fields'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Fields'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Fields'));
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Fields'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Fields'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new FieldsComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new FieldsComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_fields/src/Controller/DisplayController.php b/administrator/components/com_fields/src/Controller/DisplayController.php
index 97099ec795850..34d351f15ff91 100644
--- a/administrator/components/com_fields/src/Controller/DisplayController.php
+++ b/administrator/components/com_fields/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'fields');
- $id = $this->input->getInt('id');
+ /**
+ * Typical view method for MVC based architecture
+ *
+ * This function is provide as a default implementation, in most cases
+ * you will need to override it in your own controllers.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array|bool $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}
+ *
+ * @return BaseController|boolean A Controller object to support chaining.
+ *
+ * @since 3.7.0
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ // Set the default view name and format from the Request.
+ $vName = $this->input->get('view', 'fields');
+ $id = $this->input->getInt('id');
- // Check for edit form.
- if ($vName == 'field' && !$this->checkEditId('com_fields.edit.field', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($vName == 'field' && !$this->checkEditId('com_fields.edit.field', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_fields&view=fields&context=' . $this->input->get('context'), false));
+ $this->setRedirect(Route::_('index.php?option=com_fields&view=fields&context=' . $this->input->get('context'), false));
- return false;
- }
+ return false;
+ }
- return parent::display($cachable, $urlparams);
- }
+ return parent::display($cachable, $urlparams);
+ }
}
diff --git a/administrator/components/com_fields/src/Controller/FieldController.php b/administrator/components/com_fields/src/Controller/FieldController.php
index b0681ddb67185..584022cbf05f9 100644
--- a/administrator/components/com_fields/src/Controller/FieldController.php
+++ b/administrator/components/com_fields/src/Controller/FieldController.php
@@ -1,4 +1,5 @@
internalContext = $this->app->getUserStateFromRequest('com_fields.fields.context', 'context', 'com_content.article', 'CMD');
- $parts = FieldsHelper::extract($this->internalContext);
- $this->component = $parts ? $parts[0] : null;
- }
-
- /**
- * Method override to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', $this->component);
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- // Zero record (id:0), return component edit permission by calling parent controller method
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Check edit on the record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->component . '.field.' . $recordId))
- {
- return true;
- }
-
- // Check edit own on the record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->component . '.field.' . $recordId))
- {
- // Existing record already has an owner, get it
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record))
- {
- return false;
- }
-
- // Grant if current user is owner of the record
- return $user->id == $record->created_user_id;
- }
-
- return false;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 3.7.0
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('Field');
-
- // Preset the redirect
- $this->setRedirect('index.php?option=com_fields&view=fields&context=' . $this->internalContext);
-
- return parent::batch($model);
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 3.7.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- return parent::getRedirectToItemAppend($recordId) . '&context=' . $this->internalContext;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 3.7.0
- */
- protected function getRedirectToListAppend()
- {
- return parent::getRedirectToListAppend() . '&context=' . $this->internalContext;
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- $item = $model->getItem();
-
- if (isset($item->params) && is_array($item->params))
- {
- $registry = new Registry;
- $registry->loadArray($item->params);
- $item->params = (string) $registry;
- }
- }
+ /**
+ * @var string
+ */
+ private $internalContext;
+
+ /**
+ * @var string
+ */
+ private $component;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+
+ * @since 3.7.0
+ */
+ protected $text_prefix = 'COM_FIELDS_FIELD';
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.7.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->internalContext = $this->app->getUserStateFromRequest('com_fields.fields.context', 'context', 'com_content.article', 'CMD');
+ $parts = FieldsHelper::extract($this->internalContext);
+ $this->component = $parts ? $parts[0] : null;
+ }
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', $this->component);
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ // Zero record (id:0), return component edit permission by calling parent controller method
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Check edit on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->component . '.field.' . $recordId)) {
+ return true;
+ }
+
+ // Check edit own on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->component . '.field.' . $recordId)) {
+ // Existing record already has an owner, get it
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record)) {
+ return false;
+ }
+
+ // Grant if current user is owner of the record
+ return $user->id == $record->created_user_id;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 3.7.0
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('Field');
+
+ // Preset the redirect
+ $this->setRedirect('index.php?option=com_fields&view=fields&context=' . $this->internalContext);
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 3.7.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ return parent::getRedirectToItemAppend($recordId) . '&context=' . $this->internalContext;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 3.7.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ return parent::getRedirectToListAppend() . '&context=' . $this->internalContext;
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ $item = $model->getItem();
+
+ if (isset($item->params) && is_array($item->params)) {
+ $registry = new Registry();
+ $registry->loadArray($item->params);
+ $item->params = (string) $registry;
+ }
+ }
}
diff --git a/administrator/components/com_fields/src/Controller/FieldsController.php b/administrator/components/com_fields/src/Controller/FieldsController.php
index 9d7ad0c849212..366c2a2d81225 100644
--- a/administrator/components/com_fields/src/Controller/FieldsController.php
+++ b/administrator/components/com_fields/src/Controller/FieldsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model.
+ * @param string $prefix The prefix for the PHP class name.
+ * @param array $config Array of configuration parameters.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ *
+ * @since 3.7.0
+ */
+ public function getModel($name = 'Field', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_fields/src/Controller/GroupController.php b/administrator/components/com_fields/src/Controller/GroupController.php
index 1c644954aeed5..ec5ea578d8a50 100644
--- a/administrator/components/com_fields/src/Controller/GroupController.php
+++ b/administrator/components/com_fields/src/Controller/GroupController.php
@@ -1,4 +1,5 @@
input->getCmd('context'));
-
- if ($parts)
- {
- $this->component = $parts[0];
- }
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 3.7.0
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('Group');
-
- // Preset the redirect
- $this->setRedirect('index.php?option=com_fields&view=groups');
-
- return parent::batch($model);
- }
-
- /**
- * Method override to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', $this->component);
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- protected function allowEdit($data = array(), $key = 'parent_id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- // Zero record (parent_id:0), return component edit permission by calling parent controller method
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Check edit on the record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->component . '.fieldgroup.' . $recordId))
- {
- return true;
- }
-
- // Check edit own on the record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->component . '.fieldgroup.' . $recordId) || $user->authorise('core.edit.own', $this->component))
- {
- // Existing record already has an owner, get it
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record))
- {
- return false;
- }
-
- // Grant if current user is owner of the record
- return $user->id == $record->created_by;
- }
-
- return false;
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- $item = $model->getItem();
-
- if (isset($item->params) && is_array($item->params))
- {
- $registry = new Registry;
- $registry->loadArray($item->params);
- $item->params = (string) $registry;
- }
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
- $append .= '&context=' . $this->input->get('context');
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&context=' . $this->input->get('context');
-
- return $append;
- }
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+
+ * @since 3.7.0
+ */
+ protected $text_prefix = 'COM_FIELDS_GROUP';
+
+ /**
+ * The component for which the group applies.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ private $component = '';
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 3.7.0
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $parts = FieldsHelper::extract($this->input->getCmd('context'));
+
+ if ($parts) {
+ $this->component = $parts[0];
+ }
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 3.7.0
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('Group');
+
+ // Preset the redirect
+ $this->setRedirect('index.php?option=com_fields&view=groups');
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', $this->component);
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ protected function allowEdit($data = array(), $key = 'parent_id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ // Zero record (parent_id:0), return component edit permission by calling parent controller method
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Check edit on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->component . '.fieldgroup.' . $recordId)) {
+ return true;
+ }
+
+ // Check edit own on the record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->component . '.fieldgroup.' . $recordId) || $user->authorise('core.edit.own', $this->component)) {
+ // Existing record already has an owner, get it
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record)) {
+ return false;
+ }
+
+ // Grant if current user is owner of the record
+ return $user->id == $record->created_by;
+ }
+
+ return false;
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ $item = $model->getItem();
+
+ if (isset($item->params) && is_array($item->params)) {
+ $registry = new Registry();
+ $registry->loadArray($item->params);
+ $item->params = (string) $registry;
+ }
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+ $append .= '&context=' . $this->input->get('context');
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&context=' . $this->input->get('context');
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_fields/src/Controller/GroupsController.php b/administrator/components/com_fields/src/Controller/GroupsController.php
index 247217bb79571..7d0b0d4a06e41 100644
--- a/administrator/components/com_fields/src/Controller/GroupsController.php
+++ b/administrator/components/com_fields/src/Controller/GroupsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model.
+ * @param string $prefix The prefix for the PHP class name.
+ * @param array $config Array of configuration parameters.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ *
+ * @since 3.7.0
+ */
+ public function getModel($name = 'Group', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_fields/src/Dispatcher/Dispatcher.php b/administrator/components/com_fields/src/Dispatcher/Dispatcher.php
index ba4e8515ae1b3..e90dbd5efb511 100644
--- a/administrator/components/com_fields/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_fields/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
app->getUserStateFromRequest(
- 'com_fields.groups.context',
- 'context',
- $this->app->getUserStateFromRequest('com_fields.fields.context', 'context', 'com_content.article', 'CMD'),
- 'CMD'
- );
+ /**
+ * Method to check component access permission
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ */
+ protected function checkAccess()
+ {
+ $context = $this->app->getUserStateFromRequest(
+ 'com_fields.groups.context',
+ 'context',
+ $this->app->getUserStateFromRequest('com_fields.fields.context', 'context', 'com_content.article', 'CMD'),
+ 'CMD'
+ );
- $parts = FieldsHelper::extract($context);
+ $parts = FieldsHelper::extract($context);
- if (!$parts || !$this->app->getIdentity()->authorise('core.manage', $parts[0]))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ if (!$parts || !$this->app->getIdentity()->authorise('core.manage', $parts[0])) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_fields/src/Extension/FieldsComponent.php b/administrator/components/com_fields/src/Extension/FieldsComponent.php
index 68fa6b3bc9fb2..3c8b6ce261939 100644
--- a/administrator/components/com_fields/src/Extension/FieldsComponent.php
+++ b/administrator/components/com_fields/src/Extension/FieldsComponent.php
@@ -1,4 +1,5 @@
getDatabase();
-
- $query = $db->getQuery(true)
- ->select('DISTINCT a.name AS text, a.element AS value')
- ->from('#__extensions as a')
- ->where('a.enabled >= 1')
- ->where('a.type =' . $db->quote('component'));
-
- $items = $db->setQuery($query)->loadObjectList();
-
- $options = [];
-
- if (count($items))
- {
- $lang = Factory::getLanguage();
-
- $components = [];
-
- // Search for components supporting Fieldgroups - suppose that these components support fields as well
- foreach ($items as &$item)
- {
- $availableActions = Access::getActionsFromFile(
- JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
- "/access/section[@name='fieldgroup']/"
- );
-
- if (!empty($availableActions))
- {
- // Load language
- $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
- $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
- || $lang->load($item->value . 'sys', $source);
-
- // Translate component name
- $item->text = Text::_($item->text);
-
- $components[] = $item;
- }
- }
-
- if (empty($components))
- {
- return [];
- }
-
- foreach ($components as $component)
- {
- // Search for different contexts
- $c = Factory::getApplication()->bootComponent($component->value);
-
- if ($c instanceof FieldsServiceInterface)
- {
- $contexts = $c->getContexts();
-
- foreach ($contexts as $context)
- {
- $newOption = new \stdClass;
- $newOption->value = strtolower($component->value . '.' . $context);
- $newOption->text = $component->text . ' - ' . Text::_($context);
- $options[] = $newOption;
- }
- }
- else
- {
- $options[] = $component;
- }
- }
-
- // Sort by name
- $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $items);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'ComponentsFieldgroup';
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ // Initialise variable.
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select('DISTINCT a.name AS text, a.element AS value')
+ ->from('#__extensions as a')
+ ->where('a.enabled >= 1')
+ ->where('a.type =' . $db->quote('component'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ $options = [];
+
+ if (count($items)) {
+ $lang = Factory::getLanguage();
+
+ $components = [];
+
+ // Search for components supporting Fieldgroups - suppose that these components support fields as well
+ foreach ($items as &$item) {
+ $availableActions = Access::getActionsFromFile(
+ JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
+ "/access/section[@name='fieldgroup']/"
+ );
+
+ if (!empty($availableActions)) {
+ // Load language
+ $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
+ $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
+ || $lang->load($item->value . 'sys', $source);
+
+ // Translate component name
+ $item->text = Text::_($item->text);
+
+ $components[] = $item;
+ }
+ }
+
+ if (empty($components)) {
+ return [];
+ }
+
+ foreach ($components as $component) {
+ // Search for different contexts
+ $c = Factory::getApplication()->bootComponent($component->value);
+
+ if ($c instanceof FieldsServiceInterface) {
+ $contexts = $c->getContexts();
+
+ foreach ($contexts as $context) {
+ $newOption = new \stdClass();
+ $newOption->value = strtolower($component->value . '.' . $context);
+ $newOption->text = $component->text . ' - ' . Text::_($context);
+ $options[] = $newOption;
+ }
+ } else {
+ $options[] = $component;
+ }
+ }
+
+ // Sort by name
+ $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $items);
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_fields/src/Field/ComponentsFieldsField.php b/administrator/components/com_fields/src/Field/ComponentsFieldsField.php
index b55336b2316e3..358af76a35fd9 100644
--- a/administrator/components/com_fields/src/Field/ComponentsFieldsField.php
+++ b/administrator/components/com_fields/src/Field/ComponentsFieldsField.php
@@ -1,4 +1,5 @@
getDatabase();
-
- $query = $db->getQuery(true)
- ->select('DISTINCT a.name AS text, a.element AS value')
- ->from('#__extensions as a')
- ->where('a.enabled >= 1')
- ->where('a.type =' . $db->quote('component'));
-
- $items = $db->setQuery($query)->loadObjectList();
-
- $options = [];
-
- if (count($items))
- {
- $lang = Factory::getLanguage();
-
- $components = [];
-
- // Search for components supporting Fieldgroups - suppose that these components support fields as well
- foreach ($items as &$item)
- {
- $availableActions = Access::getActionsFromFile(
- JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
- "/access/section[@name='fieldgroup']/"
- );
-
- if (!empty($availableActions))
- {
- // Load language
- $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
- $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
- || $lang->load($item->value . 'sys', $source);
-
- // Translate component name
- $item->text = Text::_($item->text);
-
- $components[] = $item;
- }
- }
-
- if (empty($components))
- {
- return [];
- }
-
- foreach ($components as $component)
- {
- // Search for different contexts
- $c = Factory::getApplication()->bootComponent($component->value);
-
- if ($c instanceof FieldsServiceInterface)
- {
- $contexts = $c->getContexts();
-
- foreach ($contexts as $context)
- {
- $newOption = new \stdClass;
- $newOption->value = strtolower($component->value . '.' . $context);
- $newOption->text = $component->text . ' - ' . Text::_($context);
- $options[] = $newOption;
- }
- }
- else
- {
- $options[] = $component;
- }
- }
-
- // Sort by name
- $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $items);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'ComponentsFields';
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ // Initialise variable.
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select('DISTINCT a.name AS text, a.element AS value')
+ ->from('#__extensions as a')
+ ->where('a.enabled >= 1')
+ ->where('a.type =' . $db->quote('component'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ $options = [];
+
+ if (count($items)) {
+ $lang = Factory::getLanguage();
+
+ $components = [];
+
+ // Search for components supporting Fieldgroups - suppose that these components support fields as well
+ foreach ($items as &$item) {
+ $availableActions = Access::getActionsFromFile(
+ JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
+ "/access/section[@name='fieldgroup']/"
+ );
+
+ if (!empty($availableActions)) {
+ // Load language
+ $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
+ $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
+ || $lang->load($item->value . 'sys', $source);
+
+ // Translate component name
+ $item->text = Text::_($item->text);
+
+ $components[] = $item;
+ }
+ }
+
+ if (empty($components)) {
+ return [];
+ }
+
+ foreach ($components as $component) {
+ // Search for different contexts
+ $c = Factory::getApplication()->bootComponent($component->value);
+
+ if ($c instanceof FieldsServiceInterface) {
+ $contexts = $c->getContexts();
+
+ foreach ($contexts as $context) {
+ $newOption = new \stdClass();
+ $newOption->value = strtolower($component->value . '.' . $context);
+ $newOption->text = $component->text . ' - ' . Text::_($context);
+ $options[] = $newOption;
+ }
+ } else {
+ $options[] = $component;
+ }
+ }
+
+ // Sort by name
+ $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $items);
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_fields/src/Field/FieldLayoutField.php b/administrator/components/com_fields/src/Field/FieldLayoutField.php
index bf45f58c8107c..74c191aecf2e4 100644
--- a/administrator/components/com_fields/src/Field/FieldLayoutField.php
+++ b/administrator/components/com_fields/src/Field/FieldLayoutField.php
@@ -1,4 +1,5 @@
form->getValue('context'));
- $extension = $extension[0];
-
- if ($extension)
- {
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select('element, name')
- ->from('#__extensions')
- ->where('client_id = 0')
- ->where('type = ' . $db->quote('template'))
- ->where('enabled = 1');
-
- // Set the query and load the templates.
- $db->setQuery($query);
- $templates = $db->loadObjectList('element');
-
- // Build the search paths for component layouts.
- $component_path = Path::clean(JPATH_SITE . '/components/' . $extension . '/layouts/field');
-
- // Prepare array of component layouts
- $component_layouts = array();
-
- // Prepare the grouped list
- $groups = array();
-
- // Add "Use Default"
- $groups[]['items'][] = HTMLHelper::_('select.option', '', Text::_('JOPTION_USE_DEFAULT'));
-
- // Add the layout options from the component path.
- if (is_dir($component_path) && ($component_layouts = Folder::files($component_path, '^[^_]*\.php$', false, true)))
- {
- // Create the group for the component
- $groups['_'] = array();
- $groups['_']['id'] = $this->id . '__';
- $groups['_']['text'] = Text::sprintf('JOPTION_FROM_COMPONENT');
- $groups['_']['items'] = array();
-
- foreach ($component_layouts as $i => $file)
- {
- // Add an option to the component group
- $value = basename($file, '.php');
- $component_layouts[$i] = $value;
-
- if ($value === 'render')
- {
- continue;
- }
-
- $groups['_']['items'][] = HTMLHelper::_('select.option', $value, $value);
- }
- }
-
- // Loop on all templates
- if ($templates)
- {
- foreach ($templates as $template)
- {
- $files = array();
- $template_paths = array(
- Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/' . $extension . '/field'),
- Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/com_fields/field'),
- Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/field'),
- );
-
- // Add the layout options from the template paths.
- foreach ($template_paths as $template_path)
- {
- if (is_dir($template_path))
- {
- $files = array_merge($files, Folder::files($template_path, '^[^_]*\.php$', false, true));
- }
- }
-
- foreach ($files as $i => $file)
- {
- $value = basename($file, '.php');
-
- // Remove the default "render.php" or layout files that exist in the component folder
- if ($value === 'render' || in_array($value, $component_layouts))
- {
- unset($files[$i]);
- }
- }
-
- if (count($files))
- {
- // Create the group for the template
- $groups[$template->name] = array();
- $groups[$template->name]['id'] = $this->id . '_' . $template->element;
- $groups[$template->name]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
- $groups[$template->name]['items'] = array();
-
- foreach ($files as $file)
- {
- // Add an option to the template group
- $value = basename($file, '.php');
- $groups[$template->name]['items'][] = HTMLHelper::_('select.option', $value, $value);
- }
- }
- }
- }
-
- // Compute attributes for the grouped list
- $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
- $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
-
- // Prepare HTML code
- $html = array();
-
- // Compute the current selected values
- $selected = array($this->value);
-
- // Add a grouped list
- $html[] = HTMLHelper::_(
- 'select.groupedlist', $groups, $this->name,
- array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
- );
-
- return implode($html);
- }
-
- return '';
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.9.0
+ */
+ protected $type = 'FieldLayout';
+
+ /**
+ * Method to get the field input for a field layout field.
+ *
+ * @return string The field input.
+ *
+ * @since 3.9.0
+ */
+ protected function getInput()
+ {
+ $extension = explode('.', $this->form->getValue('context'));
+ $extension = $extension[0];
+
+ if ($extension) {
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select('element, name')
+ ->from('#__extensions')
+ ->where('client_id = 0')
+ ->where('type = ' . $db->quote('template'))
+ ->where('enabled = 1');
+
+ // Set the query and load the templates.
+ $db->setQuery($query);
+ $templates = $db->loadObjectList('element');
+
+ // Build the search paths for component layouts.
+ $component_path = Path::clean(JPATH_SITE . '/components/' . $extension . '/layouts/field');
+
+ // Prepare array of component layouts
+ $component_layouts = array();
+
+ // Prepare the grouped list
+ $groups = array();
+
+ // Add "Use Default"
+ $groups[]['items'][] = HTMLHelper::_('select.option', '', Text::_('JOPTION_USE_DEFAULT'));
+
+ // Add the layout options from the component path.
+ if (is_dir($component_path) && ($component_layouts = Folder::files($component_path, '^[^_]*\.php$', false, true))) {
+ // Create the group for the component
+ $groups['_'] = array();
+ $groups['_']['id'] = $this->id . '__';
+ $groups['_']['text'] = Text::sprintf('JOPTION_FROM_COMPONENT');
+ $groups['_']['items'] = array();
+
+ foreach ($component_layouts as $i => $file) {
+ // Add an option to the component group
+ $value = basename($file, '.php');
+ $component_layouts[$i] = $value;
+
+ if ($value === 'render') {
+ continue;
+ }
+
+ $groups['_']['items'][] = HTMLHelper::_('select.option', $value, $value);
+ }
+ }
+
+ // Loop on all templates
+ if ($templates) {
+ foreach ($templates as $template) {
+ $files = array();
+ $template_paths = array(
+ Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/' . $extension . '/field'),
+ Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/com_fields/field'),
+ Path::clean(JPATH_SITE . '/templates/' . $template->element . '/html/layouts/field'),
+ );
+
+ // Add the layout options from the template paths.
+ foreach ($template_paths as $template_path) {
+ if (is_dir($template_path)) {
+ $files = array_merge($files, Folder::files($template_path, '^[^_]*\.php$', false, true));
+ }
+ }
+
+ foreach ($files as $i => $file) {
+ $value = basename($file, '.php');
+
+ // Remove the default "render.php" or layout files that exist in the component folder
+ if ($value === 'render' || in_array($value, $component_layouts)) {
+ unset($files[$i]);
+ }
+ }
+
+ if (count($files)) {
+ // Create the group for the template
+ $groups[$template->name] = array();
+ $groups[$template->name]['id'] = $this->id . '_' . $template->element;
+ $groups[$template->name]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
+ $groups[$template->name]['items'] = array();
+
+ foreach ($files as $file) {
+ // Add an option to the template group
+ $value = basename($file, '.php');
+ $groups[$template->name]['items'][] = HTMLHelper::_('select.option', $value, $value);
+ }
+ }
+ }
+ }
+
+ // Compute attributes for the grouped list
+ $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
+ $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
+
+ // Prepare HTML code
+ $html = array();
+
+ // Compute the current selected values
+ $selected = array($this->value);
+
+ // Add a grouped list
+ $html[] = HTMLHelper::_(
+ 'select.groupedlist',
+ $groups,
+ $this->name,
+ array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
+ );
+
+ return implode($html);
+ }
+
+ return '';
+ }
}
diff --git a/administrator/components/com_fields/src/Field/FieldcontextsField.php b/administrator/components/com_fields/src/Field/FieldcontextsField.php
index 3f223313bad9b..00d2d91c246ff 100644
--- a/administrator/components/com_fields/src/Field/FieldcontextsField.php
+++ b/administrator/components/com_fields/src/Field/FieldcontextsField.php
@@ -1,4 +1,5 @@
getOptions() ? parent::getInput() : '';
- }
-
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.7.0
- */
- protected function getOptions()
- {
- $parts = explode('.', $this->value);
-
- $component = Factory::getApplication()->bootComponent($parts[0]);
-
- if ($component instanceof FieldsServiceInterface)
- {
- return $component->getContexts();
- }
-
- return [];
- }
+ /**
+ * Type of the field
+ *
+ * @var string
+ */
+ public $type = 'Fieldcontexts';
+
+ /**
+ * Method to get the field input markup for a generic list.
+ * Use the multiple attribute to enable multiselect.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getInput()
+ {
+ return $this->getOptions() ? parent::getInput() : '';
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $parts = explode('.', $this->value);
+
+ $component = Factory::getApplication()->bootComponent($parts[0]);
+
+ if ($component instanceof FieldsServiceInterface) {
+ return $component->getContexts();
+ }
+
+ return [];
+ }
}
diff --git a/administrator/components/com_fields/src/Field/FieldgroupsField.php b/administrator/components/com_fields/src/Field/FieldgroupsField.php
index c8a8b24746f68..28cac368bc6ef 100644
--- a/administrator/components/com_fields/src/Field/FieldgroupsField.php
+++ b/administrator/components/com_fields/src/Field/FieldgroupsField.php
@@ -1,4 +1,5 @@
element['context'];
- $states = $this->element['state'] ?: '0,1';
- $states = ArrayHelper::toInteger(explode(',', $states));
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $context = (string) $this->element['context'];
+ $states = $this->element['state'] ?: '0,1';
+ $states = ArrayHelper::toInteger(explode(',', $states));
- $user = Factory::getUser();
- $viewlevels = ArrayHelper::toInteger($user->getAuthorisedViewLevels());
+ $user = Factory::getUser();
+ $viewlevels = ArrayHelper::toInteger($user->getAuthorisedViewLevels());
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('title', 'text'),
- $db->quoteName('id', 'value'),
- $db->quoteName('state'),
- ]
- );
- $query->from($db->quoteName('#__fields_groups'));
- $query->whereIn($db->quoteName('state'), $states);
- $query->where($db->quoteName('context') . ' = :context');
- $query->whereIn($db->quoteName('access'), $viewlevels);
- $query->order('ordering asc, id asc');
- $query->bind(':context', $context);
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('title', 'text'),
+ $db->quoteName('id', 'value'),
+ $db->quoteName('state'),
+ ]
+ );
+ $query->from($db->quoteName('#__fields_groups'));
+ $query->whereIn($db->quoteName('state'), $states);
+ $query->where($db->quoteName('context') . ' = :context');
+ $query->whereIn($db->quoteName('access'), $viewlevels);
+ $query->order('ordering asc, id asc');
+ $query->bind(':context', $context);
- $db->setQuery($query);
- $options = $db->loadObjectList();
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
- foreach ($options AS $option)
- {
- if ($option->state == 0)
- {
- $option->text = '[' . $option->text . ']';
- }
+ foreach ($options as $option) {
+ if ($option->state == 0) {
+ $option->text = '[' . $option->text . ']';
+ }
- if ($option->state == 2)
- {
- $option->text = '{' . $option->text . '}';
- }
- }
+ if ($option->state == 2) {
+ $option->text = '{' . $option->text . '}';
+ }
+ }
- return array_merge(parent::getOptions(), $options);
- }
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_fields/src/Field/SectionField.php b/administrator/components/com_fields/src/Field/SectionField.php
index 2c3549c7b1a3e..651bcecafd826 100644
--- a/administrator/components/com_fields/src/Field/SectionField.php
+++ b/administrator/components/com_fields/src/Field/SectionField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
- // Onchange must always be the change context function
- $this->onchange = 'Joomla.fieldsChangeContext(this.value);';
+ // Onchange must always be the change context function
+ $this->onchange = 'Joomla.fieldsChangeContext(this.value);';
- return $return;
- }
+ return $return;
+ }
- /**
- * Method to get the field input markup for a generic list.
- * Use the multiple attribute to enable multiselect.
- *
- * @return string The field input markup.
- *
- * @since 3.7.0
- */
- protected function getInput()
- {
- Factory::getApplication()->getDocument()->getWebAssetManager()
- ->useScript('com_fields.admin-field-changecontext');
+ /**
+ * Method to get the field input markup for a generic list.
+ * Use the multiple attribute to enable multiselect.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getInput()
+ {
+ Factory::getApplication()->getDocument()->getWebAssetManager()
+ ->useScript('com_fields.admin-field-changecontext');
- return parent::getInput();
- }
+ return parent::getInput();
+ }
}
diff --git a/administrator/components/com_fields/src/Field/SubfieldsField.php b/administrator/components/com_fields/src/Field/SubfieldsField.php
index 1640d6cb1cb58..330fa46e25865 100644
--- a/administrator/components/com_fields/src/Field/SubfieldsField.php
+++ b/administrator/components/com_fields/src/Field/SubfieldsField.php
@@ -1,4 +1,5 @@
context]))
- {
- static::$customFieldsCache[$this->context] = FieldsHelper::getFields($this->context, null, false, null, true);
- }
-
- // Iterate over the custom fields for this context
- foreach (static::$customFieldsCache[$this->context] as $customField)
- {
- // Skip our own subform type. We won't have subform in subform.
- if ($customField->type == 'subform')
- {
- continue;
- }
-
- $options[] = HTMLHelper::_(
- 'select.option',
- $customField->id,
- ($customField->title . ' (' . $customField->type . ')')
- );
- }
-
- // Sorting the fields based on the text which is displayed
- usort(
- $options,
- function ($a, $b)
- {
- return strcmp($a->text, $b->text);
- }
- );
-
- if (count($options) == 0)
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_FIELDS_NO_FIELDS_TO_CREATE_SUBFORM_FIELD_WARNING'), 'warning');
- }
-
- return $options;
- }
-
- /**
- * Method to attach a JForm object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->context = (string) $this->element['context'];
- }
-
- return $return;
- }
+ /**
+ * The name of this Field type.
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ public $type = 'Subfields';
+
+ /**
+ * Configuration option for this field type to could filter the displayed custom field instances
+ * by a given context. Default value empty string. If given empty string, displays all custom fields.
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ protected $context = '';
+
+ /**
+ * Array to do a fast in-memory caching of all custom field items. Used to not bother the
+ * FieldsHelper with a call every time this field is being rendered.
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected static $customFieldsCache = array();
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 4.0.0
+ */
+ protected function getOptions()
+ {
+ $options = parent::getOptions();
+
+ // Check whether we have a result for this context yet
+ if (!isset(static::$customFieldsCache[$this->context])) {
+ static::$customFieldsCache[$this->context] = FieldsHelper::getFields($this->context, null, false, null, true);
+ }
+
+ // Iterate over the custom fields for this context
+ foreach (static::$customFieldsCache[$this->context] as $customField) {
+ // Skip our own subform type. We won't have subform in subform.
+ if ($customField->type == 'subform') {
+ continue;
+ }
+
+ $options[] = HTMLHelper::_(
+ 'select.option',
+ $customField->id,
+ ($customField->title . ' (' . $customField->type . ')')
+ );
+ }
+
+ // Sorting the fields based on the text which is displayed
+ usort(
+ $options,
+ function ($a, $b) {
+ return strcmp($a->text, $b->text);
+ }
+ );
+
+ if (count($options) == 0) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_FIELDS_NO_FIELDS_TO_CREATE_SUBFORM_FIELD_WARNING'), 'warning');
+ }
+
+ return $options;
+ }
+
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->context = (string) $this->element['context'];
+ }
+
+ return $return;
+ }
}
diff --git a/administrator/components/com_fields/src/Field/TypeField.php b/administrator/components/com_fields/src/Field/TypeField.php
index d48b1aefa780d..760a598d84694 100644
--- a/administrator/components/com_fields/src/Field/TypeField.php
+++ b/administrator/components/com_fields/src/Field/TypeField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
- $this->onchange = 'Joomla.typeHasChanged(this);';
+ $this->onchange = 'Joomla.typeHasChanged(this);';
- return $return;
- }
+ return $return;
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.7.0
- */
- protected function getOptions()
- {
- $options = parent::getOptions();
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $options = parent::getOptions();
- $fieldTypes = FieldsHelper::getFieldTypes();
+ $fieldTypes = FieldsHelper::getFieldTypes();
- foreach ($fieldTypes as $fieldType)
- {
- $options[] = HTMLHelper::_('select.option', $fieldType['type'], $fieldType['label']);
- }
+ foreach ($fieldTypes as $fieldType) {
+ $options[] = HTMLHelper::_('select.option', $fieldType['type'], $fieldType['label']);
+ }
- // Sorting the fields based on the text which is displayed
- usort(
- $options,
- function ($a, $b)
- {
- return strcmp($a->text, $b->text);
- }
- );
+ // Sorting the fields based on the text which is displayed
+ usort(
+ $options,
+ function ($a, $b) {
+ return strcmp($a->text, $b->text);
+ }
+ );
- // Load scripts
- Factory::getApplication()->getDocument()->getWebAssetManager()
- ->useScript('com_fields.admin-field-typehaschanged')
- ->useScript('webcomponent.core-loader');
+ // Load scripts
+ Factory::getApplication()->getDocument()->getWebAssetManager()
+ ->useScript('com_fields.admin-field-typehaschanged')
+ ->useScript('webcomponent.core-loader');
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_fields/src/Helper/FieldsHelper.php b/administrator/components/com_fields/src/Helper/FieldsHelper.php
index e2f39866ab86b..d0ba28d4f5b13 100644
--- a/administrator/components/com_fields/src/Helper/FieldsHelper.php
+++ b/administrator/components/com_fields/src/Helper/FieldsHelper.php
@@ -1,4 +1,5 @@
bootComponent($parts[0]);
-
- if ($component instanceof FieldsServiceInterface)
- {
- $newSection = $component->validateSection($parts[1], $item);
- }
-
- if ($newSection)
- {
- $parts[1] = $newSection;
- }
-
- return $parts;
- }
-
- /**
- * Returns the fields for the given context.
- * If the item is an object the returned fields do have an additional field
- * "value" which represents the value for the given item. If the item has an
- * assigned_cat_ids field, then additionally fields which belong to that
- * category will be returned.
- * Should the value being prepared to be shown in an HTML context then
- * prepareValue must be set to true. No further escaping needs to be done.
- * The values of the fields can be overridden by an associative array where the keys
- * have to be a name and its corresponding value.
- *
- * @param string $context The context of the content passed to the helper
- * @param null $item The item being edited in the form
- * @param int|bool $prepareValue (if int is display event): 1 - AfterTitle, 2 - BeforeDisplay, 3 - AfterDisplay, 0 - OFF
- * @param array|null $valuesToOverride The values to override
- * @param bool $includeSubformFields Should I include fields marked as Only Use In Subform?
- *
- * @return array
- *
- * @throws \Exception
- * @since 3.7.0
- */
- public static function getFields(
- $context, $item = null, $prepareValue = false, array $valuesToOverride = null, bool $includeSubformFields = false
- )
- {
- if (self::$fieldsCache === null)
- {
- // Load the model
- self::$fieldsCache = Factory::getApplication()->bootComponent('com_fields')
- ->getMVCFactory()->createModel('Fields', 'Administrator', ['ignore_request' => true]);
-
- self::$fieldsCache->setState('filter.state', 1);
- self::$fieldsCache->setState('list.limit', 0);
- }
-
- if ($includeSubformFields)
- {
- self::$fieldsCache->setState('filter.only_use_in_subform', '');
- }
- else
- {
- self::$fieldsCache->setState('filter.only_use_in_subform', 0);
- }
-
- if (is_array($item))
- {
- $item = (object) $item;
- }
-
- if (Multilanguage::isEnabled() && isset($item->language) && $item->language != '*')
- {
- self::$fieldsCache->setState('filter.language', array('*', $item->language));
- }
-
- self::$fieldsCache->setState('filter.context', $context);
- self::$fieldsCache->setState('filter.assigned_cat_ids', array());
-
- /*
- * If item has assigned_cat_ids parameter display only fields which
- * belong to the category
- */
- if ($item && (isset($item->catid) || isset($item->fieldscatid)))
- {
- $assignedCatIds = $item->catid ?? $item->fieldscatid;
-
- if (!is_array($assignedCatIds))
- {
- $assignedCatIds = explode(',', $assignedCatIds);
- }
-
- // Fields without any category assigned should show as well
- $assignedCatIds[] = 0;
-
- self::$fieldsCache->setState('filter.assigned_cat_ids', $assignedCatIds);
- }
-
- $fields = self::$fieldsCache->getItems();
-
- if ($fields === false)
- {
- return array();
- }
-
- if ($item && isset($item->id))
- {
- if (self::$fieldCache === null)
- {
- self::$fieldCache = Factory::getApplication()->bootComponent('com_fields')
- ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
- }
-
- $fieldIds = array_map(
- function ($f)
- {
- return $f->id;
- },
- $fields
- );
-
- $fieldValues = self::$fieldCache->getFieldValues($fieldIds, $item->id);
-
- $new = array();
-
- foreach ($fields as $key => $original)
- {
- /*
- * Doing a clone, otherwise fields for different items will
- * always reference to the same object
- */
- $field = clone $original;
-
- if ($valuesToOverride && array_key_exists($field->name, $valuesToOverride))
- {
- $field->value = $valuesToOverride[$field->name];
- }
- elseif ($valuesToOverride && array_key_exists($field->id, $valuesToOverride))
- {
- $field->value = $valuesToOverride[$field->id];
- }
- elseif (array_key_exists($field->id, $fieldValues))
- {
- $field->value = $fieldValues[$field->id];
- }
-
- if (!isset($field->value) || $field->value === '')
- {
- $field->value = $field->default_value;
- }
-
- $field->rawvalue = $field->value;
-
- // If boolean prepare, if int, it is the event type: 1 - After Title, 2 - Before Display Content, 3 - After Display Content, 0 - Do not prepare
- if ($prepareValue && (is_bool($prepareValue) || $prepareValue === (int) $field->params->get('display', '2')))
- {
- PluginHelper::importPlugin('fields');
-
- /*
- * On before field prepare
- * Event allow plugins to modify the output of the field before it is prepared
- */
- Factory::getApplication()->triggerEvent('onCustomFieldsBeforePrepareField', array($context, $item, &$field));
-
- // Gathering the value for the field
- $value = Factory::getApplication()->triggerEvent('onCustomFieldsPrepareField', array($context, $item, &$field));
-
- if (is_array($value))
- {
- $value = implode(' ', $value);
- }
-
- /*
- * On after field render
- * Event allows plugins to modify the output of the prepared field
- */
- Factory::getApplication()->triggerEvent('onCustomFieldsAfterPrepareField', array($context, $item, $field, &$value));
-
- // Assign the value
- $field->value = $value;
- }
-
- $new[$key] = $field;
- }
-
- $fields = $new;
- }
-
- return $fields;
- }
-
- /**
- * Renders the layout file and data on the context and does a fall back to
- * Fields afterwards.
- *
- * @param string $context The context of the content passed to the helper
- * @param string $layoutFile layoutFile
- * @param array $displayData displayData
- *
- * @return NULL|string
- *
- * @since 3.7.0
- */
- public static function render($context, $layoutFile, $displayData)
- {
- $value = '';
-
- /*
- * Because the layout refreshes the paths before the render function is
- * called, so there is no way to load the layout overrides in the order
- * template -> context -> fields.
- * If there is no override in the context then we need to call the
- * layout from Fields.
- */
- if ($parts = self::extract($context))
- {
- // Trying to render the layout on the component from the context
- $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => $parts[0], 'client' => 0));
- }
-
- if ($value == '')
- {
- // Trying to render the layout on Fields itself
- $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => 'com_fields','client' => 0));
- }
-
- return $value;
- }
-
- /**
- * PrepareForm
- *
- * @param string $context The context of the content passed to the helper
- * @param Form $form form
- * @param object $data data.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public static function prepareForm($context, Form $form, $data)
- {
- // Extracting the component and section
- $parts = self::extract($context);
-
- if (! $parts)
- {
- return true;
- }
-
- $context = $parts[0] . '.' . $parts[1];
-
- // When no fields available return here
- $fields = self::getFields($parts[0] . '.' . $parts[1], new CMSObject);
-
- if (! $fields)
- {
- return true;
- }
-
- $component = $parts[0];
- $section = $parts[1];
-
- $assignedCatids = $data->catid ?? $data->fieldscatid ?? $form->getValue('catid');
-
- // Account for case that a submitted form has a multi-value category id field (e.g. a filtering form), just use the first category
- $assignedCatids = is_array($assignedCatids)
- ? (int) reset($assignedCatids)
- : (int) $assignedCatids;
-
- if (!$assignedCatids && $formField = $form->getField('catid'))
- {
- $assignedCatids = $formField->getAttribute('default', null);
-
- if (!$assignedCatids)
- {
- // Choose the first category available
- $catOptions = $formField->options;
-
- if ($catOptions && !empty($catOptions[0]->value))
- {
- $assignedCatids = (int) $catOptions[0]->value;
- }
- }
-
- $data->fieldscatid = $assignedCatids;
- }
-
- /*
- * If there is a catid field we need to reload the page when the catid
- * is changed
- */
- if ($form->getField('catid') && $parts[0] != 'com_fields')
- {
- /*
- * Setting some parameters for the category field
- */
- $form->setFieldAttribute('catid', 'refresh-enabled', true);
- $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids);
- $form->setFieldAttribute('catid', 'refresh-section', $section);
- }
-
- // Getting the fields
- $fields = self::getFields($parts[0] . '.' . $parts[1], $data);
-
- if (!$fields)
- {
- return true;
- }
-
- $fieldTypes = self::getFieldTypes();
-
- // Creating the dom
- $xml = new \DOMDocument('1.0', 'UTF-8');
- $fieldsNode = $xml->appendChild(new \DOMElement('form'))->appendChild(new \DOMElement('fields'));
- $fieldsNode->setAttribute('name', 'com_fields');
-
- // Organizing the fields according to their group
- $fieldsPerGroup = array(0 => array());
-
- foreach ($fields as $field)
- {
- if (!array_key_exists($field->type, $fieldTypes))
- {
- // Field type is not available
- continue;
- }
-
- if (!array_key_exists($field->group_id, $fieldsPerGroup))
- {
- $fieldsPerGroup[$field->group_id] = array();
- }
-
- if ($path = $fieldTypes[$field->type]['path'])
- {
- // Add the lookup path for the field
- FormHelper::addFieldPath($path);
- }
-
- if ($path = $fieldTypes[$field->type]['rules'])
- {
- // Add the lookup path for the rule
- FormHelper::addRulePath($path);
- }
-
- $fieldsPerGroup[$field->group_id][] = $field;
- }
-
- $model = Factory::getApplication()->bootComponent('com_fields')
- ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]);
- $model->setState('filter.context', $context);
-
- /**
- * $model->getItems() would only return existing groups, but we also
- * have the 'default' group with id 0 which is not in the database,
- * so we create it virtually here.
- */
- $defaultGroup = new \stdClass;
- $defaultGroup->id = 0;
- $defaultGroup->title = '';
- $defaultGroup->description = '';
- $iterateGroups = array_merge(array($defaultGroup), $model->getItems());
-
- // Looping through the groups
- foreach ($iterateGroups as $group)
- {
- if (empty($fieldsPerGroup[$group->id]))
- {
- continue;
- }
-
- // Defining the field set
- /** @var \DOMElement $fieldset */
- $fieldset = $fieldsNode->appendChild(new \DOMElement('fieldset'));
- $fieldset->setAttribute('name', 'fields-' . $group->id);
- $fieldset->setAttribute('addfieldpath', '/administrator/components/' . $component . '/models/fields');
- $fieldset->setAttribute('addrulepath', '/administrator/components/' . $component . '/models/rules');
-
- $label = $group->title;
- $description = $group->description;
-
- if (!$label)
- {
- $key = strtoupper($component . '_FIELDS_' . $section . '_LABEL');
-
- if (!Factory::getLanguage()->hasKey($key))
- {
- $key = 'JGLOBAL_FIELDS';
- }
-
- $label = $key;
- }
-
- if (!$description)
- {
- $key = strtoupper($component . '_FIELDS_' . $section . '_DESC');
-
- if (Factory::getLanguage()->hasKey($key))
- {
- $description = $key;
- }
- }
-
- $fieldset->setAttribute('label', $label);
- $fieldset->setAttribute('description', strip_tags($description));
-
- // Looping through the fields for that context
- foreach ($fieldsPerGroup[$group->id] as $field)
- {
- try
- {
- Factory::getApplication()->triggerEvent('onCustomFieldsPrepareDom', array($field, $fieldset, $form));
-
- /*
- * If the field belongs to an assigned_cat_id but the assigned_cat_ids in the data
- * is not known, set the required flag to false on any circumstance.
- */
- if (!$assignedCatids && !empty($field->assigned_cat_ids) && $form->getField($field->name))
- {
- $form->setFieldAttribute($field->name, 'required', 'false');
- }
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- // When the field set is empty, then remove it
- if (!$fieldset->hasChildNodes())
- {
- $fieldsNode->removeChild($fieldset);
- }
- }
-
- // Loading the XML fields string into the form
- $form->load($xml->saveXML());
-
- $model = Factory::getApplication()->bootComponent('com_fields')
- ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
-
- if ((!isset($data->id) || !$data->id) && Factory::getApplication()->input->getCmd('controller') == 'modules'
- && Factory::getApplication()->isClient('site'))
- {
- // Modules on front end editing don't have data and an id set
- $data->id = Factory::getApplication()->input->getInt('id');
- }
-
- // Looping through the fields again to set the value
- if (!isset($data->id) || !$data->id)
- {
- return true;
- }
-
- foreach ($fields as $field)
- {
- $value = $model->getFieldValue($field->id, $data->id);
-
- if ($value === null)
- {
- continue;
- }
-
- if (!is_array($value) && $value !== '')
- {
- // Function getField doesn't cache the fields, so we try to do it only when necessary
- $formField = $form->getField($field->name, 'com_fields');
-
- if ($formField && $formField->forceMultiple)
- {
- $value = (array) $value;
- }
- }
-
- // Setting the value on the field
- $form->setValue($field->name, 'com_fields', $value);
- }
-
- return true;
- }
-
- /**
- * Return a boolean if the actual logged in user can edit the given field value.
- *
- * @param \stdClass $field The field
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public static function canEditFieldValue($field)
- {
- $parts = self::extract($field->context);
-
- return Factory::getUser()->authorise('core.edit.value', $parts[0] . '.field.' . (int) $field->id);
- }
-
- /**
- * Return a boolean based on field (and field group) display / show_on settings
- *
- * @param \stdClass $field The field
- *
- * @return boolean
- *
- * @since 3.8.7
- */
- public static function displayFieldOnForm($field)
- {
- $app = Factory::getApplication();
-
- // Detect if the field should be shown at all
- if ($field->params->get('show_on') == 1 && $app->isClient('administrator'))
- {
- return false;
- }
- elseif ($field->params->get('show_on') == 2 && $app->isClient('site'))
- {
- return false;
- }
-
- if (!self::canEditFieldValue($field))
- {
- $fieldDisplayReadOnly = $field->params->get('display_readonly', '2');
-
- if ($fieldDisplayReadOnly == '2')
- {
- // Inherit from field group display read-only setting
- $groupModel = $app->bootComponent('com_fields')
- ->getMVCFactory()->createModel('Group', 'Administrator', ['ignore_request' => true]);
- $groupDisplayReadOnly = $groupModel->getItem($field->group_id)->params->get('display_readonly', '1');
- $fieldDisplayReadOnly = $groupDisplayReadOnly;
- }
-
- if ($fieldDisplayReadOnly == '0')
- {
- // Do not display field on form when field is read-only
- return false;
- }
- }
-
- // Display field on form
- return true;
- }
-
- /**
- * Gets assigned categories ids for a field
- *
- * @param \stdClass[] $fieldId The field ID
- *
- * @return array Array with the assigned category ids
- *
- * @since 4.0.0
- */
- public static function getAssignedCategoriesIds($fieldId)
- {
- $fieldId = (int) $fieldId;
-
- if (!$fieldId)
- {
- return array();
- }
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('a.category_id'))
- ->from($db->quoteName('#__fields_categories', 'a'))
- ->where('a.field_id = ' . $fieldId);
-
- $db->setQuery($query);
-
- return $db->loadColumn();
- }
-
- /**
- * Gets assigned categories titles for a field
- *
- * @param \stdClass[] $fieldId The field ID
- *
- * @return array Array with the assigned categories
- *
- * @since 3.7.0
- */
- public static function getAssignedCategoriesTitles($fieldId)
- {
- $fieldId = (int) $fieldId;
-
- if (!$fieldId)
- {
- return [];
- }
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('c.title'))
- ->from($db->quoteName('#__fields_categories', 'a'))
- ->join('INNER', $db->quoteName('#__categories', 'c') . ' ON a.category_id = c.id')
- ->where($db->quoteName('field_id') . ' = :fieldid')
- ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- return $db->loadColumn();
- }
-
- /**
- * Gets the fields system plugin extension id.
- *
- * @return integer The fields system plugin extension id.
- *
- * @since 3.7.0
- */
- public static function getFieldsPluginId()
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('fields'));
- $db->setQuery($query);
-
- try
- {
- $result = (int) $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- $result = 0;
- }
-
- return $result;
- }
-
- /**
- * Loads the fields plugins and returns an array of field types from the plugins.
- *
- * The returned array contains arrays with the following keys:
- * - label: The label of the field
- * - type: The type of the field
- * - path: The path of the folder where the field can be found
- *
- * @return array
- *
- * @since 3.7.0
- */
- public static function getFieldTypes()
- {
- PluginHelper::importPlugin('fields');
- $eventData = Factory::getApplication()->triggerEvent('onCustomFieldsGetTypes');
-
- $data = array();
-
- foreach ($eventData as $fields)
- {
- foreach ($fields as $fieldDescription)
- {
- if (!array_key_exists('path', $fieldDescription))
- {
- $fieldDescription['path'] = null;
- }
-
- if (!array_key_exists('rules', $fieldDescription))
- {
- $fieldDescription['rules'] = null;
- }
-
- $data[$fieldDescription['type']] = $fieldDescription;
- }
- }
-
- return $data;
- }
-
- /**
- * Clears the internal cache for the custom fields.
- *
- * @return void
- *
- * @since 3.8.0
- */
- public static function clearFieldsCache()
- {
- self::$fieldCache = null;
- self::$fieldsCache = null;
- }
+ /**
+ * @var FieldsModel
+ */
+ private static $fieldsCache = null;
+
+ /**
+ * @var FieldsModel
+ */
+ private static $fieldCache = null;
+
+ /**
+ * Extracts the component and section from the context string which has to
+ * be in the format component.context.
+ *
+ * @param string $contextString contextString
+ * @param object $item optional item object
+ *
+ * @return array|null
+ *
+ * @since 3.7.0
+ */
+ public static function extract($contextString, $item = null)
+ {
+ $parts = explode('.', $contextString, 2);
+
+ if (count($parts) < 2) {
+ return null;
+ }
+
+ $newSection = '';
+
+ $component = Factory::getApplication()->bootComponent($parts[0]);
+
+ if ($component instanceof FieldsServiceInterface) {
+ $newSection = $component->validateSection($parts[1], $item);
+ }
+
+ if ($newSection) {
+ $parts[1] = $newSection;
+ }
+
+ return $parts;
+ }
+
+ /**
+ * Returns the fields for the given context.
+ * If the item is an object the returned fields do have an additional field
+ * "value" which represents the value for the given item. If the item has an
+ * assigned_cat_ids field, then additionally fields which belong to that
+ * category will be returned.
+ * Should the value being prepared to be shown in an HTML context then
+ * prepareValue must be set to true. No further escaping needs to be done.
+ * The values of the fields can be overridden by an associative array where the keys
+ * have to be a name and its corresponding value.
+ *
+ * @param string $context The context of the content passed to the helper
+ * @param null $item The item being edited in the form
+ * @param int|bool $prepareValue (if int is display event): 1 - AfterTitle, 2 - BeforeDisplay, 3 - AfterDisplay, 0 - OFF
+ * @param array|null $valuesToOverride The values to override
+ * @param bool $includeSubformFields Should I include fields marked as Only Use In Subform?
+ *
+ * @return array
+ *
+ * @throws \Exception
+ * @since 3.7.0
+ */
+ public static function getFields(
+ $context,
+ $item = null,
+ $prepareValue = false,
+ array $valuesToOverride = null,
+ bool $includeSubformFields = false
+ ) {
+ if (self::$fieldsCache === null) {
+ // Load the model
+ self::$fieldsCache = Factory::getApplication()->bootComponent('com_fields')
+ ->getMVCFactory()->createModel('Fields', 'Administrator', ['ignore_request' => true]);
+
+ self::$fieldsCache->setState('filter.state', 1);
+ self::$fieldsCache->setState('list.limit', 0);
+ }
+
+ if ($includeSubformFields) {
+ self::$fieldsCache->setState('filter.only_use_in_subform', '');
+ } else {
+ self::$fieldsCache->setState('filter.only_use_in_subform', 0);
+ }
+
+ if (is_array($item)) {
+ $item = (object) $item;
+ }
+
+ if (Multilanguage::isEnabled() && isset($item->language) && $item->language != '*') {
+ self::$fieldsCache->setState('filter.language', array('*', $item->language));
+ }
+
+ self::$fieldsCache->setState('filter.context', $context);
+ self::$fieldsCache->setState('filter.assigned_cat_ids', array());
+
+ /*
+ * If item has assigned_cat_ids parameter display only fields which
+ * belong to the category
+ */
+ if ($item && (isset($item->catid) || isset($item->fieldscatid))) {
+ $assignedCatIds = $item->catid ?? $item->fieldscatid;
+
+ if (!is_array($assignedCatIds)) {
+ $assignedCatIds = explode(',', $assignedCatIds);
+ }
+
+ // Fields without any category assigned should show as well
+ $assignedCatIds[] = 0;
+
+ self::$fieldsCache->setState('filter.assigned_cat_ids', $assignedCatIds);
+ }
+
+ $fields = self::$fieldsCache->getItems();
+
+ if ($fields === false) {
+ return array();
+ }
+
+ if ($item && isset($item->id)) {
+ if (self::$fieldCache === null) {
+ self::$fieldCache = Factory::getApplication()->bootComponent('com_fields')
+ ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
+ }
+
+ $fieldIds = array_map(
+ function ($f) {
+ return $f->id;
+ },
+ $fields
+ );
+
+ $fieldValues = self::$fieldCache->getFieldValues($fieldIds, $item->id);
+
+ $new = array();
+
+ foreach ($fields as $key => $original) {
+ /*
+ * Doing a clone, otherwise fields for different items will
+ * always reference to the same object
+ */
+ $field = clone $original;
+
+ if ($valuesToOverride && array_key_exists($field->name, $valuesToOverride)) {
+ $field->value = $valuesToOverride[$field->name];
+ } elseif ($valuesToOverride && array_key_exists($field->id, $valuesToOverride)) {
+ $field->value = $valuesToOverride[$field->id];
+ } elseif (array_key_exists($field->id, $fieldValues)) {
+ $field->value = $fieldValues[$field->id];
+ }
+
+ if (!isset($field->value) || $field->value === '') {
+ $field->value = $field->default_value;
+ }
+
+ $field->rawvalue = $field->value;
+
+ // If boolean prepare, if int, it is the event type: 1 - After Title, 2 - Before Display Content, 3 - After Display Content, 0 - Do not prepare
+ if ($prepareValue && (is_bool($prepareValue) || $prepareValue === (int) $field->params->get('display', '2'))) {
+ PluginHelper::importPlugin('fields');
+
+ /*
+ * On before field prepare
+ * Event allow plugins to modify the output of the field before it is prepared
+ */
+ Factory::getApplication()->triggerEvent('onCustomFieldsBeforePrepareField', array($context, $item, &$field));
+
+ // Gathering the value for the field
+ $value = Factory::getApplication()->triggerEvent('onCustomFieldsPrepareField', array($context, $item, &$field));
+
+ if (is_array($value)) {
+ $value = implode(' ', $value);
+ }
+
+ /*
+ * On after field render
+ * Event allows plugins to modify the output of the prepared field
+ */
+ Factory::getApplication()->triggerEvent('onCustomFieldsAfterPrepareField', array($context, $item, $field, &$value));
+
+ // Assign the value
+ $field->value = $value;
+ }
+
+ $new[$key] = $field;
+ }
+
+ $fields = $new;
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Renders the layout file and data on the context and does a fall back to
+ * Fields afterwards.
+ *
+ * @param string $context The context of the content passed to the helper
+ * @param string $layoutFile layoutFile
+ * @param array $displayData displayData
+ *
+ * @return NULL|string
+ *
+ * @since 3.7.0
+ */
+ public static function render($context, $layoutFile, $displayData)
+ {
+ $value = '';
+
+ /*
+ * Because the layout refreshes the paths before the render function is
+ * called, so there is no way to load the layout overrides in the order
+ * template -> context -> fields.
+ * If there is no override in the context then we need to call the
+ * layout from Fields.
+ */
+ if ($parts = self::extract($context)) {
+ // Trying to render the layout on the component from the context
+ $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => $parts[0], 'client' => 0));
+ }
+
+ if ($value == '') {
+ // Trying to render the layout on Fields itself
+ $value = LayoutHelper::render($layoutFile, $displayData, null, array('component' => 'com_fields','client' => 0));
+ }
+
+ return $value;
+ }
+
+ /**
+ * PrepareForm
+ *
+ * @param string $context The context of the content passed to the helper
+ * @param Form $form form
+ * @param object $data data.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public static function prepareForm($context, Form $form, $data)
+ {
+ // Extracting the component and section
+ $parts = self::extract($context);
+
+ if (! $parts) {
+ return true;
+ }
+
+ $context = $parts[0] . '.' . $parts[1];
+
+ // When no fields available return here
+ $fields = self::getFields($parts[0] . '.' . $parts[1], new CMSObject());
+
+ if (! $fields) {
+ return true;
+ }
+
+ $component = $parts[0];
+ $section = $parts[1];
+
+ $assignedCatids = $data->catid ?? $data->fieldscatid ?? $form->getValue('catid');
+
+ // Account for case that a submitted form has a multi-value category id field (e.g. a filtering form), just use the first category
+ $assignedCatids = is_array($assignedCatids)
+ ? (int) reset($assignedCatids)
+ : (int) $assignedCatids;
+
+ if (!$assignedCatids && $formField = $form->getField('catid')) {
+ $assignedCatids = $formField->getAttribute('default', null);
+
+ if (!$assignedCatids) {
+ // Choose the first category available
+ $catOptions = $formField->options;
+
+ if ($catOptions && !empty($catOptions[0]->value)) {
+ $assignedCatids = (int) $catOptions[0]->value;
+ }
+ }
+
+ $data->fieldscatid = $assignedCatids;
+ }
+
+ /*
+ * If there is a catid field we need to reload the page when the catid
+ * is changed
+ */
+ if ($form->getField('catid') && $parts[0] != 'com_fields') {
+ /*
+ * Setting some parameters for the category field
+ */
+ $form->setFieldAttribute('catid', 'refresh-enabled', true);
+ $form->setFieldAttribute('catid', 'refresh-cat-id', $assignedCatids);
+ $form->setFieldAttribute('catid', 'refresh-section', $section);
+ }
+
+ // Getting the fields
+ $fields = self::getFields($parts[0] . '.' . $parts[1], $data);
+
+ if (!$fields) {
+ return true;
+ }
+
+ $fieldTypes = self::getFieldTypes();
+
+ // Creating the dom
+ $xml = new \DOMDocument('1.0', 'UTF-8');
+ $fieldsNode = $xml->appendChild(new \DOMElement('form'))->appendChild(new \DOMElement('fields'));
+ $fieldsNode->setAttribute('name', 'com_fields');
+
+ // Organizing the fields according to their group
+ $fieldsPerGroup = array(0 => array());
+
+ foreach ($fields as $field) {
+ if (!array_key_exists($field->type, $fieldTypes)) {
+ // Field type is not available
+ continue;
+ }
+
+ if (!array_key_exists($field->group_id, $fieldsPerGroup)) {
+ $fieldsPerGroup[$field->group_id] = array();
+ }
+
+ if ($path = $fieldTypes[$field->type]['path']) {
+ // Add the lookup path for the field
+ FormHelper::addFieldPath($path);
+ }
+
+ if ($path = $fieldTypes[$field->type]['rules']) {
+ // Add the lookup path for the rule
+ FormHelper::addRulePath($path);
+ }
+
+ $fieldsPerGroup[$field->group_id][] = $field;
+ }
+
+ $model = Factory::getApplication()->bootComponent('com_fields')
+ ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]);
+ $model->setState('filter.context', $context);
+
+ /**
+ * $model->getItems() would only return existing groups, but we also
+ * have the 'default' group with id 0 which is not in the database,
+ * so we create it virtually here.
+ */
+ $defaultGroup = new \stdClass();
+ $defaultGroup->id = 0;
+ $defaultGroup->title = '';
+ $defaultGroup->description = '';
+ $iterateGroups = array_merge(array($defaultGroup), $model->getItems());
+
+ // Looping through the groups
+ foreach ($iterateGroups as $group) {
+ if (empty($fieldsPerGroup[$group->id])) {
+ continue;
+ }
+
+ // Defining the field set
+ /** @var \DOMElement $fieldset */
+ $fieldset = $fieldsNode->appendChild(new \DOMElement('fieldset'));
+ $fieldset->setAttribute('name', 'fields-' . $group->id);
+ $fieldset->setAttribute('addfieldpath', '/administrator/components/' . $component . '/models/fields');
+ $fieldset->setAttribute('addrulepath', '/administrator/components/' . $component . '/models/rules');
+
+ $label = $group->title;
+ $description = $group->description;
+
+ if (!$label) {
+ $key = strtoupper($component . '_FIELDS_' . $section . '_LABEL');
+
+ if (!Factory::getLanguage()->hasKey($key)) {
+ $key = 'JGLOBAL_FIELDS';
+ }
+
+ $label = $key;
+ }
+
+ if (!$description) {
+ $key = strtoupper($component . '_FIELDS_' . $section . '_DESC');
+
+ if (Factory::getLanguage()->hasKey($key)) {
+ $description = $key;
+ }
+ }
+
+ $fieldset->setAttribute('label', $label);
+ $fieldset->setAttribute('description', strip_tags($description));
+
+ // Looping through the fields for that context
+ foreach ($fieldsPerGroup[$group->id] as $field) {
+ try {
+ Factory::getApplication()->triggerEvent('onCustomFieldsPrepareDom', array($field, $fieldset, $form));
+
+ /*
+ * If the field belongs to an assigned_cat_id but the assigned_cat_ids in the data
+ * is not known, set the required flag to false on any circumstance.
+ */
+ if (!$assignedCatids && !empty($field->assigned_cat_ids) && $form->getField($field->name)) {
+ $form->setFieldAttribute($field->name, 'required', 'false');
+ }
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ // When the field set is empty, then remove it
+ if (!$fieldset->hasChildNodes()) {
+ $fieldsNode->removeChild($fieldset);
+ }
+ }
+
+ // Loading the XML fields string into the form
+ $form->load($xml->saveXML());
+
+ $model = Factory::getApplication()->bootComponent('com_fields')
+ ->getMVCFactory()->createModel('Field', 'Administrator', ['ignore_request' => true]);
+
+ if (
+ (!isset($data->id) || !$data->id) && Factory::getApplication()->input->getCmd('controller') == 'modules'
+ && Factory::getApplication()->isClient('site')
+ ) {
+ // Modules on front end editing don't have data and an id set
+ $data->id = Factory::getApplication()->input->getInt('id');
+ }
+
+ // Looping through the fields again to set the value
+ if (!isset($data->id) || !$data->id) {
+ return true;
+ }
+
+ foreach ($fields as $field) {
+ $value = $model->getFieldValue($field->id, $data->id);
+
+ if ($value === null) {
+ continue;
+ }
+
+ if (!is_array($value) && $value !== '') {
+ // Function getField doesn't cache the fields, so we try to do it only when necessary
+ $formField = $form->getField($field->name, 'com_fields');
+
+ if ($formField && $formField->forceMultiple) {
+ $value = (array) $value;
+ }
+ }
+
+ // Setting the value on the field
+ $form->setValue($field->name, 'com_fields', $value);
+ }
+
+ return true;
+ }
+
+ /**
+ * Return a boolean if the actual logged in user can edit the given field value.
+ *
+ * @param \stdClass $field The field
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public static function canEditFieldValue($field)
+ {
+ $parts = self::extract($field->context);
+
+ return Factory::getUser()->authorise('core.edit.value', $parts[0] . '.field.' . (int) $field->id);
+ }
+
+ /**
+ * Return a boolean based on field (and field group) display / show_on settings
+ *
+ * @param \stdClass $field The field
+ *
+ * @return boolean
+ *
+ * @since 3.8.7
+ */
+ public static function displayFieldOnForm($field)
+ {
+ $app = Factory::getApplication();
+
+ // Detect if the field should be shown at all
+ if ($field->params->get('show_on') == 1 && $app->isClient('administrator')) {
+ return false;
+ } elseif ($field->params->get('show_on') == 2 && $app->isClient('site')) {
+ return false;
+ }
+
+ if (!self::canEditFieldValue($field)) {
+ $fieldDisplayReadOnly = $field->params->get('display_readonly', '2');
+
+ if ($fieldDisplayReadOnly == '2') {
+ // Inherit from field group display read-only setting
+ $groupModel = $app->bootComponent('com_fields')
+ ->getMVCFactory()->createModel('Group', 'Administrator', ['ignore_request' => true]);
+ $groupDisplayReadOnly = $groupModel->getItem($field->group_id)->params->get('display_readonly', '1');
+ $fieldDisplayReadOnly = $groupDisplayReadOnly;
+ }
+
+ if ($fieldDisplayReadOnly == '0') {
+ // Do not display field on form when field is read-only
+ return false;
+ }
+ }
+
+ // Display field on form
+ return true;
+ }
+
+ /**
+ * Gets assigned categories ids for a field
+ *
+ * @param \stdClass[] $fieldId The field ID
+ *
+ * @return array Array with the assigned category ids
+ *
+ * @since 4.0.0
+ */
+ public static function getAssignedCategoriesIds($fieldId)
+ {
+ $fieldId = (int) $fieldId;
+
+ if (!$fieldId) {
+ return array();
+ }
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('a.category_id'))
+ ->from($db->quoteName('#__fields_categories', 'a'))
+ ->where('a.field_id = ' . $fieldId);
+
+ $db->setQuery($query);
+
+ return $db->loadColumn();
+ }
+
+ /**
+ * Gets assigned categories titles for a field
+ *
+ * @param \stdClass[] $fieldId The field ID
+ *
+ * @return array Array with the assigned categories
+ *
+ * @since 3.7.0
+ */
+ public static function getAssignedCategoriesTitles($fieldId)
+ {
+ $fieldId = (int) $fieldId;
+
+ if (!$fieldId) {
+ return [];
+ }
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('c.title'))
+ ->from($db->quoteName('#__fields_categories', 'a'))
+ ->join('INNER', $db->quoteName('#__categories', 'c') . ' ON a.category_id = c.id')
+ ->where($db->quoteName('field_id') . ' = :fieldid')
+ ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ return $db->loadColumn();
+ }
+
+ /**
+ * Gets the fields system plugin extension id.
+ *
+ * @return integer The fields system plugin extension id.
+ *
+ * @since 3.7.0
+ */
+ public static function getFieldsPluginId()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('fields'));
+ $db->setQuery($query);
+
+ try {
+ $result = (int) $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ $result = 0;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Loads the fields plugins and returns an array of field types from the plugins.
+ *
+ * The returned array contains arrays with the following keys:
+ * - label: The label of the field
+ * - type: The type of the field
+ * - path: The path of the folder where the field can be found
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public static function getFieldTypes()
+ {
+ PluginHelper::importPlugin('fields');
+ $eventData = Factory::getApplication()->triggerEvent('onCustomFieldsGetTypes');
+
+ $data = array();
+
+ foreach ($eventData as $fields) {
+ foreach ($fields as $fieldDescription) {
+ if (!array_key_exists('path', $fieldDescription)) {
+ $fieldDescription['path'] = null;
+ }
+
+ if (!array_key_exists('rules', $fieldDescription)) {
+ $fieldDescription['rules'] = null;
+ }
+
+ $data[$fieldDescription['type']] = $fieldDescription;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Clears the internal cache for the custom fields.
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ public static function clearFieldsCache()
+ {
+ self::$fieldCache = null;
+ self::$fieldsCache = null;
+ }
}
diff --git a/administrator/components/com_fields/src/Model/FieldModel.php b/administrator/components/com_fields/src/Model/FieldModel.php
index d8af27d19629f..af2f28f4c7a59 100644
--- a/administrator/components/com_fields/src/Model/FieldModel.php
+++ b/administrator/components/com_fields/src/Model/FieldModel.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage'
- );
-
- /**
- * @var array
- *
- * @since 3.7.0
- */
- private $valueCache = array();
-
- /**
- * Constructor
- *
- * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
- * @param MVCFactoryInterface $factory The factory.
- *
- * @since 3.7.0
- * @throws \Exception
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null)
- {
- parent::__construct($config, $factory);
-
- $this->typeAlias = Factory::getApplication()->input->getCmd('context', 'com_content.article') . '.field';
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success, False on error.
- *
- * @since 3.7.0
- */
- public function save($data)
- {
- $field = null;
-
- if (isset($data['id']) && $data['id'])
- {
- $field = $this->getItem($data['id']);
- }
-
- if (!isset($data['label']) && isset($data['params']['label']))
- {
- $data['label'] = $data['params']['label'];
-
- unset($data['params']['label']);
- }
-
- // Alter the title for save as copy
- $input = Factory::getApplication()->input;
-
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['title'] == $origTable->title)
- {
- list($title, $name) = $this->generateNewTitle($data['group_id'], $data['name'], $data['title']);
- $data['title'] = $title;
- $data['label'] = $title;
- $data['name'] = $name;
- }
- else
- {
- if ($data['name'] == $origTable->name)
- {
- $data['name'] = '';
- }
- }
-
- $data['state'] = 0;
- }
-
- // Load the fields plugins, perhaps they want to do something
- PluginHelper::importPlugin('fields');
-
- $message = $this->checkDefaultValue($data);
-
- if ($message !== true)
- {
- $this->setError($message);
-
- return false;
- }
-
- if (!parent::save($data))
- {
- return false;
- }
-
- // Save the assigned categories into #__fields_categories
- $db = $this->getDatabase();
- $id = (int) $this->getState('field.id');
-
- /**
- * If the field is only used in subform, set Category to None automatically so that it will only be displayed
- * as part of SubForm on add/edit item screen
- */
- if (!empty($data['only_use_in_subform']))
- {
- $cats = [-1];
- }
- else
- {
- $cats = isset($data['assigned_cat_ids']) ? (array) $data['assigned_cat_ids'] : array();
- $cats = ArrayHelper::toInteger($cats);
- }
-
- $assignedCatIds = array();
-
- foreach ($cats as $cat)
- {
- // If we have found the 'JNONE' category, remove all other from the result and break.
- if ($cat == '-1')
- {
- $assignedCatIds = array('-1');
- break;
- }
-
- if ($cat)
- {
- $assignedCatIds[] = $cat;
- }
- }
-
- // First delete all assigned categories
- $query = $db->getQuery(true);
- $query->delete('#__fields_categories')
- ->where($db->quoteName('field_id') . ' = :fieldid')
- ->bind(':fieldid', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
- $db->execute();
-
- // Inset new assigned categories
- $tupel = new \stdClass;
- $tupel->field_id = $id;
-
- foreach ($assignedCatIds as $catId)
- {
- $tupel->category_id = $catId;
- $db->insertObject('#__fields_categories', $tupel);
- }
-
- /**
- * If the options have changed, delete the values. This should only apply for list, checkboxes and radio
- * custom field types, because when their options are being changed, their values might get invalid, because
- * e.g. there is a value selected from a list, which is not part of the list anymore. Hence we need to delete
- * all values that are not part of the options anymore. Note: The only field types with fieldparams+options
- * are those above listed plus the subfields type. And we do explicitly not want the values to be deleted
- * when the options of a subfields field are getting changed.
- */
- if ($field && in_array($field->type, array('list', 'checkboxes', 'radio'), true)
- && isset($data['fieldparams']['options']) && isset($field->fieldparams['options']))
- {
- $oldParams = $this->getParams($field->fieldparams['options']);
- $newParams = $this->getParams($data['fieldparams']['options']);
-
- if (is_object($oldParams) && is_object($newParams) && $oldParams != $newParams)
- {
- // Get new values.
- $names = array_column((array) $newParams, 'value');
-
- $fieldId = (int) $field->id;
- $query = $db->getQuery(true);
- $query->delete($db->quoteName('#__fields_values'))
- ->where($db->quoteName('field_id') . ' = :fieldid')
- ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
-
- // If new values are set, delete only old values. Otherwise delete all values.
- if ($names)
- {
- $query->whereNotIn($db->quoteName('value'), $names, ParameterType::STRING);
- }
-
- $db->setQuery($query);
- $db->execute();
- }
- }
-
- FieldsHelper::clearFieldsCache();
-
- return true;
- }
-
-
- /**
- * Checks if the default value is valid for the given data. If a string is returned then
- * it can be assumed that the default value is invalid.
- *
- * @param array $data The data.
- *
- * @return true|string true if valid, a string containing the exception message when not.
- *
- * @since 3.7.0
- */
- private function checkDefaultValue($data)
- {
- // Empty default values are correct
- if (empty($data['default_value']) && $data['default_value'] !== '0')
- {
- return true;
- }
-
- $types = FieldsHelper::getFieldTypes();
-
- // Check if type exists
- if (!array_key_exists($data['type'], $types))
- {
- return true;
- }
-
- $path = $types[$data['type']]['rules'];
-
- // Add the path for the rules of the plugin when available
- if ($path)
- {
- // Add the lookup path for the rule
- FormHelper::addRulePath($path);
- }
-
- // Create the fields object
- $obj = (object) $data;
- $obj->params = new Registry($obj->params);
- $obj->fieldparams = new Registry(!empty($obj->fieldparams) ? $obj->fieldparams : array());
-
- // Prepare the dom
- $dom = new \DOMDocument;
- $node = $dom->appendChild(new \DOMElement('form'));
-
- // Trigger the event to create the field dom node
- $form = new Form($data['context']);
- $form->setDatabase($this->getDatabase());
- Factory::getApplication()->triggerEvent('onCustomFieldsPrepareDom', array($obj, $node, $form));
-
- // Check if a node is created
- if (!$node->firstChild)
- {
- return true;
- }
-
- // Define the type either from the field or from the data
- $type = $node->firstChild->getAttribute('validate') ? : $data['type'];
-
- // Load the rule
- $rule = FormHelper::loadRuleType($type);
-
- // When no rule exists, we allow the default value
- if (!$rule)
- {
- return true;
- }
-
- if ($rule instanceof DatabaseAwareInterface)
- {
- try
- {
- $rule->setDatabase($this->getDatabase());
- }
- catch (DatabaseNotFoundException $e)
- {
- @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
- $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
- }
- }
-
- try
- {
- // Perform the check
- $result = $rule->test(simplexml_import_dom($node->firstChild), $data['default_value']);
-
- // Check if the test succeeded
- return $result === true ? : Text::_('COM_FIELDS_FIELD_INVALID_DEFAULT_VALUE');
- }
- catch (\UnexpectedValueException $e)
- {
- return $e->getMessage();
- }
- }
-
- /**
- * Converts the unknown params into an object.
- *
- * @param mixed $params The params.
- *
- * @return \stdClass Object on success, false on failure.
- *
- * @since 3.7.0
- */
- private function getParams($params)
- {
- if (is_string($params))
- {
- $params = json_decode($params);
- }
-
- if (is_array($params))
- {
- $params = (object) $params;
- }
-
- return $params;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 3.7.0
- */
- public function getItem($pk = null)
- {
- $result = parent::getItem($pk);
-
- if ($result)
- {
- // Prime required properties.
- if (empty($result->id))
- {
- $result->context = Factory::getApplication()->input->getCmd('context', $this->getState('field.context'));
- }
-
- if (property_exists($result, 'fieldparams') && $result->fieldparams !== null)
- {
- $registry = new Registry;
-
- if ($result->fieldparams)
- {
- $registry->loadString($result->fieldparams);
- }
-
- $result->fieldparams = $registry->toArray();
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $fieldId = (int) $result->id;
- $query->select($db->quoteName('category_id'))
- ->from($db->quoteName('#__fields_categories'))
- ->where($db->quoteName('field_id') . ' = :fieldid')
- ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
-
- $db->setQuery($query);
- $result->assigned_cat_ids = $db->loadColumn() ?: array(0);
- }
-
- return $result;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $name The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $options Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 3.7.0
- * @throws \Exception
- */
- public function getTable($name = 'Field', $prefix = 'Administrator', $options = array())
- {
- // Default to text type
- $table = parent::getTable($name, $prefix, $options);
- $table->type = 'text';
-
- return $table;
- }
-
- /**
- * Method to change the title & name.
- *
- * @param integer $categoryId The id of the category.
- * @param string $name The name.
- * @param string $title The title.
- *
- * @return array Contains the modified title and name.
- *
- * @since 3.7.0
- */
- protected function generateNewTitle($categoryId, $name, $title)
- {
- // Alter the title & name
- $table = $this->getTable();
-
- while ($table->load(array('name' => $name)))
- {
- $title = StringHelper::increment($title);
- $name = StringHelper::increment($name, 'dash');
- }
-
- return array(
- $title,
- $name,
- );
- }
-
- /**
- * Method to delete one or more records.
- *
- * @param array $pks An array of record primary keys.
- *
- * @return boolean True if successful, false if an error occurs.
- *
- * @since 3.7.0
- */
- public function delete(&$pks)
- {
- $success = parent::delete($pks);
-
- if ($success)
- {
- $pks = (array) $pks;
- $pks = ArrayHelper::toInteger($pks);
- $pks = array_filter($pks);
-
- if (!empty($pks))
- {
- // Delete Values
- $query = $this->getDatabase()->getQuery(true);
-
- $query->delete($query->quoteName('#__fields_values'))
- ->whereIn($query->quoteName('field_id'), $pks);
-
- $this->getDatabase()->setQuery($query)->execute();
-
- // Delete Assigned Categories
- $query = $this->getDatabase()->getQuery(true);
-
- $query->delete($query->quoteName('#__fields_categories'))
- ->whereIn($query->quoteName('field_id'), $pks);
-
- $this->getDatabase()->setQuery($query)->execute();
- }
- }
-
- return $success;
- }
-
- /**
- * Abstract method for getting the form from the model.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 3.7.0
- */
- public function getForm($data = array(), $loadData = true)
- {
- $context = $this->getState('field.context');
- $jinput = Factory::getApplication()->input;
-
- // A workaround to get the context into the model for save requests.
- if (empty($context) && isset($data['context']))
- {
- $context = $data['context'];
- $parts = FieldsHelper::extract($context);
-
- $this->setState('field.context', $context);
-
- if ($parts)
- {
- $this->setState('field.component', $parts[0]);
- $this->setState('field.section', $parts[1]);
- }
- }
-
- if (isset($data['type']))
- {
- // This is needed that the plugins can determine the type
- $this->setState('field.type', $data['type']);
- }
-
- // Load the fields plugin that they can add additional parameters to the form
- PluginHelper::importPlugin('fields');
-
- // Get the form.
- $form = $this->loadForm(
- 'com_fields.field.' . $context, 'field',
- array(
- 'control' => 'jform',
- 'load_data' => true,
- )
- );
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on Edit State access controls.
- if (empty($data['context']))
- {
- $data['context'] = $context;
- }
-
- $fieldId = $jinput->get('id');
- $assetKey = $this->state->get('field.component') . '.field.' . $fieldId;
-
- if (!Factory::getUser()->authorise('core.edit.state', $assetKey))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('state', 'disabled', 'true');
-
- // Disable fields while saving. The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('state', 'filter', 'unset');
- }
-
- // Don't allow to change the created_user_id user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_user_id', 'filter', 'unset');
- }
-
- // In case we are editing a field, field type cannot be changed, so some extra handling below is needed
- if ($fieldId)
- {
- $fieldType = $form->getField('type');
-
- if ($fieldType->value == 'subform')
- {
- // Only Use In subform should not be available for subform field type, so we remove it
- $form->removeField('only_use_in_subform');
- }
- else
- {
- // Field type could not be changed, so remove showon attribute to avoid js errors
- $form->setFieldAttribute('only_use_in_subform', 'showon', '');
- }
- }
-
- return $form;
- }
-
- /**
- * Setting the value for the given field id, context and item id.
- *
- * @param string $fieldId The field ID.
- * @param string $itemId The ID of the item.
- * @param string $value The value.
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- public function setFieldValue($fieldId, $itemId, $value)
- {
- $field = $this->getItem($fieldId);
- $params = $field->params;
-
- if (is_array($params))
- {
- $params = new Registry($params);
- }
-
- // Don't save the value when the user is not authorized to change it
- if (!$field || !FieldsHelper::canEditFieldValue($field))
- {
- return false;
- }
-
- $needsDelete = false;
- $needsInsert = false;
- $needsUpdate = false;
-
- $oldValue = $this->getFieldValue($fieldId, $itemId);
- $value = (array) $value;
-
- if ($oldValue === null)
- {
- // No records available, doing normal insert
- $needsInsert = true;
- }
- elseif (count($value) == 1 && count((array) $oldValue) == 1)
- {
- // Only a single row value update can be done when not empty
- $needsUpdate = is_array($value[0]) ? count($value[0]) : strlen($value[0]);
- $needsDelete = !$needsUpdate;
- }
- else
- {
- // Multiple values, we need to purge the data and do a new
- // insert
- $needsDelete = true;
- $needsInsert = true;
- }
-
- if ($needsDelete)
- {
- $fieldId = (int) $fieldId;
-
- // Deleting the existing record as it is a reset
- $query = $this->getDatabase()->getQuery(true);
-
- $query->delete($query->quoteName('#__fields_values'))
- ->where($query->quoteName('field_id') . ' = :fieldid')
- ->where($query->quoteName('item_id') . ' = :itemid')
- ->bind(':fieldid', $fieldId, ParameterType::INTEGER)
- ->bind(':itemid', $itemId);
-
- $this->getDatabase()->setQuery($query)->execute();
- }
-
- if ($needsInsert)
- {
- $newObj = new \stdClass;
-
- $newObj->field_id = (int) $fieldId;
- $newObj->item_id = $itemId;
-
- foreach ($value as $v)
- {
- $newObj->value = $v;
-
- $this->getDatabase()->insertObject('#__fields_values', $newObj);
- }
- }
-
- if ($needsUpdate)
- {
- $updateObj = new \stdClass;
-
- $updateObj->field_id = (int) $fieldId;
- $updateObj->item_id = $itemId;
- $updateObj->value = reset($value);
-
- $this->getDatabase()->updateObject('#__fields_values', $updateObj, array('field_id', 'item_id'));
- }
-
- $this->valueCache = array();
- FieldsHelper::clearFieldsCache();
-
- return true;
- }
-
- /**
- * Returning the value for the given field id, context and item id.
- *
- * @param string $fieldId The field ID.
- * @param string $itemId The ID of the item.
- *
- * @return NULL|string
- *
- * @since 3.7.0
- */
- public function getFieldValue($fieldId, $itemId)
- {
- $values = $this->getFieldValues(array($fieldId), $itemId);
-
- if (array_key_exists($fieldId, $values))
- {
- return $values[$fieldId];
- }
-
- return null;
- }
-
- /**
- * Returning the values for the given field ids, context and item id.
- *
- * @param array $fieldIds The field Ids.
- * @param string $itemId The ID of the item.
- *
- * @return NULL|array
- *
- * @since 3.7.0
- */
- public function getFieldValues(array $fieldIds, $itemId)
- {
- if (!$fieldIds)
- {
- return array();
- }
-
- // Create a unique key for the cache
- $key = md5(serialize($fieldIds) . $itemId);
-
- // Fill the cache when it doesn't exist
- if (!array_key_exists($key, $this->valueCache))
- {
- // Create the query
- $query = $this->getDatabase()->getQuery(true);
-
- $query->select($query->quoteName(['field_id', 'value']))
- ->from($query->quoteName('#__fields_values'))
- ->whereIn($query->quoteName('field_id'), ArrayHelper::toInteger($fieldIds))
- ->where($query->quoteName('item_id') . ' = :itemid')
- ->bind(':itemid', $itemId);
-
- // Fetch the row from the database
- $rows = $this->getDatabase()->setQuery($query)->loadObjectList();
-
- $data = array();
-
- // Fill the data container from the database rows
- foreach ($rows as $row)
- {
- // If there are multiple values for a field, create an array
- if (array_key_exists($row->field_id, $data))
- {
- // Transform it to an array
- if (!is_array($data[$row->field_id]))
- {
- $data[$row->field_id] = array($data[$row->field_id]);
- }
-
- // Set the value in the array
- $data[$row->field_id][] = $row->value;
-
- // Go to the next row, otherwise the value gets overwritten in the data container
- continue;
- }
-
- // Set the value
- $data[$row->field_id] = $row->value;
- }
-
- // Assign it to the internal cache
- $this->valueCache[$key] = $data;
- }
-
- // Return the value from the cache
- return $this->valueCache[$key];
- }
-
- /**
- * Cleaning up the values for the given item on the context.
- *
- * @param string $context The context.
- * @param string $itemId The Item ID.
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function cleanupValues($context, $itemId)
- {
- // Delete with inner join is not possible so we need to do a subquery
- $fieldsQuery = $this->getDatabase()->getQuery(true);
- $fieldsQuery->select($fieldsQuery->quoteName('id'))
- ->from($fieldsQuery->quoteName('#__fields'))
- ->where($fieldsQuery->quoteName('context') . ' = :context');
-
- $query = $this->getDatabase()->getQuery(true);
-
- $query->delete($query->quoteName('#__fields_values'))
- ->where($query->quoteName('field_id') . ' IN (' . $fieldsQuery . ')')
- ->where($query->quoteName('item_id') . ' = :itemid')
- ->bind(':itemid', $itemId)
- ->bind(':context', $context);
-
- $this->getDatabase()->setQuery($query)->execute();
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
- *
- * @since 3.7.0
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->state != -2)
- {
- return false;
- }
-
- $parts = FieldsHelper::extract($record->context);
-
- return Factory::getUser()->authorise('core.delete', $parts[0] . '.field.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission for the
- * component.
- *
- * @since 3.7.0
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
- $parts = FieldsHelper::extract($record->context);
-
- // Check for existing field.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $parts[0] . '.field.' . (int) $record->id);
- }
-
- return $user->authorise('core.edit.state', $parts[0]);
- }
-
- /**
- * Stock method to auto-populate the model state.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState($this->getName() . '.id', $pk);
-
- $context = $app->input->get('context', 'com_content.article');
- $this->setState('field.context', $context);
- $parts = FieldsHelper::extract($context);
-
- // Extract the component name
- $this->setState('field.component', $parts[0]);
-
- // Extract the optional section name
- $this->setState('field.section', (count($parts) > 1) ? $parts[1] : null);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_fields');
- $this->setState('params', $params);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param Table $table A Table object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 3.7.0
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('context') . ' = ' . $db->quote($table->context),
- ];
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return array The default data is an empty array.
- *
- * @since 3.7.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $app = Factory::getApplication();
- $data = $app->getUserState('com_fields.edit.field.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Pre-select some filters (Status, Language, Access) in edit form
- // if those have been selected in Category Manager
- if (!$data->id)
- {
- // Check for which context the Category Manager is used and
- // get selected fields
- $filters = (array) $app->getUserState('com_fields.fields.filter');
-
- $data->set('state', $app->input->getInt('state', ((isset($filters['state']) && $filters['state'] !== '') ? $filters['state'] : null)));
- $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
- $data->set('group_id', $app->input->getString('group_id', (!empty($filters['group_id']) ? $filters['group_id'] : null)));
- $data->set(
- 'access',
- $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
- );
-
- // Set the type if available from the request
- $data->set('type', $app->input->getWord('type', $this->state->get('field.type', $data->get('type'))));
- }
-
- if ($data->label && !isset($data->params['label']))
- {
- $data->params['label'] = $data->label;
- }
- }
-
- $this->preprocessData('com_fields.field', $data);
-
- return $data;
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see JFormRule
- * @see JFilterInput
- * @since 3.9.23
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', 'com_fields'))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to allow derived classes to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 3.7.0
- *
- * @throws \Exception if there is an error in the form event.
- *
- * @see \Joomla\CMS\Form\FormField
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $component = $this->state->get('field.component');
- $section = $this->state->get('field.section');
- $dataObject = $data;
-
- if (is_array($dataObject))
- {
- $dataObject = (object) $dataObject;
- }
-
- if (isset($dataObject->type))
- {
- $form->setFieldAttribute('type', 'component', $component);
-
- // Not allowed to change the type of an existing record
- if ($dataObject->id)
- {
- $form->setFieldAttribute('type', 'readonly', 'true');
- }
-
- // Allow to override the default value label and description through the plugin
- $key = 'PLG_FIELDS_' . strtoupper($dataObject->type) . '_DEFAULT_VALUE_LABEL';
-
- if (Factory::getLanguage()->hasKey($key))
- {
- $form->setFieldAttribute('default_value', 'label', $key);
- }
-
- $key = 'PLG_FIELDS_' . strtoupper($dataObject->type) . '_DEFAULT_VALUE_DESC';
-
- if (Factory::getLanguage()->hasKey($key))
- {
- $form->setFieldAttribute('default_value', 'description', $key);
- }
-
- // Remove placeholder field on list fields
- if ($dataObject->type == 'list')
- {
- $form->removeField('hint', 'params');
- }
- }
-
- // Get the categories for this component (and optionally this section, if available)
- $cat = (
- function () use ($component, $section) {
- // Get the CategoryService for this component
- $componentObject = $this->bootComponent($component);
-
- if (!$componentObject instanceof CategoryServiceInterface)
- {
- // No CategoryService -> no categories
- return null;
- }
-
- $cat = null;
-
- // Try to get the categories for this component and section
- try
- {
- $cat = $componentObject->getCategory([], $section ?: '');
- }
- catch (SectionNotFoundException $e)
- {
- // Not found for component and section -> Now try once more without the section, so only component
- try
- {
- $cat = $componentObject->getCategory();
- }
- catch (SectionNotFoundException $e)
- {
- // If we haven't found it now, return (no categories available for this component)
- return null;
- }
- }
-
- // So we found categories for at least the component, return them
- return $cat;
- }
- )();
-
- // If we found categories, and if the root category has children, set them in the form
- if ($cat && $cat->get('root')->hasChildren())
- {
- $form->setFieldAttribute('assigned_cat_ids', 'extension', $cat->getExtension());
- }
- else
- {
- // Else remove the field from the form
- $form->removeField('assigned_cat_ids');
- }
-
- $form->setFieldAttribute('type', 'component', $component);
- $form->setFieldAttribute('group_id', 'context', $this->state->get('field.context'));
- $form->setFieldAttribute('rules', 'component', $component);
-
- // Looking in the component forms folder for a specific section forms file
- $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/forms/fields/' . $section . '.xml');
-
- if (!file_exists($path))
- {
- // Looking in the component models/forms folder for a specific section forms file
- $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/fields/' . $section . '.xml');
- }
-
- if (file_exists($path))
- {
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE);
- $lang->load($component, JPATH_BASE . '/components/' . $component);
-
- if (!$form->loadFile($path, false))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Clean the cache
- *
- * @param string $group The cache group
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- $context = Factory::getApplication()->input->get('context');
-
- switch ($context)
- {
- case 'com_content':
- parent::cleanCache('com_content');
- parent::cleanCache('mod_articles_archive');
- parent::cleanCache('mod_articles_categories');
- parent::cleanCache('mod_articles_category');
- parent::cleanCache('mod_articles_latest');
- parent::cleanCache('mod_articles_news');
- parent::cleanCache('mod_articles_popular');
- break;
- default:
- parent::cleanCache($context);
- break;
- }
- }
-
- /**
- * Batch copy fields to a new group.
- *
- * @param integer $value The new value matching a fields group.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return array|boolean new IDs if successful, false otherwise and internal error is set.
- *
- * @since 3.7.0
- */
- protected function batchCopy($value, $pks, $contexts)
- {
- // Set the variables
- $user = Factory::getUser();
- $table = $this->getTable();
- $newIds = array();
- $component = $this->state->get('filter.component');
- $value = (int) $value;
-
- foreach ($pks as $pk)
- {
- if ($user->authorise('core.create', $component . '.fieldgroup.' . $value))
- {
- $table->reset();
- $table->load($pk);
-
- $table->group_id = $value;
-
- // Reset the ID because we are making a copy
- $table->id = 0;
-
- // Unpublish the new field
- $table->state = 0;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Get the new item ID
- $newId = $table->get('id');
-
- // Add the new ID to the array
- $newIds[$pk] = $newId;
- }
- else
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return $newIds;
- }
-
- /**
- * Batch move fields to a new group.
- *
- * @param integer $value The new value matching a fields group.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 3.7.0
- */
- protected function batchMove($value, $pks, $contexts)
- {
- // Set the variables
- $user = Factory::getUser();
- $table = $this->getTable();
- $context = explode('.', Factory::getApplication()->getUserState('com_fields.fields.context'));
- $value = (int) $value;
-
- foreach ($pks as $pk)
- {
- if ($user->authorise('core.edit', $context[0] . '.fieldgroup.' . $value))
- {
- $table->reset();
- $table->load($pk);
-
- $table->group_id = $value;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- else
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
+ /**
+ * @var null|string
+ *
+ * @since 3.7.0
+ */
+ public $typeAlias = null;
+
+ /**
+ * @var string
+ *
+ * @since 3.7.0
+ */
+ protected $text_prefix = 'COM_FIELDS';
+
+ /**
+ * Batch copy/move command. If set to false,
+ * the batch copy/move command is not supported
+ *
+ * @var string
+ * @since 3.4
+ */
+ protected $batch_copymove = 'group_id';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage'
+ );
+
+ /**
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ private $valueCache = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ parent::__construct($config, $factory);
+
+ $this->typeAlias = Factory::getApplication()->input->getCmd('context', 'com_content.article') . '.field';
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success, False on error.
+ *
+ * @since 3.7.0
+ */
+ public function save($data)
+ {
+ $field = null;
+
+ if (isset($data['id']) && $data['id']) {
+ $field = $this->getItem($data['id']);
+ }
+
+ if (!isset($data['label']) && isset($data['params']['label'])) {
+ $data['label'] = $data['params']['label'];
+
+ unset($data['params']['label']);
+ }
+
+ // Alter the title for save as copy
+ $input = Factory::getApplication()->input;
+
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['title'] == $origTable->title) {
+ list($title, $name) = $this->generateNewTitle($data['group_id'], $data['name'], $data['title']);
+ $data['title'] = $title;
+ $data['label'] = $title;
+ $data['name'] = $name;
+ } else {
+ if ($data['name'] == $origTable->name) {
+ $data['name'] = '';
+ }
+ }
+
+ $data['state'] = 0;
+ }
+
+ // Load the fields plugins, perhaps they want to do something
+ PluginHelper::importPlugin('fields');
+
+ $message = $this->checkDefaultValue($data);
+
+ if ($message !== true) {
+ $this->setError($message);
+
+ return false;
+ }
+
+ if (!parent::save($data)) {
+ return false;
+ }
+
+ // Save the assigned categories into #__fields_categories
+ $db = $this->getDatabase();
+ $id = (int) $this->getState('field.id');
+
+ /**
+ * If the field is only used in subform, set Category to None automatically so that it will only be displayed
+ * as part of SubForm on add/edit item screen
+ */
+ if (!empty($data['only_use_in_subform'])) {
+ $cats = [-1];
+ } else {
+ $cats = isset($data['assigned_cat_ids']) ? (array) $data['assigned_cat_ids'] : array();
+ $cats = ArrayHelper::toInteger($cats);
+ }
+
+ $assignedCatIds = array();
+
+ foreach ($cats as $cat) {
+ // If we have found the 'JNONE' category, remove all other from the result and break.
+ if ($cat == '-1') {
+ $assignedCatIds = array('-1');
+ break;
+ }
+
+ if ($cat) {
+ $assignedCatIds[] = $cat;
+ }
+ }
+
+ // First delete all assigned categories
+ $query = $db->getQuery(true);
+ $query->delete('#__fields_categories')
+ ->where($db->quoteName('field_id') . ' = :fieldid')
+ ->bind(':fieldid', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $db->execute();
+
+ // Inset new assigned categories
+ $tupel = new \stdClass();
+ $tupel->field_id = $id;
+
+ foreach ($assignedCatIds as $catId) {
+ $tupel->category_id = $catId;
+ $db->insertObject('#__fields_categories', $tupel);
+ }
+
+ /**
+ * If the options have changed, delete the values. This should only apply for list, checkboxes and radio
+ * custom field types, because when their options are being changed, their values might get invalid, because
+ * e.g. there is a value selected from a list, which is not part of the list anymore. Hence we need to delete
+ * all values that are not part of the options anymore. Note: The only field types with fieldparams+options
+ * are those above listed plus the subfields type. And we do explicitly not want the values to be deleted
+ * when the options of a subfields field are getting changed.
+ */
+ if (
+ $field && in_array($field->type, array('list', 'checkboxes', 'radio'), true)
+ && isset($data['fieldparams']['options']) && isset($field->fieldparams['options'])
+ ) {
+ $oldParams = $this->getParams($field->fieldparams['options']);
+ $newParams = $this->getParams($data['fieldparams']['options']);
+
+ if (is_object($oldParams) && is_object($newParams) && $oldParams != $newParams) {
+ // Get new values.
+ $names = array_column((array) $newParams, 'value');
+
+ $fieldId = (int) $field->id;
+ $query = $db->getQuery(true);
+ $query->delete($db->quoteName('#__fields_values'))
+ ->where($db->quoteName('field_id') . ' = :fieldid')
+ ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
+
+ // If new values are set, delete only old values. Otherwise delete all values.
+ if ($names) {
+ $query->whereNotIn($db->quoteName('value'), $names, ParameterType::STRING);
+ }
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ }
+
+ FieldsHelper::clearFieldsCache();
+
+ return true;
+ }
+
+
+ /**
+ * Checks if the default value is valid for the given data. If a string is returned then
+ * it can be assumed that the default value is invalid.
+ *
+ * @param array $data The data.
+ *
+ * @return true|string true if valid, a string containing the exception message when not.
+ *
+ * @since 3.7.0
+ */
+ private function checkDefaultValue($data)
+ {
+ // Empty default values are correct
+ if (empty($data['default_value']) && $data['default_value'] !== '0') {
+ return true;
+ }
+
+ $types = FieldsHelper::getFieldTypes();
+
+ // Check if type exists
+ if (!array_key_exists($data['type'], $types)) {
+ return true;
+ }
+
+ $path = $types[$data['type']]['rules'];
+
+ // Add the path for the rules of the plugin when available
+ if ($path) {
+ // Add the lookup path for the rule
+ FormHelper::addRulePath($path);
+ }
+
+ // Create the fields object
+ $obj = (object) $data;
+ $obj->params = new Registry($obj->params);
+ $obj->fieldparams = new Registry(!empty($obj->fieldparams) ? $obj->fieldparams : array());
+
+ // Prepare the dom
+ $dom = new \DOMDocument();
+ $node = $dom->appendChild(new \DOMElement('form'));
+
+ // Trigger the event to create the field dom node
+ $form = new Form($data['context']);
+ $form->setDatabase($this->getDatabase());
+ Factory::getApplication()->triggerEvent('onCustomFieldsPrepareDom', array($obj, $node, $form));
+
+ // Check if a node is created
+ if (!$node->firstChild) {
+ return true;
+ }
+
+ // Define the type either from the field or from the data
+ $type = $node->firstChild->getAttribute('validate') ? : $data['type'];
+
+ // Load the rule
+ $rule = FormHelper::loadRuleType($type);
+
+ // When no rule exists, we allow the default value
+ if (!$rule) {
+ return true;
+ }
+
+ if ($rule instanceof DatabaseAwareInterface) {
+ try {
+ $rule->setDatabase($this->getDatabase());
+ } catch (DatabaseNotFoundException $e) {
+ @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
+ $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
+ }
+ }
+
+ try {
+ // Perform the check
+ $result = $rule->test(simplexml_import_dom($node->firstChild), $data['default_value']);
+
+ // Check if the test succeeded
+ return $result === true ? : Text::_('COM_FIELDS_FIELD_INVALID_DEFAULT_VALUE');
+ } catch (\UnexpectedValueException $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * Converts the unknown params into an object.
+ *
+ * @param mixed $params The params.
+ *
+ * @return \stdClass Object on success, false on failure.
+ *
+ * @since 3.7.0
+ */
+ private function getParams($params)
+ {
+ if (is_string($params)) {
+ $params = json_decode($params);
+ }
+
+ if (is_array($params)) {
+ $params = (object) $params;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 3.7.0
+ */
+ public function getItem($pk = null)
+ {
+ $result = parent::getItem($pk);
+
+ if ($result) {
+ // Prime required properties.
+ if (empty($result->id)) {
+ $result->context = Factory::getApplication()->input->getCmd('context', $this->getState('field.context'));
+ }
+
+ if (property_exists($result, 'fieldparams') && $result->fieldparams !== null) {
+ $registry = new Registry();
+
+ if ($result->fieldparams) {
+ $registry->loadString($result->fieldparams);
+ }
+
+ $result->fieldparams = $registry->toArray();
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $fieldId = (int) $result->id;
+ $query->select($db->quoteName('category_id'))
+ ->from($db->quoteName('#__fields_categories'))
+ ->where($db->quoteName('field_id') . ' = :fieldid')
+ ->bind(':fieldid', $fieldId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $result->assigned_cat_ids = $db->loadColumn() ?: array(0);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $name The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $options Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ */
+ public function getTable($name = 'Field', $prefix = 'Administrator', $options = array())
+ {
+ // Default to text type
+ $table = parent::getTable($name, $prefix, $options);
+ $table->type = 'text';
+
+ return $table;
+ }
+
+ /**
+ * Method to change the title & name.
+ *
+ * @param integer $categoryId The id of the category.
+ * @param string $name The name.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and name.
+ *
+ * @since 3.7.0
+ */
+ protected function generateNewTitle($categoryId, $name, $title)
+ {
+ // Alter the title & name
+ $table = $this->getTable();
+
+ while ($table->load(array('name' => $name))) {
+ $title = StringHelper::increment($title);
+ $name = StringHelper::increment($name, 'dash');
+ }
+
+ return array(
+ $title,
+ $name,
+ );
+ }
+
+ /**
+ * Method to delete one or more records.
+ *
+ * @param array $pks An array of record primary keys.
+ *
+ * @return boolean True if successful, false if an error occurs.
+ *
+ * @since 3.7.0
+ */
+ public function delete(&$pks)
+ {
+ $success = parent::delete($pks);
+
+ if ($success) {
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+ $pks = array_filter($pks);
+
+ if (!empty($pks)) {
+ // Delete Values
+ $query = $this->getDatabase()->getQuery(true);
+
+ $query->delete($query->quoteName('#__fields_values'))
+ ->whereIn($query->quoteName('field_id'), $pks);
+
+ $this->getDatabase()->setQuery($query)->execute();
+
+ // Delete Assigned Categories
+ $query = $this->getDatabase()->getQuery(true);
+
+ $query->delete($query->quoteName('#__fields_categories'))
+ ->whereIn($query->quoteName('field_id'), $pks);
+
+ $this->getDatabase()->setQuery($query)->execute();
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Abstract method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 3.7.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $context = $this->getState('field.context');
+ $jinput = Factory::getApplication()->input;
+
+ // A workaround to get the context into the model for save requests.
+ if (empty($context) && isset($data['context'])) {
+ $context = $data['context'];
+ $parts = FieldsHelper::extract($context);
+
+ $this->setState('field.context', $context);
+
+ if ($parts) {
+ $this->setState('field.component', $parts[0]);
+ $this->setState('field.section', $parts[1]);
+ }
+ }
+
+ if (isset($data['type'])) {
+ // This is needed that the plugins can determine the type
+ $this->setState('field.type', $data['type']);
+ }
+
+ // Load the fields plugin that they can add additional parameters to the form
+ PluginHelper::importPlugin('fields');
+
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_fields.field.' . $context,
+ 'field',
+ array(
+ 'control' => 'jform',
+ 'load_data' => true,
+ )
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on Edit State access controls.
+ if (empty($data['context'])) {
+ $data['context'] = $context;
+ }
+
+ $fieldId = $jinput->get('id');
+ $assetKey = $this->state->get('field.component') . '.field.' . $fieldId;
+
+ if (!Factory::getUser()->authorise('core.edit.state', $assetKey)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('state', 'disabled', 'true');
+
+ // Disable fields while saving. The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_user_id user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_user_id', 'filter', 'unset');
+ }
+
+ // In case we are editing a field, field type cannot be changed, so some extra handling below is needed
+ if ($fieldId) {
+ $fieldType = $form->getField('type');
+
+ if ($fieldType->value == 'subform') {
+ // Only Use In subform should not be available for subform field type, so we remove it
+ $form->removeField('only_use_in_subform');
+ } else {
+ // Field type could not be changed, so remove showon attribute to avoid js errors
+ $form->setFieldAttribute('only_use_in_subform', 'showon', '');
+ }
+ }
+
+ return $form;
+ }
+
+ /**
+ * Setting the value for the given field id, context and item id.
+ *
+ * @param string $fieldId The field ID.
+ * @param string $itemId The ID of the item.
+ * @param string $value The value.
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ public function setFieldValue($fieldId, $itemId, $value)
+ {
+ $field = $this->getItem($fieldId);
+ $params = $field->params;
+
+ if (is_array($params)) {
+ $params = new Registry($params);
+ }
+
+ // Don't save the value when the user is not authorized to change it
+ if (!$field || !FieldsHelper::canEditFieldValue($field)) {
+ return false;
+ }
+
+ $needsDelete = false;
+ $needsInsert = false;
+ $needsUpdate = false;
+
+ $oldValue = $this->getFieldValue($fieldId, $itemId);
+ $value = (array) $value;
+
+ if ($oldValue === null) {
+ // No records available, doing normal insert
+ $needsInsert = true;
+ } elseif (count($value) == 1 && count((array) $oldValue) == 1) {
+ // Only a single row value update can be done when not empty
+ $needsUpdate = is_array($value[0]) ? count($value[0]) : strlen($value[0]);
+ $needsDelete = !$needsUpdate;
+ } else {
+ // Multiple values, we need to purge the data and do a new
+ // insert
+ $needsDelete = true;
+ $needsInsert = true;
+ }
+
+ if ($needsDelete) {
+ $fieldId = (int) $fieldId;
+
+ // Deleting the existing record as it is a reset
+ $query = $this->getDatabase()->getQuery(true);
+
+ $query->delete($query->quoteName('#__fields_values'))
+ ->where($query->quoteName('field_id') . ' = :fieldid')
+ ->where($query->quoteName('item_id') . ' = :itemid')
+ ->bind(':fieldid', $fieldId, ParameterType::INTEGER)
+ ->bind(':itemid', $itemId);
+
+ $this->getDatabase()->setQuery($query)->execute();
+ }
+
+ if ($needsInsert) {
+ $newObj = new \stdClass();
+
+ $newObj->field_id = (int) $fieldId;
+ $newObj->item_id = $itemId;
+
+ foreach ($value as $v) {
+ $newObj->value = $v;
+
+ $this->getDatabase()->insertObject('#__fields_values', $newObj);
+ }
+ }
+
+ if ($needsUpdate) {
+ $updateObj = new \stdClass();
+
+ $updateObj->field_id = (int) $fieldId;
+ $updateObj->item_id = $itemId;
+ $updateObj->value = reset($value);
+
+ $this->getDatabase()->updateObject('#__fields_values', $updateObj, array('field_id', 'item_id'));
+ }
+
+ $this->valueCache = array();
+ FieldsHelper::clearFieldsCache();
+
+ return true;
+ }
+
+ /**
+ * Returning the value for the given field id, context and item id.
+ *
+ * @param string $fieldId The field ID.
+ * @param string $itemId The ID of the item.
+ *
+ * @return NULL|string
+ *
+ * @since 3.7.0
+ */
+ public function getFieldValue($fieldId, $itemId)
+ {
+ $values = $this->getFieldValues(array($fieldId), $itemId);
+
+ if (array_key_exists($fieldId, $values)) {
+ return $values[$fieldId];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returning the values for the given field ids, context and item id.
+ *
+ * @param array $fieldIds The field Ids.
+ * @param string $itemId The ID of the item.
+ *
+ * @return NULL|array
+ *
+ * @since 3.7.0
+ */
+ public function getFieldValues(array $fieldIds, $itemId)
+ {
+ if (!$fieldIds) {
+ return array();
+ }
+
+ // Create a unique key for the cache
+ $key = md5(serialize($fieldIds) . $itemId);
+
+ // Fill the cache when it doesn't exist
+ if (!array_key_exists($key, $this->valueCache)) {
+ // Create the query
+ $query = $this->getDatabase()->getQuery(true);
+
+ $query->select($query->quoteName(['field_id', 'value']))
+ ->from($query->quoteName('#__fields_values'))
+ ->whereIn($query->quoteName('field_id'), ArrayHelper::toInteger($fieldIds))
+ ->where($query->quoteName('item_id') . ' = :itemid')
+ ->bind(':itemid', $itemId);
+
+ // Fetch the row from the database
+ $rows = $this->getDatabase()->setQuery($query)->loadObjectList();
+
+ $data = array();
+
+ // Fill the data container from the database rows
+ foreach ($rows as $row) {
+ // If there are multiple values for a field, create an array
+ if (array_key_exists($row->field_id, $data)) {
+ // Transform it to an array
+ if (!is_array($data[$row->field_id])) {
+ $data[$row->field_id] = array($data[$row->field_id]);
+ }
+
+ // Set the value in the array
+ $data[$row->field_id][] = $row->value;
+
+ // Go to the next row, otherwise the value gets overwritten in the data container
+ continue;
+ }
+
+ // Set the value
+ $data[$row->field_id] = $row->value;
+ }
+
+ // Assign it to the internal cache
+ $this->valueCache[$key] = $data;
+ }
+
+ // Return the value from the cache
+ return $this->valueCache[$key];
+ }
+
+ /**
+ * Cleaning up the values for the given item on the context.
+ *
+ * @param string $context The context.
+ * @param string $itemId The Item ID.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function cleanupValues($context, $itemId)
+ {
+ // Delete with inner join is not possible so we need to do a subquery
+ $fieldsQuery = $this->getDatabase()->getQuery(true);
+ $fieldsQuery->select($fieldsQuery->quoteName('id'))
+ ->from($fieldsQuery->quoteName('#__fields'))
+ ->where($fieldsQuery->quoteName('context') . ' = :context');
+
+ $query = $this->getDatabase()->getQuery(true);
+
+ $query->delete($query->quoteName('#__fields_values'))
+ ->where($query->quoteName('field_id') . ' IN (' . $fieldsQuery . ')')
+ ->where($query->quoteName('item_id') . ' = :itemid')
+ ->bind(':itemid', $itemId)
+ ->bind(':context', $context);
+
+ $this->getDatabase()->setQuery($query)->execute();
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
+ *
+ * @since 3.7.0
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->state != -2) {
+ return false;
+ }
+
+ $parts = FieldsHelper::extract($record->context);
+
+ return Factory::getUser()->authorise('core.delete', $parts[0] . '.field.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission for the
+ * component.
+ *
+ * @since 3.7.0
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+ $parts = FieldsHelper::extract($record->context);
+
+ // Check for existing field.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $parts[0] . '.field.' . (int) $record->id);
+ }
+
+ return $user->authorise('core.edit.state', $parts[0]);
+ }
+
+ /**
+ * Stock method to auto-populate the model state.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState($this->getName() . '.id', $pk);
+
+ $context = $app->input->get('context', 'com_content.article');
+ $this->setState('field.context', $context);
+ $parts = FieldsHelper::extract($context);
+
+ // Extract the component name
+ $this->setState('field.component', $parts[0]);
+
+ // Extract the optional section name
+ $this->setState('field.section', (count($parts) > 1) ? $parts[1] : null);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_fields');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param Table $table A Table object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 3.7.0
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('context') . ' = ' . $db->quote($table->context),
+ ];
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return array The default data is an empty array.
+ *
+ * @since 3.7.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $app = Factory::getApplication();
+ $data = $app->getUserState('com_fields.edit.field.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Pre-select some filters (Status, Language, Access) in edit form
+ // if those have been selected in Category Manager
+ if (!$data->id) {
+ // Check for which context the Category Manager is used and
+ // get selected fields
+ $filters = (array) $app->getUserState('com_fields.fields.filter');
+
+ $data->set('state', $app->input->getInt('state', ((isset($filters['state']) && $filters['state'] !== '') ? $filters['state'] : null)));
+ $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
+ $data->set('group_id', $app->input->getString('group_id', (!empty($filters['group_id']) ? $filters['group_id'] : null)));
+ $data->set(
+ 'access',
+ $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
+ );
+
+ // Set the type if available from the request
+ $data->set('type', $app->input->getWord('type', $this->state->get('field.type', $data->get('type'))));
+ }
+
+ if ($data->label && !isset($data->params['label'])) {
+ $data->params['label'] = $data->label;
+ }
+ }
+
+ $this->preprocessData('com_fields.field', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see JFormRule
+ * @see JFilterInput
+ * @since 3.9.23
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', 'com_fields')) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ *
+ * @throws \Exception if there is an error in the form event.
+ *
+ * @see \Joomla\CMS\Form\FormField
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $component = $this->state->get('field.component');
+ $section = $this->state->get('field.section');
+ $dataObject = $data;
+
+ if (is_array($dataObject)) {
+ $dataObject = (object) $dataObject;
+ }
+
+ if (isset($dataObject->type)) {
+ $form->setFieldAttribute('type', 'component', $component);
+
+ // Not allowed to change the type of an existing record
+ if ($dataObject->id) {
+ $form->setFieldAttribute('type', 'readonly', 'true');
+ }
+
+ // Allow to override the default value label and description through the plugin
+ $key = 'PLG_FIELDS_' . strtoupper($dataObject->type) . '_DEFAULT_VALUE_LABEL';
+
+ if (Factory::getLanguage()->hasKey($key)) {
+ $form->setFieldAttribute('default_value', 'label', $key);
+ }
+
+ $key = 'PLG_FIELDS_' . strtoupper($dataObject->type) . '_DEFAULT_VALUE_DESC';
+
+ if (Factory::getLanguage()->hasKey($key)) {
+ $form->setFieldAttribute('default_value', 'description', $key);
+ }
+
+ // Remove placeholder field on list fields
+ if ($dataObject->type == 'list') {
+ $form->removeField('hint', 'params');
+ }
+ }
+
+ // Get the categories for this component (and optionally this section, if available)
+ $cat = (
+ function () use ($component, $section) {
+ // Get the CategoryService for this component
+ $componentObject = $this->bootComponent($component);
+
+ if (!$componentObject instanceof CategoryServiceInterface) {
+ // No CategoryService -> no categories
+ return null;
+ }
+
+ $cat = null;
+
+ // Try to get the categories for this component and section
+ try {
+ $cat = $componentObject->getCategory([], $section ?: '');
+ } catch (SectionNotFoundException $e) {
+ // Not found for component and section -> Now try once more without the section, so only component
+ try {
+ $cat = $componentObject->getCategory();
+ } catch (SectionNotFoundException $e) {
+ // If we haven't found it now, return (no categories available for this component)
+ return null;
+ }
+ }
+
+ // So we found categories for at least the component, return them
+ return $cat;
+ }
+ )();
+
+ // If we found categories, and if the root category has children, set them in the form
+ if ($cat && $cat->get('root')->hasChildren()) {
+ $form->setFieldAttribute('assigned_cat_ids', 'extension', $cat->getExtension());
+ } else {
+ // Else remove the field from the form
+ $form->removeField('assigned_cat_ids');
+ }
+
+ $form->setFieldAttribute('type', 'component', $component);
+ $form->setFieldAttribute('group_id', 'context', $this->state->get('field.context'));
+ $form->setFieldAttribute('rules', 'component', $component);
+
+ // Looking in the component forms folder for a specific section forms file
+ $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/forms/fields/' . $section . '.xml');
+
+ if (!file_exists($path)) {
+ // Looking in the component models/forms folder for a specific section forms file
+ $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/fields/' . $section . '.xml');
+ }
+
+ if (file_exists($path)) {
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE);
+ $lang->load($component, JPATH_BASE . '/components/' . $component);
+
+ if (!$form->loadFile($path, false)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Clean the cache
+ *
+ * @param string $group The cache group
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ $context = Factory::getApplication()->input->get('context');
+
+ switch ($context) {
+ case 'com_content':
+ parent::cleanCache('com_content');
+ parent::cleanCache('mod_articles_archive');
+ parent::cleanCache('mod_articles_categories');
+ parent::cleanCache('mod_articles_category');
+ parent::cleanCache('mod_articles_latest');
+ parent::cleanCache('mod_articles_news');
+ parent::cleanCache('mod_articles_popular');
+ break;
+ default:
+ parent::cleanCache($context);
+ break;
+ }
+ }
+
+ /**
+ * Batch copy fields to a new group.
+ *
+ * @param integer $value The new value matching a fields group.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return array|boolean new IDs if successful, false otherwise and internal error is set.
+ *
+ * @since 3.7.0
+ */
+ protected function batchCopy($value, $pks, $contexts)
+ {
+ // Set the variables
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $newIds = array();
+ $component = $this->state->get('filter.component');
+ $value = (int) $value;
+
+ foreach ($pks as $pk) {
+ if ($user->authorise('core.create', $component . '.fieldgroup.' . $value)) {
+ $table->reset();
+ $table->load($pk);
+
+ $table->group_id = $value;
+
+ // Reset the ID because we are making a copy
+ $table->id = 0;
+
+ // Unpublish the new field
+ $table->state = 0;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Get the new item ID
+ $newId = $table->get('id');
+
+ // Add the new ID to the array
+ $newIds[$pk] = $newId;
+ } else {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return $newIds;
+ }
+
+ /**
+ * Batch move fields to a new group.
+ *
+ * @param integer $value The new value matching a fields group.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 3.7.0
+ */
+ protected function batchMove($value, $pks, $contexts)
+ {
+ // Set the variables
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $context = explode('.', Factory::getApplication()->getUserState('com_fields.fields.context'));
+ $value = (int) $value;
+
+ foreach ($pks as $pk) {
+ if ($user->authorise('core.edit', $context[0] . '.fieldgroup.' . $value)) {
+ $table->reset();
+ $table->load($pk);
+
+ $table->group_id = $value;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ } else {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
}
diff --git a/administrator/components/com_fields/src/Model/FieldsModel.php b/administrator/components/com_fields/src/Model/FieldsModel.php
index 9bcb237868609..13edfa9255709 100644
--- a/administrator/components/com_fields/src/Model/FieldsModel.php
+++ b/administrator/components/com_fields/src/Model/FieldsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.context', 'context', 'com_content.article', 'CMD');
- $this->setState('filter.context', $context);
-
- // Split context into component and optional section
- $parts = FieldsHelper::extract($context);
-
- if ($parts)
- {
- $this->setState('filter.component', $parts[0]);
- $this->setState('filter.section', $parts[1]);
- }
- }
-
- /**
- * Method to get a store id based on the model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id An identifier string to generate the store id.
- *
- * @return string A store id.
- *
- * @since 3.7.0
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.context');
- $id .= ':' . serialize($this->getState('filter.assigned_cat_ids'));
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.group_id');
- $id .= ':' . serialize($this->getState('filter.language'));
- $id .= ':' . $this->getState('filter.only_use_in_subform');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to get a DatabaseQuery object for retrieving the data set from a database.
- *
- * @return \Joomla\Database\DatabaseQuery A DatabaseQuery object to retrieve the data set.
- *
- * @since 3.7.0
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
- $app = Factory::getApplication();
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'DISTINCT a.id, a.title, a.name, a.checked_out, a.checked_out_time, a.note' .
- ', a.state, a.access, a.created_time, a.created_user_id, a.ordering, a.language' .
- ', a.fieldparams, a.params, a.type, a.default_value, a.context, a.group_id' .
- ', a.label, a.description, a.required, a.only_use_in_subform'
- )
- );
- $query->from('#__fields AS a');
-
- // Join over the language
- $query->select('l.title AS language_title, l.image AS language_image')
- ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language');
-
- // Join over the users for the checked out user.
- $query->select('uc.name AS editor')->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
-
- // Join over the asset groups.
- $query->select('ag.title AS access_level')->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
-
- // Join over the users for the author.
- $query->select('ua.name AS author_name')->join('LEFT', '#__users AS ua ON ua.id = a.created_user_id');
-
- // Join over the field groups.
- $query->select('g.title AS group_title, g.access as group_access, g.state AS group_state, g.note as group_note');
- $query->join('LEFT', '#__fields_groups AS g ON g.id = a.group_id');
-
- // Filter by context
- if ($context = $this->getState('filter.context'))
- {
- $query->where($db->quoteName('a.context') . ' = :context')
- ->bind(':context', $context);
- }
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- if (is_array($access))
- {
- $access = ArrayHelper::toInteger($access);
- $query->whereIn($db->quoteName('a.access'), $access);
- }
- else
- {
- $access = (int) $access;
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
- }
-
- if (($categories = $this->getState('filter.assigned_cat_ids')) && $context)
- {
- $categories = (array) $categories;
- $categories = ArrayHelper::toInteger($categories);
- $parts = FieldsHelper::extract($context);
-
- if ($parts)
- {
- // Get the categories for this component (and optionally this section, if available)
- $cat = (
- function () use ($parts) {
- // Get the CategoryService for this component
- $componentObject = $this->bootComponent($parts[0]);
-
- if (!$componentObject instanceof CategoryServiceInterface)
- {
- // No CategoryService -> no categories
- return null;
- }
-
- $cat = null;
-
- // Try to get the categories for this component and section
- try
- {
- $cat = $componentObject->getCategory([], $parts[1] ?: '');
- }
- catch (SectionNotFoundException $e)
- {
- // Not found for component and section -> Now try once more without the section, so only component
- try
- {
- $cat = $componentObject->getCategory();
- }
- catch (SectionNotFoundException $e)
- {
- // If we haven't found it now, return (no categories available for this component)
- return null;
- }
- }
-
- // So we found categories for at least the component, return them
- return $cat;
- }
- )();
-
- if ($cat)
- {
- foreach ($categories as $assignedCatIds)
- {
- // Check if we have the actual category
- $parent = $cat->get($assignedCatIds);
-
- if ($parent)
- {
- $categories[] = (int) $parent->id;
-
- // Traverse the tree up to get all the fields which are attached to a parent
- while ($parent->getParent() && $parent->getParent()->id != 'root')
- {
- $parent = $parent->getParent();
- $categories[] = (int) $parent->id;
- }
- }
- }
- }
- }
-
- $categories = array_unique($categories);
-
- // Join over the assigned categories
- $query->join('LEFT', $db->quoteName('#__fields_categories') . ' AS fc ON fc.field_id = a.id');
-
- if (in_array('0', $categories))
- {
- $query->where(
- '(' .
- $db->quoteName('fc.category_id') . ' IS NULL OR ' .
- $db->quoteName('fc.category_id') . ' IN (' . implode(',', $query->bindArray(array_values($categories), ParameterType::INTEGER)) . ')' .
- ')'
- );
- }
- else
- {
- $query->whereIn($db->quoteName('fc.category_id'), $categories);
- }
- }
-
- // Implement View Level Access
- if (!$app->isClient('administrator') || !$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.group_id') . ' = 0',
- $db->quoteName('g.access') . ' IN (' . implode(',', $query->bindArray($groups, ParameterType::INTEGER)) . ')'
- ],
- 'OR'
- );
- }
-
- // Filter by state
- $state = $this->getState('filter.state');
-
- // Include group state only when not on on back end list
- $includeGroupState = !$app->isClient('administrator') ||
- $app->input->get('option') != 'com_fields' ||
- $app->input->get('view') != 'fields';
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
-
- if ($includeGroupState)
- {
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.group_id') . ' = 0',
- $db->quoteName('g.state') . ' = :gstate',
- ],
- 'OR'
- )
- ->bind(':gstate', $state, ParameterType::INTEGER);
- }
- }
- elseif (!$state)
- {
- $query->whereIn($db->quoteName('a.state'), [0, 1]);
-
- if ($includeGroupState)
- {
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.group_id') . ' = 0',
- $db->quoteName('g.state') . ' IN (' . implode(',', $query->bindArray([0, 1], ParameterType::INTEGER)) . ')'
- ],
- 'OR'
- );
- }
- }
-
- $groupId = $this->getState('filter.group_id');
-
- if (is_numeric($groupId))
- {
- $groupId = (int) $groupId;
- $query->where($db->quoteName('a.group_id') . ' = :groupid')
- ->bind(':groupid', $groupId, ParameterType::INTEGER);
- }
-
- $onlyUseInSubForm = $this->getState('filter.only_use_in_subform');
-
- if (is_numeric($onlyUseInSubForm))
- {
- $onlyUseInSubForm = (int) $onlyUseInSubForm;
- $query->where($db->quoteName('a.only_use_in_subform') . ' = :only_use_in_subform')
- ->bind(':only_use_in_subform', $onlyUseInSubForm, ParameterType::INTEGER);
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $search, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'author:') === 0)
- {
- $search = '%' . substr($search, 7) . '%';
- $query->where(
- '(' .
- $db->quoteName('ua.name') . ' LIKE :name OR ' .
- $db->quoteName('ua.username') . ' LIKE :username' .
- ')'
- )
- ->bind(':name', $search)
- ->bind(':username', $search);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where(
- '(' .
- $db->quoteName('a.title') . ' LIKE :title OR ' .
- $db->quoteName('a.name') . ' LIKE :sname OR ' .
- $db->quoteName('a.note') . ' LIKE :note' .
- ')'
- )
- ->bind(':title', $search)
- ->bind(':sname', $search)
- ->bind(':note', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $language = (array) $language;
-
- $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
- }
-
- // Add the list ordering clause
- $listOrdering = $this->state->get('list.ordering', 'a.ordering');
- $orderDirn = $this->state->get('list.direction', 'ASC');
-
- $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));
-
- return $query;
- }
-
- /**
- * Gets an array of objects from the results of database query.
- *
- * @param string $query The query.
- * @param integer $limitstart Offset.
- * @param integer $limit The number of records.
- *
- * @return array An array of results.
- *
- * @since 3.7.0
- * @throws \RuntimeException
- */
- protected function _getList($query, $limitstart = 0, $limit = 0)
- {
- $result = parent::_getList($query, $limitstart, $limit);
-
- if (is_array($result))
- {
- foreach ($result as $field)
- {
- $field->fieldparams = new Registry($field->fieldparams);
- $field->params = new Registry($field->params);
- }
- }
-
- return $result;
- }
-
- /**
- * Get the filter form
- *
- * @param array $data data
- * @param boolean $loadData load current data
- *
- * @return \Joomla\CMS\Form\Form|bool the Form object or false
- *
- * @since 3.7.0
- */
- public function getFilterForm($data = array(), $loadData = true)
- {
- $form = parent::getFilterForm($data, $loadData);
-
- if ($form)
- {
- $form->setValue('context', null, $this->getState('filter.context'));
- $form->setFieldAttribute('group_id', 'context', $this->getState('filter.context'), 'filter');
- $form->setFieldAttribute('assigned_cat_ids', 'extension', $this->state->get('filter.component'), 'filter');
- }
-
- return $form;
- }
-
- /**
- * Get the groups for the batch method
- *
- * @return array An array of groups
- *
- * @since 3.7.0
- */
- public function getGroups()
- {
- $user = Factory::getUser();
- $viewlevels = ArrayHelper::toInteger($user->getAuthorisedViewLevels());
- $context = $this->state->get('filter.context');
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('title', 'text'),
- $db->quoteName('id', 'value'),
- $db->quoteName('state'),
- ]
- );
- $query->from($db->quoteName('#__fields_groups'));
- $query->whereIn($db->quoteName('state'), [0, 1]);
- $query->where($db->quoteName('context') . ' = :context');
- $query->whereIn($db->quoteName('access'), $viewlevels);
- $query->bind(':context', $context);
-
- $db->setQuery($query);
-
- return $db->loadObjectList();
- }
+ /**
+ * Constructor
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'type', 'a.type',
+ 'name', 'a.name',
+ 'state', 'a.state',
+ 'access', 'a.access',
+ 'access_level',
+ 'only_use_in_subform',
+ 'language', 'a.language',
+ 'ordering', 'a.ordering',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'created_time', 'a.created_time',
+ 'created_user_id', 'a.created_user_id',
+ 'group_title', 'g.title',
+ 'category_id', 'a.category_id',
+ 'group_id', 'a.group_id',
+ 'assigned_cat_ids'
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function populateState($ordering = null, $direction = null)
+ {
+ // List state information.
+ parent::populateState('a.ordering', 'asc');
+
+ $context = $this->getUserStateFromRequest($this->context . '.context', 'context', 'com_content.article', 'CMD');
+ $this->setState('filter.context', $context);
+
+ // Split context into component and optional section
+ $parts = FieldsHelper::extract($context);
+
+ if ($parts) {
+ $this->setState('filter.component', $parts[0]);
+ $this->setState('filter.section', $parts[1]);
+ }
+ }
+
+ /**
+ * Method to get a store id based on the model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id An identifier string to generate the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 3.7.0
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.context');
+ $id .= ':' . serialize($this->getState('filter.assigned_cat_ids'));
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.group_id');
+ $id .= ':' . serialize($this->getState('filter.language'));
+ $id .= ':' . $this->getState('filter.only_use_in_subform');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to get a DatabaseQuery object for retrieving the data set from a database.
+ *
+ * @return \Joomla\Database\DatabaseQuery A DatabaseQuery object to retrieve the data set.
+ *
+ * @since 3.7.0
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+ $app = Factory::getApplication();
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'DISTINCT a.id, a.title, a.name, a.checked_out, a.checked_out_time, a.note' .
+ ', a.state, a.access, a.created_time, a.created_user_id, a.ordering, a.language' .
+ ', a.fieldparams, a.params, a.type, a.default_value, a.context, a.group_id' .
+ ', a.label, a.description, a.required, a.only_use_in_subform'
+ )
+ );
+ $query->from('#__fields AS a');
+
+ // Join over the language
+ $query->select('l.title AS language_title, l.image AS language_image')
+ ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language');
+
+ // Join over the users for the checked out user.
+ $query->select('uc.name AS editor')->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
+
+ // Join over the asset groups.
+ $query->select('ag.title AS access_level')->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
+
+ // Join over the users for the author.
+ $query->select('ua.name AS author_name')->join('LEFT', '#__users AS ua ON ua.id = a.created_user_id');
+
+ // Join over the field groups.
+ $query->select('g.title AS group_title, g.access as group_access, g.state AS group_state, g.note as group_note');
+ $query->join('LEFT', '#__fields_groups AS g ON g.id = a.group_id');
+
+ // Filter by context
+ if ($context = $this->getState('filter.context')) {
+ $query->where($db->quoteName('a.context') . ' = :context')
+ ->bind(':context', $context);
+ }
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ if (is_array($access)) {
+ $access = ArrayHelper::toInteger($access);
+ $query->whereIn($db->quoteName('a.access'), $access);
+ } else {
+ $access = (int) $access;
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+ }
+
+ if (($categories = $this->getState('filter.assigned_cat_ids')) && $context) {
+ $categories = (array) $categories;
+ $categories = ArrayHelper::toInteger($categories);
+ $parts = FieldsHelper::extract($context);
+
+ if ($parts) {
+ // Get the categories for this component (and optionally this section, if available)
+ $cat = (
+ function () use ($parts) {
+ // Get the CategoryService for this component
+ $componentObject = $this->bootComponent($parts[0]);
+
+ if (!$componentObject instanceof CategoryServiceInterface) {
+ // No CategoryService -> no categories
+ return null;
+ }
+
+ $cat = null;
+
+ // Try to get the categories for this component and section
+ try {
+ $cat = $componentObject->getCategory([], $parts[1] ?: '');
+ } catch (SectionNotFoundException $e) {
+ // Not found for component and section -> Now try once more without the section, so only component
+ try {
+ $cat = $componentObject->getCategory();
+ } catch (SectionNotFoundException $e) {
+ // If we haven't found it now, return (no categories available for this component)
+ return null;
+ }
+ }
+
+ // So we found categories for at least the component, return them
+ return $cat;
+ }
+ )();
+
+ if ($cat) {
+ foreach ($categories as $assignedCatIds) {
+ // Check if we have the actual category
+ $parent = $cat->get($assignedCatIds);
+
+ if ($parent) {
+ $categories[] = (int) $parent->id;
+
+ // Traverse the tree up to get all the fields which are attached to a parent
+ while ($parent->getParent() && $parent->getParent()->id != 'root') {
+ $parent = $parent->getParent();
+ $categories[] = (int) $parent->id;
+ }
+ }
+ }
+ }
+ }
+
+ $categories = array_unique($categories);
+
+ // Join over the assigned categories
+ $query->join('LEFT', $db->quoteName('#__fields_categories') . ' AS fc ON fc.field_id = a.id');
+
+ if (in_array('0', $categories)) {
+ $query->where(
+ '(' .
+ $db->quoteName('fc.category_id') . ' IS NULL OR ' .
+ $db->quoteName('fc.category_id') . ' IN (' . implode(',', $query->bindArray(array_values($categories), ParameterType::INTEGER)) . ')' .
+ ')'
+ );
+ } else {
+ $query->whereIn($db->quoteName('fc.category_id'), $categories);
+ }
+ }
+
+ // Implement View Level Access
+ if (!$app->isClient('administrator') || !$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.group_id') . ' = 0',
+ $db->quoteName('g.access') . ' IN (' . implode(',', $query->bindArray($groups, ParameterType::INTEGER)) . ')'
+ ],
+ 'OR'
+ );
+ }
+
+ // Filter by state
+ $state = $this->getState('filter.state');
+
+ // Include group state only when not on on back end list
+ $includeGroupState = !$app->isClient('administrator') ||
+ $app->input->get('option') != 'com_fields' ||
+ $app->input->get('view') != 'fields';
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+
+ if ($includeGroupState) {
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.group_id') . ' = 0',
+ $db->quoteName('g.state') . ' = :gstate',
+ ],
+ 'OR'
+ )
+ ->bind(':gstate', $state, ParameterType::INTEGER);
+ }
+ } elseif (!$state) {
+ $query->whereIn($db->quoteName('a.state'), [0, 1]);
+
+ if ($includeGroupState) {
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.group_id') . ' = 0',
+ $db->quoteName('g.state') . ' IN (' . implode(',', $query->bindArray([0, 1], ParameterType::INTEGER)) . ')'
+ ],
+ 'OR'
+ );
+ }
+ }
+
+ $groupId = $this->getState('filter.group_id');
+
+ if (is_numeric($groupId)) {
+ $groupId = (int) $groupId;
+ $query->where($db->quoteName('a.group_id') . ' = :groupid')
+ ->bind(':groupid', $groupId, ParameterType::INTEGER);
+ }
+
+ $onlyUseInSubForm = $this->getState('filter.only_use_in_subform');
+
+ if (is_numeric($onlyUseInSubForm)) {
+ $onlyUseInSubForm = (int) $onlyUseInSubForm;
+ $query->where($db->quoteName('a.only_use_in_subform') . ' = :only_use_in_subform')
+ ->bind(':only_use_in_subform', $onlyUseInSubForm, ParameterType::INTEGER);
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $search, ParameterType::INTEGER);
+ } elseif (stripos($search, 'author:') === 0) {
+ $search = '%' . substr($search, 7) . '%';
+ $query->where(
+ '(' .
+ $db->quoteName('ua.name') . ' LIKE :name OR ' .
+ $db->quoteName('ua.username') . ' LIKE :username' .
+ ')'
+ )
+ ->bind(':name', $search)
+ ->bind(':username', $search);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where(
+ '(' .
+ $db->quoteName('a.title') . ' LIKE :title OR ' .
+ $db->quoteName('a.name') . ' LIKE :sname OR ' .
+ $db->quoteName('a.note') . ' LIKE :note' .
+ ')'
+ )
+ ->bind(':title', $search)
+ ->bind(':sname', $search)
+ ->bind(':note', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $language = (array) $language;
+
+ $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
+ }
+
+ // Add the list ordering clause
+ $listOrdering = $this->state->get('list.ordering', 'a.ordering');
+ $orderDirn = $this->state->get('list.direction', 'ASC');
+
+ $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));
+
+ return $query;
+ }
+
+ /**
+ * Gets an array of objects from the results of database query.
+ *
+ * @param string $query The query.
+ * @param integer $limitstart Offset.
+ * @param integer $limit The number of records.
+ *
+ * @return array An array of results.
+ *
+ * @since 3.7.0
+ * @throws \RuntimeException
+ */
+ protected function _getList($query, $limitstart = 0, $limit = 0)
+ {
+ $result = parent::_getList($query, $limitstart, $limit);
+
+ if (is_array($result)) {
+ foreach ($result as $field) {
+ $field->fieldparams = new Registry($field->fieldparams);
+ $field->params = new Registry($field->params);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return \Joomla\CMS\Form\Form|bool the Form object or false
+ *
+ * @since 3.7.0
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ if ($form) {
+ $form->setValue('context', null, $this->getState('filter.context'));
+ $form->setFieldAttribute('group_id', 'context', $this->getState('filter.context'), 'filter');
+ $form->setFieldAttribute('assigned_cat_ids', 'extension', $this->state->get('filter.component'), 'filter');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Get the groups for the batch method
+ *
+ * @return array An array of groups
+ *
+ * @since 3.7.0
+ */
+ public function getGroups()
+ {
+ $user = Factory::getUser();
+ $viewlevels = ArrayHelper::toInteger($user->getAuthorisedViewLevels());
+ $context = $this->state->get('filter.context');
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('title', 'text'),
+ $db->quoteName('id', 'value'),
+ $db->quoteName('state'),
+ ]
+ );
+ $query->from($db->quoteName('#__fields_groups'));
+ $query->whereIn($db->quoteName('state'), [0, 1]);
+ $query->where($db->quoteName('context') . ' = :context');
+ $query->whereIn($db->quoteName('access'), $viewlevels);
+ $query->bind(':context', $context);
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList();
+ }
}
diff --git a/administrator/components/com_fields/src/Model/GroupModel.php b/administrator/components/com_fields/src/Model/GroupModel.php
index 81c1c2b4947cc..58a529d5f464b 100644
--- a/administrator/components/com_fields/src/Model/GroupModel.php
+++ b/administrator/components/com_fields/src/Model/GroupModel.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage'
- );
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success, False on error.
- *
- * @since 3.7.0
- */
- public function save($data)
- {
- // Alter the title for save as copy
- $input = Factory::getApplication()->input;
-
- // Save new group as unpublished
- if ($input->get('task') == 'save2copy')
- {
- $data['state'] = 0;
- }
-
- return parent::save($data);
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $name The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $options Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @since 3.7.0
- * @throws \Exception
- */
- public function getTable($name = 'Group', $prefix = 'Administrator', $options = array())
- {
- return parent::getTable($name, $prefix, $options);
- }
-
- /**
- * Abstract method for getting the form from the model.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return mixed A Form object on success, false on failure
- *
- * @since 3.7.0
- */
- public function getForm($data = array(), $loadData = true)
- {
- $context = $this->getState('filter.context');
- $jinput = Factory::getApplication()->input;
-
- if (empty($context) && isset($data['context']))
- {
- $context = $data['context'];
- $this->setState('filter.context', $context);
- }
-
- // Get the form.
- $form = $this->loadForm(
- 'com_fields.group.' . $context, 'group',
- array(
- 'control' => 'jform',
- 'load_data' => $loadData,
- )
- );
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on Edit State access controls.
- if (empty($data['context']))
- {
- $data['context'] = $context;
- }
-
- $user = Factory::getUser();
-
- if (!$user->authorise('core.edit.state', $context . '.fieldgroup.' . $jinput->get('id')))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('state', 'disabled', 'true');
-
- // Disable fields while saving. The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('state', 'filter', 'unset');
- }
-
- // Don't allow to change the created_by user if not allowed to access com_users.
- if (!$user->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_by', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
- *
- * @since 3.7.0
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->state != -2)
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.delete', $record->context . '.fieldgroup.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission for the
- * component.
- *
- * @since 3.7.0
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
-
- // Check for existing fieldgroup.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $record->context . '.fieldgroup.' . (int) $record->id);
- }
-
- // Default to component settings.
- return $user->authorise('core.edit.state', $record->context);
- }
-
- /**
- * Auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function populateState()
- {
- parent::populateState();
-
- $context = Factory::getApplication()->getUserStateFromRequest('com_fields.groups.context', 'context', 'com_fields', 'CMD');
- $this->setState('filter.context', $context);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param Table $table A Table object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 3.7.0
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('context') . ' = ' . $db->quote($table->context),
- ];
- }
-
- /**
- * Method to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @see \Joomla\CMS\Form\FormField
- * @since 3.7.0
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- parent::preprocessForm($form, $data, $group);
-
- $parts = FieldsHelper::extract($this->state->get('filter.context'));
-
- // Extract the component name
- $component = $parts[0];
-
- // Extract the optional section name
- $section = (count($parts) > 1) ? $parts[1] : null;
-
- if ($parts)
- {
- // Set the access control rules field component value.
- $form->setFieldAttribute('rules', 'component', $component);
- }
-
- if ($section !== null)
- {
- // Looking first in the component models/forms folder
- $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/fieldgroup/' . $section . '.xml');
-
- if (file_exists($path))
- {
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_BASE);
- $lang->load($component, JPATH_BASE . '/components/' . $component);
-
- if (!$form->loadFile($path, false))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
- }
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see JFormRule
- * @see JFilterInput
- * @since 3.9.23
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', 'com_fields'))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return array The default data is an empty array.
- *
- * @since 3.7.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $app = Factory::getApplication();
- $data = $app->getUserState('com_fields.edit.group.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Field Group Manager
- if (!$data->id)
- {
- // Check for which context the Field Group Manager is used and get selected fields
- $context = substr($app->getUserState('com_fields.groups.filter.context', ''), 4);
- $filters = (array) $app->getUserState('com_fields.groups.' . $context . '.filter');
-
- $data->set(
- 'state',
- $app->input->getInt('state', (!empty($filters['state']) ? $filters['state'] : null))
- );
- $data->set(
- 'language',
- $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null))
- );
- $data->set(
- 'access',
- $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
- );
- }
- }
-
- $this->preprocessData('com_fields.group', $data);
-
- return $data;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 3.7.0
- */
- public function getItem($pk = null)
- {
- if ($item = parent::getItem($pk))
- {
- // Prime required properties.
- if (empty($item->id))
- {
- $item->context = $this->getState('filter.context');
- }
-
- if (property_exists($item, 'params'))
- {
- $item->params = new Registry($item->params);
- }
- }
-
- return $item;
- }
-
- /**
- * Clean the cache
- *
- * @param string $group The cache group
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- $context = Factory::getApplication()->input->get('context');
-
- parent::cleanCache($context);
- }
+ /**
+ * @var null|string
+ *
+ * @since 3.7.0
+ */
+ public $typeAlias = null;
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage'
+ );
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success, False on error.
+ *
+ * @since 3.7.0
+ */
+ public function save($data)
+ {
+ // Alter the title for save as copy
+ $input = Factory::getApplication()->input;
+
+ // Save new group as unpublished
+ if ($input->get('task') == 'save2copy') {
+ $data['state'] = 0;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $name The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $options Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ */
+ public function getTable($name = 'Group', $prefix = 'Administrator', $options = array())
+ {
+ return parent::getTable($name, $prefix, $options);
+ }
+
+ /**
+ * Abstract method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return mixed A Form object on success, false on failure
+ *
+ * @since 3.7.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $context = $this->getState('filter.context');
+ $jinput = Factory::getApplication()->input;
+
+ if (empty($context) && isset($data['context'])) {
+ $context = $data['context'];
+ $this->setState('filter.context', $context);
+ }
+
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_fields.group.' . $context,
+ 'group',
+ array(
+ 'control' => 'jform',
+ 'load_data' => $loadData,
+ )
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on Edit State access controls.
+ if (empty($data['context'])) {
+ $data['context'] = $context;
+ }
+
+ $user = Factory::getUser();
+
+ if (!$user->authorise('core.edit.state', $context . '.fieldgroup.' . $jinput->get('id'))) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('state', 'disabled', 'true');
+
+ // Disable fields while saving. The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!$user->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
+ *
+ * @since 3.7.0
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->state != -2) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.delete', $record->context . '.fieldgroup.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission for the
+ * component.
+ *
+ * @since 3.7.0
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+
+ // Check for existing fieldgroup.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $record->context . '.fieldgroup.' . (int) $record->id);
+ }
+
+ // Default to component settings.
+ return $user->authorise('core.edit.state', $record->context);
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $context = Factory::getApplication()->getUserStateFromRequest('com_fields.groups.context', 'context', 'com_fields', 'CMD');
+ $this->setState('filter.context', $context);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param Table $table A Table object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 3.7.0
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('context') . ' = ' . $db->quote($table->context),
+ ];
+ }
+
+ /**
+ * Method to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @see \Joomla\CMS\Form\FormField
+ * @since 3.7.0
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ parent::preprocessForm($form, $data, $group);
+
+ $parts = FieldsHelper::extract($this->state->get('filter.context'));
+
+ // Extract the component name
+ $component = $parts[0];
+
+ // Extract the optional section name
+ $section = (count($parts) > 1) ? $parts[1] : null;
+
+ if ($parts) {
+ // Set the access control rules field component value.
+ $form->setFieldAttribute('rules', 'component', $component);
+ }
+
+ if ($section !== null) {
+ // Looking first in the component models/forms folder
+ $path = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/models/forms/fieldgroup/' . $section . '.xml');
+
+ if (file_exists($path)) {
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_BASE);
+ $lang->load($component, JPATH_BASE . '/components/' . $component);
+
+ if (!$form->loadFile($path, false)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see JFormRule
+ * @see JFilterInput
+ * @since 3.9.23
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', 'com_fields')) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return array The default data is an empty array.
+ *
+ * @since 3.7.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $app = Factory::getApplication();
+ $data = $app->getUserState('com_fields.edit.group.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Pre-select some filters (Status, Language, Access) in edit form if those have been selected in Field Group Manager
+ if (!$data->id) {
+ // Check for which context the Field Group Manager is used and get selected fields
+ $context = substr($app->getUserState('com_fields.groups.filter.context', ''), 4);
+ $filters = (array) $app->getUserState('com_fields.groups.' . $context . '.filter');
+
+ $data->set(
+ 'state',
+ $app->input->getInt('state', (!empty($filters['state']) ? $filters['state'] : null))
+ );
+ $data->set(
+ 'language',
+ $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null))
+ );
+ $data->set(
+ 'access',
+ $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access')))
+ );
+ }
+ }
+
+ $this->preprocessData('com_fields.group', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 3.7.0
+ */
+ public function getItem($pk = null)
+ {
+ if ($item = parent::getItem($pk)) {
+ // Prime required properties.
+ if (empty($item->id)) {
+ $item->context = $this->getState('filter.context');
+ }
+
+ if (property_exists($item, 'params')) {
+ $item->params = new Registry($item->params);
+ }
+ }
+
+ return $item;
+ }
+
+ /**
+ * Clean the cache
+ *
+ * @param string $group The cache group
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ $context = Factory::getApplication()->input->get('context');
+
+ parent::cleanCache($context);
+ }
}
diff --git a/administrator/components/com_fields/src/Model/GroupsModel.php b/administrator/components/com_fields/src/Model/GroupsModel.php
index 2669f0834171e..09b555d1d885d 100644
--- a/administrator/components/com_fields/src/Model/GroupsModel.php
+++ b/administrator/components/com_fields/src/Model/GroupsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.context', 'context', 'com_content', 'CMD');
- $this->setState('filter.context', $context);
- }
-
- /**
- * Method to get a store id based on the model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id An identifier string to generate the store id.
- *
- * @return string A store id.
- *
- * @since 3.7.0
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.context');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . print_r($this->getState('filter.language'), true);
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to get a DatabaseQuery object for retrieving the data set from a database.
- *
- * @return \Joomla\Database\DatabaseQuery A DatabaseQuery object to retrieve the data set.
- *
- * @since 3.7.0
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- // Select the required fields from the table.
- $query->select($this->getState('list.select', 'a.*'));
- $query->from('#__fields_groups AS a');
-
- // Join over the language
- $query->select('l.title AS language_title, l.image AS language_image')
- ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language');
-
- // Join over the users for the checked out user.
- $query->select('uc.name AS editor')->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
-
- // Join over the asset groups.
- $query->select('ag.title AS access_level')->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
-
- // Join over the users for the author.
- $query->select('ua.name AS author_name')->join('LEFT', '#__users AS ua ON ua.id = a.created_by');
-
- // Filter by context
- if ($context = $this->getState('filter.context', 'com_fields'))
- {
- $query->where($db->quoteName('a.context') . ' = :context')
- ->bind(':context', $context);
- }
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- if (is_array($access))
- {
- $access = ArrayHelper::toInteger($access);
- $query->whereIn($db->quoteName('a.access'), $access);
- }
- else
- {
- $access = (int) $access;
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
-
- // Filter by published state
- $state = $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif (!$state)
- {
- $query->whereIn($db->quoteName('a.state'), [0, 1]);
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':id', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where($db->quoteName('a.title') . ' LIKE :search')
- ->bind(':search', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $language = (array) $language;
-
- $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
- }
-
- // Add the list ordering clause
- $listOrdering = $this->getState('list.ordering', 'a.ordering');
- $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
-
- $query->order($db->escape($listOrdering) . ' ' . $listDirn);
-
- return $query;
- }
-
- /**
- * Gets an array of objects from the results of database query.
- *
- * @param string $query The query.
- * @param integer $limitstart Offset.
- * @param integer $limit The number of records.
- *
- * @return array An array of results.
- *
- * @since 3.8.7
- * @throws \RuntimeException
- */
- protected function _getList($query, $limitstart = 0, $limit = 0)
- {
- $result = parent::_getList($query, $limitstart, $limit);
-
- if (is_array($result))
- {
- foreach ($result as $group)
- {
- $group->params = new Registry($group->params);
- }
- }
-
- return $result;
- }
+ /**
+ * Context string for the model type. This is used to handle uniqueness
+ * when dealing with the getStoreId() method and caching data structures.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $context = 'com_fields.groups';
+
+ /**
+ * Constructor
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'type', 'a.type',
+ 'state', 'a.state',
+ 'access', 'a.access',
+ 'access_level',
+ 'language', 'a.language',
+ 'ordering', 'a.ordering',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'created', 'a.created',
+ 'created_by', 'a.created_by',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function populateState($ordering = null, $direction = null)
+ {
+ // List state information.
+ parent::populateState('a.ordering', 'asc');
+
+ $context = $this->getUserStateFromRequest($this->context . '.context', 'context', 'com_content', 'CMD');
+ $this->setState('filter.context', $context);
+ }
+
+ /**
+ * Method to get a store id based on the model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id An identifier string to generate the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 3.7.0
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.context');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . print_r($this->getState('filter.language'), true);
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to get a DatabaseQuery object for retrieving the data set from a database.
+ *
+ * @return \Joomla\Database\DatabaseQuery A DatabaseQuery object to retrieve the data set.
+ *
+ * @since 3.7.0
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ // Select the required fields from the table.
+ $query->select($this->getState('list.select', 'a.*'));
+ $query->from('#__fields_groups AS a');
+
+ // Join over the language
+ $query->select('l.title AS language_title, l.image AS language_image')
+ ->join('LEFT', $db->quoteName('#__languages') . ' AS l ON l.lang_code = a.language');
+
+ // Join over the users for the checked out user.
+ $query->select('uc.name AS editor')->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
+
+ // Join over the asset groups.
+ $query->select('ag.title AS access_level')->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
+
+ // Join over the users for the author.
+ $query->select('ua.name AS author_name')->join('LEFT', '#__users AS ua ON ua.id = a.created_by');
+
+ // Filter by context
+ if ($context = $this->getState('filter.context', 'com_fields')) {
+ $query->where($db->quoteName('a.context') . ' = :context')
+ ->bind(':context', $context);
+ }
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ if (is_array($access)) {
+ $access = ArrayHelper::toInteger($access);
+ $query->whereIn($db->quoteName('a.access'), $access);
+ } else {
+ $access = (int) $access;
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+
+ // Filter by published state
+ $state = $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif (!$state) {
+ $query->whereIn($db->quoteName('a.state'), [0, 1]);
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':id', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where($db->quoteName('a.title') . ' LIKE :search')
+ ->bind(':search', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $language = (array) $language;
+
+ $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
+ }
+
+ // Add the list ordering clause
+ $listOrdering = $this->getState('list.ordering', 'a.ordering');
+ $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
+
+ $query->order($db->escape($listOrdering) . ' ' . $listDirn);
+
+ return $query;
+ }
+
+ /**
+ * Gets an array of objects from the results of database query.
+ *
+ * @param string $query The query.
+ * @param integer $limitstart Offset.
+ * @param integer $limit The number of records.
+ *
+ * @return array An array of results.
+ *
+ * @since 3.8.7
+ * @throws \RuntimeException
+ */
+ protected function _getList($query, $limitstart = 0, $limit = 0)
+ {
+ $result = parent::_getList($query, $limitstart, $limit);
+
+ if (is_array($result)) {
+ foreach ($result as $group) {
+ $group->params = new Registry($group->params);
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/administrator/components/com_fields/src/Plugin/FieldsListPlugin.php b/administrator/components/com_fields/src/Plugin/FieldsListPlugin.php
index 4d112e36f9a76..5a67a785989ff 100644
--- a/administrator/components/com_fields/src/Plugin/FieldsListPlugin.php
+++ b/administrator/components/com_fields/src/Plugin/FieldsListPlugin.php
@@ -1,4 +1,5 @@
setAttribute('validate', 'options');
+ $fieldNode->setAttribute('validate', 'options');
- foreach ($this->getOptionsFromField($field) as $value => $name)
- {
- $option = new \DOMElement('option', htmlspecialchars($value, ENT_COMPAT, 'UTF-8'));
- $option->textContent = htmlspecialchars(Text::_($name), ENT_COMPAT, 'UTF-8');
+ foreach ($this->getOptionsFromField($field) as $value => $name) {
+ $option = new \DOMElement('option', htmlspecialchars($value, ENT_COMPAT, 'UTF-8'));
+ $option->textContent = htmlspecialchars(Text::_($name), ENT_COMPAT, 'UTF-8');
- $element = $fieldNode->appendChild($option);
- $element->setAttribute('value', $value);
- }
+ $element = $fieldNode->appendChild($option);
+ $element->setAttribute('value', $value);
+ }
- return $fieldNode;
- }
+ return $fieldNode;
+ }
- /**
- * Returns an array of key values to put in a list from the given field.
- *
- * @param \stdClass $field The field.
- *
- * @return array
- *
- * @since 3.7.0
- */
- public function getOptionsFromField($field)
- {
- $data = array();
+ /**
+ * Returns an array of key values to put in a list from the given field.
+ *
+ * @param \stdClass $field The field.
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public function getOptionsFromField($field)
+ {
+ $data = array();
- // Fetch the options from the plugin
- $params = clone $this->params;
- $params->merge($field->fieldparams);
+ // Fetch the options from the plugin
+ $params = clone $this->params;
+ $params->merge($field->fieldparams);
- foreach ($params->get('options', array()) as $option)
- {
- $op = (object) $option;
- $data[$op->value] = $op->name;
- }
+ foreach ($params->get('options', array()) as $option) {
+ $op = (object) $option;
+ $data[$op->value] = $op->name;
+ }
- return $data;
- }
+ return $data;
+ }
}
diff --git a/administrator/components/com_fields/src/Plugin/FieldsPlugin.php b/administrator/components/com_fields/src/Plugin/FieldsPlugin.php
index 5d2554d0f171e..5c827c2a38c99 100644
--- a/administrator/components/com_fields/src/Plugin/FieldsPlugin.php
+++ b/administrator/components/com_fields/src/Plugin/FieldsPlugin.php
@@ -1,4 +1,5 @@
_type . $this->_name]))
- {
- return $types_cache[$this->_type . $this->_name];
- }
-
- $types = array();
-
- // The root of the plugin
- $root = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name;
-
- foreach (Folder::files($root . '/tmpl', '.php') as $layout)
- {
- // Strip the extension
- $layout = str_replace('.php', '', $layout);
-
- // The data array
- $data = array();
-
- // The language key
- $key = strtoupper($layout);
-
- if ($key != strtoupper($this->_name))
- {
- $key = strtoupper($this->_name) . '_' . $layout;
- }
-
- // Needed attributes
- $data['type'] = $layout;
-
- if ($this->app->getLanguage()->hasKey('PLG_FIELDS_' . $key . '_LABEL'))
- {
- $data['label'] = Text::sprintf('PLG_FIELDS_' . $key . '_LABEL', strtolower($key));
-
- // Fix wrongly set parentheses in RTL languages
- if ($this->app->getLanguage()->isRtl())
- {
- $data['label'] = $data['label'] . '';
- }
- }
- else
- {
- $data['label'] = $key;
- }
-
- $path = $root . '/fields';
-
- // Add the path when it exists
- if (file_exists($path))
- {
- $data['path'] = $path;
- }
-
- $path = $root . '/rules';
-
- // Add the path when it exists
- if (file_exists($path))
- {
- $data['rules'] = $path;
- }
-
- $types[] = $data;
- }
-
- // Add to cache and return the data
- $types_cache[$this->_type . $this->_name] = $types;
-
- return $types;
- }
-
- /**
- * Prepares the field value.
- *
- * @param string $context The context.
- * @param \stdclass $item The item.
- * @param \stdclass $field The field.
- *
- * @return string
- *
- * @since 3.7.0
- */
- public function onCustomFieldsPrepareField($context, $item, $field)
- {
- // Check if the field should be processed by us
- if (!$this->isTypeSupported($field->type))
- {
- return;
- }
-
- // Merge the params from the plugin and field which has precedence
- $fieldParams = clone $this->params;
- $fieldParams->merge($field->fieldparams);
-
- // Get the path for the layout file
- $path = PluginHelper::getLayoutPath('fields', $this->_name, $field->type);
-
- // Render the layout
- ob_start();
- include $path;
- $output = ob_get_clean();
-
- // Return the output
- return $output;
- }
-
- /**
- * Transforms the field into a DOM XML element and appends it as a child on the given parent.
- *
- * @param \stdClass $field The field.
- * @param \DOMElement $parent The field node parent.
- * @param Form $form The form.
- *
- * @return \DOMElement
- *
- * @since 3.7.0
- */
- public function onCustomFieldsPrepareDom($field, \DOMElement $parent, Form $form)
- {
- // Check if the field should be processed by us
- if (!$this->isTypeSupported($field->type))
- {
- return null;
- }
-
- // Detect if the field is configured to be displayed on the form
- if (!FieldsHelper::displayFieldOnForm($field))
- {
- return null;
- }
-
- // Create the node
- $node = $parent->appendChild(new \DOMElement('field'));
-
- // Set the attributes
- $node->setAttribute('name', $field->name);
- $node->setAttribute('type', $field->type);
- $node->setAttribute('label', $field->label);
- $node->setAttribute('labelclass', $field->params->get('label_class', ''));
- $node->setAttribute('description', $field->description);
- $node->setAttribute('class', $field->params->get('class', ''));
- $node->setAttribute('hint', $field->params->get('hint', ''));
- $node->setAttribute('required', $field->required ? 'true' : 'false');
-
- if ($layout = $field->params->get('form_layout'))
- {
- $node->setAttribute('layout', $layout);
- }
-
- if ($field->default_value !== '')
- {
- $defaultNode = $node->appendChild(new \DOMElement('default'));
- $defaultNode->appendChild(new \DOMCdataSection($field->default_value));
- }
-
- // Combine the two params
- $params = clone $this->params;
- $params->merge($field->fieldparams);
-
- // Set the specific field parameters
- foreach ($params->toArray() as $key => $param)
- {
- if (is_array($param))
- {
- // Multidimensional arrays (eg. list options) can't be transformed properly
- $param = count($param) == count($param, COUNT_RECURSIVE) ? implode(',', $param) : '';
- }
-
- if ($param === '' || (!is_string($param) && !is_numeric($param)))
- {
- continue;
- }
-
- $node->setAttribute($key, $param);
- }
-
- // Check if it is allowed to edit the field
- if (!FieldsHelper::canEditFieldValue($field))
- {
- $node->setAttribute('disabled', 'true');
- }
-
- // Return the node
- return $node;
- }
-
- /**
- * The form event. Load additional parameters when available into the field form.
- * Only when the type of the form is of interest.
- *
- * @param Form $form The form
- * @param \stdClass $data The data
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function onContentPrepareForm(Form $form, $data)
- {
- $path = $this->getFormPath($form, $data);
-
- if ($path === null)
- {
- return;
- }
-
- // Load the specific plugin parameters
- $form->load(file_get_contents($path), true, '/form/*');
- }
-
- /**
- * Returns the path of the XML definition file for the field parameters
- *
- * @param Form $form The form
- * @param \stdClass $data The data
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function getFormPath(Form $form, $data)
- {
- // Check if the field form is calling us
- if (strpos($form->getName(), 'com_fields.field') !== 0)
- {
- return null;
- }
-
- // Ensure it is an object
- $formData = (object) $data;
-
- // Gather the type
- $type = $form->getValue('type');
-
- if (!empty($formData->type))
- {
- $type = $formData->type;
- }
-
- // Not us
- if (!$this->isTypeSupported($type))
- {
- return null;
- }
-
- $path = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name . '/params/' . $type . '.xml';
-
- // Check if params file exists
- if (!file_exists($path))
- {
- return null;
- }
-
- return $path;
- }
-
- /**
- * Returns true if the given type is supported by the plugin.
- *
- * @param string $type The type
- *
- * @return boolean
- *
- * @since 3.7.0
- */
- protected function isTypeSupported($type)
- {
- foreach ($this->onCustomFieldsGetTypes() as $typeSpecification)
- {
- if ($type == $typeSpecification['type'])
- {
- return true;
- }
- }
-
- return false;
- }
+ /**
+ * Affects constructor behavior. If true, language files will be loaded automatically.
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ protected $autoloadLanguage = true;
+
+ /**
+ * Application object.
+ *
+ * @var \Joomla\CMS\Application\CMSApplication
+ * @since 4.0.0
+ */
+ protected $app;
+
+ /**
+ * Returns the custom fields types.
+ *
+ * @return string[][]
+ *
+ * @since 3.7.0
+ */
+ public function onCustomFieldsGetTypes()
+ {
+ // Cache filesystem access / checks
+ static $types_cache = array();
+
+ if (isset($types_cache[$this->_type . $this->_name])) {
+ return $types_cache[$this->_type . $this->_name];
+ }
+
+ $types = array();
+
+ // The root of the plugin
+ $root = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name;
+
+ foreach (Folder::files($root . '/tmpl', '.php') as $layout) {
+ // Strip the extension
+ $layout = str_replace('.php', '', $layout);
+
+ // The data array
+ $data = array();
+
+ // The language key
+ $key = strtoupper($layout);
+
+ if ($key != strtoupper($this->_name)) {
+ $key = strtoupper($this->_name) . '_' . $layout;
+ }
+
+ // Needed attributes
+ $data['type'] = $layout;
+
+ if ($this->app->getLanguage()->hasKey('PLG_FIELDS_' . $key . '_LABEL')) {
+ $data['label'] = Text::sprintf('PLG_FIELDS_' . $key . '_LABEL', strtolower($key));
+
+ // Fix wrongly set parentheses in RTL languages
+ if ($this->app->getLanguage()->isRtl()) {
+ $data['label'] = $data['label'] . '';
+ }
+ } else {
+ $data['label'] = $key;
+ }
+
+ $path = $root . '/fields';
+
+ // Add the path when it exists
+ if (file_exists($path)) {
+ $data['path'] = $path;
+ }
+
+ $path = $root . '/rules';
+
+ // Add the path when it exists
+ if (file_exists($path)) {
+ $data['rules'] = $path;
+ }
+
+ $types[] = $data;
+ }
+
+ // Add to cache and return the data
+ $types_cache[$this->_type . $this->_name] = $types;
+
+ return $types;
+ }
+
+ /**
+ * Prepares the field value.
+ *
+ * @param string $context The context.
+ * @param \stdclass $item The item.
+ * @param \stdclass $field The field.
+ *
+ * @return string
+ *
+ * @since 3.7.0
+ */
+ public function onCustomFieldsPrepareField($context, $item, $field)
+ {
+ // Check if the field should be processed by us
+ if (!$this->isTypeSupported($field->type)) {
+ return;
+ }
+
+ // Merge the params from the plugin and field which has precedence
+ $fieldParams = clone $this->params;
+ $fieldParams->merge($field->fieldparams);
+
+ // Get the path for the layout file
+ $path = PluginHelper::getLayoutPath('fields', $this->_name, $field->type);
+
+ // Render the layout
+ ob_start();
+ include $path;
+ $output = ob_get_clean();
+
+ // Return the output
+ return $output;
+ }
+
+ /**
+ * Transforms the field into a DOM XML element and appends it as a child on the given parent.
+ *
+ * @param \stdClass $field The field.
+ * @param \DOMElement $parent The field node parent.
+ * @param Form $form The form.
+ *
+ * @return \DOMElement
+ *
+ * @since 3.7.0
+ */
+ public function onCustomFieldsPrepareDom($field, \DOMElement $parent, Form $form)
+ {
+ // Check if the field should be processed by us
+ if (!$this->isTypeSupported($field->type)) {
+ return null;
+ }
+
+ // Detect if the field is configured to be displayed on the form
+ if (!FieldsHelper::displayFieldOnForm($field)) {
+ return null;
+ }
+
+ // Create the node
+ $node = $parent->appendChild(new \DOMElement('field'));
+
+ // Set the attributes
+ $node->setAttribute('name', $field->name);
+ $node->setAttribute('type', $field->type);
+ $node->setAttribute('label', $field->label);
+ $node->setAttribute('labelclass', $field->params->get('label_class', ''));
+ $node->setAttribute('description', $field->description);
+ $node->setAttribute('class', $field->params->get('class', ''));
+ $node->setAttribute('hint', $field->params->get('hint', ''));
+ $node->setAttribute('required', $field->required ? 'true' : 'false');
+
+ if ($layout = $field->params->get('form_layout')) {
+ $node->setAttribute('layout', $layout);
+ }
+
+ if ($field->default_value !== '') {
+ $defaultNode = $node->appendChild(new \DOMElement('default'));
+ $defaultNode->appendChild(new \DOMCdataSection($field->default_value));
+ }
+
+ // Combine the two params
+ $params = clone $this->params;
+ $params->merge($field->fieldparams);
+
+ // Set the specific field parameters
+ foreach ($params->toArray() as $key => $param) {
+ if (is_array($param)) {
+ // Multidimensional arrays (eg. list options) can't be transformed properly
+ $param = count($param) == count($param, COUNT_RECURSIVE) ? implode(',', $param) : '';
+ }
+
+ if ($param === '' || (!is_string($param) && !is_numeric($param))) {
+ continue;
+ }
+
+ $node->setAttribute($key, $param);
+ }
+
+ // Check if it is allowed to edit the field
+ if (!FieldsHelper::canEditFieldValue($field)) {
+ $node->setAttribute('disabled', 'true');
+ }
+
+ // Return the node
+ return $node;
+ }
+
+ /**
+ * The form event. Load additional parameters when available into the field form.
+ * Only when the type of the form is of interest.
+ *
+ * @param Form $form The form
+ * @param \stdClass $data The data
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function onContentPrepareForm(Form $form, $data)
+ {
+ $path = $this->getFormPath($form, $data);
+
+ if ($path === null) {
+ return;
+ }
+
+ // Load the specific plugin parameters
+ $form->load(file_get_contents($path), true, '/form/*');
+ }
+
+ /**
+ * Returns the path of the XML definition file for the field parameters
+ *
+ * @param Form $form The form
+ * @param \stdClass $data The data
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function getFormPath(Form $form, $data)
+ {
+ // Check if the field form is calling us
+ if (strpos($form->getName(), 'com_fields.field') !== 0) {
+ return null;
+ }
+
+ // Ensure it is an object
+ $formData = (object) $data;
+
+ // Gather the type
+ $type = $form->getValue('type');
+
+ if (!empty($formData->type)) {
+ $type = $formData->type;
+ }
+
+ // Not us
+ if (!$this->isTypeSupported($type)) {
+ return null;
+ }
+
+ $path = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name . '/params/' . $type . '.xml';
+
+ // Check if params file exists
+ if (!file_exists($path)) {
+ return null;
+ }
+
+ return $path;
+ }
+
+ /**
+ * Returns true if the given type is supported by the plugin.
+ *
+ * @param string $type The type
+ *
+ * @return boolean
+ *
+ * @since 3.7.0
+ */
+ protected function isTypeSupported($type)
+ {
+ foreach ($this->onCustomFieldsGetTypes() as $typeSpecification) {
+ if ($type == $typeSpecification['type']) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/administrator/components/com_fields/src/Table/FieldTable.php b/administrator/components/com_fields/src/Table/FieldTable.php
index 20953310ebe4c..9995e4c7a64d6 100644
--- a/administrator/components/com_fields/src/Table/FieldTable.php
+++ b/administrator/components/com_fields/src/Table/FieldTable.php
@@ -1,4 +1,5 @@
setColumnAlias('published', 'state');
- }
-
- /**
- * Method to bind an associative array or object to the JTable instance.This
- * method only binds properties that are publicly accessible and optionally
- * takes an array of properties to ignore when binding.
- *
- * @param mixed $src An associative array or object to bind to the JTable instance.
- * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- * @throws \InvalidArgumentException
- */
- public function bind($src, $ignore = '')
- {
- if (isset($src['params']) && is_array($src['params']))
- {
- $registry = new Registry;
- $registry->loadArray($src['params']);
- $src['params'] = (string) $registry;
- }
-
- if (isset($src['fieldparams']) && is_array($src['fieldparams']))
- {
- // Make sure $registry->options contains no duplicates when the field type is subform
- if (isset($src['type']) && $src['type'] == 'subform' && isset($src['fieldparams']['options']))
- {
- // Fast lookup map to check which custom field ids we have already seen
- $seen_customfields = array();
-
- // Container for the new $src['fieldparams']['options']
- $options = array();
-
- // Iterate through the old options
- $i = 0;
-
- foreach ($src['fieldparams']['options'] as $option)
- {
- // Check whether we have not yet seen this custom field id
- if (!isset($seen_customfields[$option['customfield']]))
- {
- // We haven't, so add it to the final options
- $seen_customfields[$option['customfield']] = true;
- $options['option' . $i] = $option;
- $i++;
- }
- }
-
- // And replace the options with the deduplicated ones.
- $src['fieldparams']['options'] = $options;
- }
-
- $registry = new Registry;
- $registry->loadArray($src['fieldparams']);
- $src['fieldparams'] = (string) $registry;
- }
-
- // Bind the rules.
- if (isset($src['rules']) && is_array($src['rules']))
- {
- $rules = new Rules($src['rules']);
- $this->setRules($rules);
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Method to perform sanity checks on the JTable instance properties to ensure
- * they are safe to store in the database. Child classes should override this
- * method to make sure the data they are storing in the database is safe and
- * as expected before storage.
- *
- * @return boolean True if the instance is sane and able to be stored in the database.
- *
- * @link https://docs.joomla.org/Special:MyLanguage/JTable/check
- * @since 3.7.0
- */
- public function check()
- {
- // Check for valid name
- if (trim($this->title) == '')
- {
- $this->setError(Text::_('COM_FIELDS_MUSTCONTAIN_A_TITLE_FIELD'));
-
- return false;
- }
-
- if (empty($this->name))
- {
- $this->name = $this->title;
- }
-
- $this->name = ApplicationHelper::stringURLSafe($this->name, $this->language);
-
- if (trim(str_replace('-', '', $this->name)) == '')
- {
- $this->name = StringHelper::increment($this->name, 'dash');
- }
-
- $this->name = str_replace(',', '-', $this->name);
-
- // Verify that the name is unique
- $table = new static($this->_db);
-
- if ($table->load(array('name' => $this->name)) && ($table->id != $this->id || $this->id == 0))
- {
- $this->setError(Text::_('COM_FIELDS_ERROR_UNIQUE_NAME'));
-
- return false;
- }
-
- $this->name = str_replace(',', '-', $this->name);
-
- if (empty($this->type))
- {
- $this->type = 'text';
- }
-
- if (empty($this->fieldparams))
- {
- $this->fieldparams = '{}';
- }
-
- $date = Factory::getDate()->toSql();
- $user = Factory::getUser();
-
- // Set created date if not set.
- if (!(int) $this->created_time)
- {
- $this->created_time = $date;
- }
-
- if ($this->id)
- {
- // Existing item
- $this->modified_time = $date;
- $this->modified_by = $user->get('id');
- }
- else
- {
- if (!(int) $this->modified_time)
- {
- $this->modified_time = $this->created_time;
- }
-
- if (empty($this->created_user_id))
- {
- $this->created_user_id = $user->get('id');
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_user_id;
- }
- }
-
- if (empty($this->group_id))
- {
- $this->group_id = 0;
- }
-
- return true;
- }
-
- /**
- * Overloaded store function
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return mixed False on failure, positive integer on success.
- *
- * @see Table::store()
- * @since 4.0.0
- */
- public function store($updateNulls = true)
- {
- return parent::store($updateNulls);
- }
-
- /**
- * Method to compute the default name of the asset.
- * The default name is in the form table_name.id
- * where id is the value of the primary key of the table.
- *
- * @return string
- *
- * @since 3.7.0
- */
- protected function _getAssetName()
- {
- $contextArray = explode('.', $this->context);
-
- return $contextArray[0] . '.field.' . (int) $this->id;
- }
-
- /**
- * Method to return the title to use for the asset table. In
- * tracking the assets a title is kept for each asset so that there is some
- * context available in a unified access manager. Usually this would just
- * return $this->title or $this->name or whatever is being used for the
- * primary name of the row. If this method is not overridden, the asset name is used.
- *
- * @return string The string to use as the title in the asset table.
- *
- * @link https://docs.joomla.org/Special:MyLanguage/JTable/getAssetTitle
- * @since 3.7.0
- */
- protected function _getAssetTitle()
- {
- return $this->title;
- }
-
- /**
- * Method to get the parent asset under which to register this one.
- * By default, all assets are registered to the ROOT node with ID,
- * which will default to 1 if none exists.
- * The extended class can define a table and id to lookup. If the
- * asset does not exist it will be created.
- *
- * @param Table $table A Table object for the asset parent.
- * @param integer $id Id to look up
- *
- * @return integer
- *
- * @since 3.7.0
- */
- protected function _getAssetParentId(Table $table = null, $id = null)
- {
- $contextArray = explode('.', $this->context);
- $component = $contextArray[0];
-
- if ($this->group_id)
- {
- $assetId = $this->getAssetId($component . '.fieldgroup.' . (int) $this->group_id);
-
- if ($assetId)
- {
- return $assetId;
- }
- }
- else
- {
- $assetId = $this->getAssetId($component);
-
- if ($assetId)
- {
- return $assetId;
- }
- }
-
- return parent::_getAssetParentId($table, $id);
- }
-
- /**
- * Returns an asset id for the given name or false.
- *
- * @param string $name The asset name
- *
- * @return number|boolean
- *
- * @since 3.7.0
- */
- private function getAssetId($name)
- {
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('name') . ' = :name')
- ->bind(':name', $name);
-
- // Get the asset id from the database.
- $db->setQuery($query);
-
- $assetId = null;
-
- if ($result = $db->loadResult())
- {
- $assetId = (int) $result;
-
- if ($assetId)
- {
- return $assetId;
- }
- }
-
- return false;
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Class constructor.
+ *
+ * @param DatabaseDriver $db DatabaseDriver object.
+ *
+ * @since 3.7.0
+ */
+ public function __construct($db = null)
+ {
+ parent::__construct('#__fields', 'id', $db);
+
+ $this->setColumnAlias('published', 'state');
+ }
+
+ /**
+ * Method to bind an associative array or object to the JTable instance.This
+ * method only binds properties that are publicly accessible and optionally
+ * takes an array of properties to ignore when binding.
+ *
+ * @param mixed $src An associative array or object to bind to the JTable instance.
+ * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ * @throws \InvalidArgumentException
+ */
+ public function bind($src, $ignore = '')
+ {
+ if (isset($src['params']) && is_array($src['params'])) {
+ $registry = new Registry();
+ $registry->loadArray($src['params']);
+ $src['params'] = (string) $registry;
+ }
+
+ if (isset($src['fieldparams']) && is_array($src['fieldparams'])) {
+ // Make sure $registry->options contains no duplicates when the field type is subform
+ if (isset($src['type']) && $src['type'] == 'subform' && isset($src['fieldparams']['options'])) {
+ // Fast lookup map to check which custom field ids we have already seen
+ $seen_customfields = array();
+
+ // Container for the new $src['fieldparams']['options']
+ $options = array();
+
+ // Iterate through the old options
+ $i = 0;
+
+ foreach ($src['fieldparams']['options'] as $option) {
+ // Check whether we have not yet seen this custom field id
+ if (!isset($seen_customfields[$option['customfield']])) {
+ // We haven't, so add it to the final options
+ $seen_customfields[$option['customfield']] = true;
+ $options['option' . $i] = $option;
+ $i++;
+ }
+ }
+
+ // And replace the options with the deduplicated ones.
+ $src['fieldparams']['options'] = $options;
+ }
+
+ $registry = new Registry();
+ $registry->loadArray($src['fieldparams']);
+ $src['fieldparams'] = (string) $registry;
+ }
+
+ // Bind the rules.
+ if (isset($src['rules']) && is_array($src['rules'])) {
+ $rules = new Rules($src['rules']);
+ $this->setRules($rules);
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Method to perform sanity checks on the JTable instance properties to ensure
+ * they are safe to store in the database. Child classes should override this
+ * method to make sure the data they are storing in the database is safe and
+ * as expected before storage.
+ *
+ * @return boolean True if the instance is sane and able to be stored in the database.
+ *
+ * @link https://docs.joomla.org/Special:MyLanguage/JTable/check
+ * @since 3.7.0
+ */
+ public function check()
+ {
+ // Check for valid name
+ if (trim($this->title) == '') {
+ $this->setError(Text::_('COM_FIELDS_MUSTCONTAIN_A_TITLE_FIELD'));
+
+ return false;
+ }
+
+ if (empty($this->name)) {
+ $this->name = $this->title;
+ }
+
+ $this->name = ApplicationHelper::stringURLSafe($this->name, $this->language);
+
+ if (trim(str_replace('-', '', $this->name)) == '') {
+ $this->name = StringHelper::increment($this->name, 'dash');
+ }
+
+ $this->name = str_replace(',', '-', $this->name);
+
+ // Verify that the name is unique
+ $table = new static($this->_db);
+
+ if ($table->load(array('name' => $this->name)) && ($table->id != $this->id || $this->id == 0)) {
+ $this->setError(Text::_('COM_FIELDS_ERROR_UNIQUE_NAME'));
+
+ return false;
+ }
+
+ $this->name = str_replace(',', '-', $this->name);
+
+ if (empty($this->type)) {
+ $this->type = 'text';
+ }
+
+ if (empty($this->fieldparams)) {
+ $this->fieldparams = '{}';
+ }
+
+ $date = Factory::getDate()->toSql();
+ $user = Factory::getUser();
+
+ // Set created date if not set.
+ if (!(int) $this->created_time) {
+ $this->created_time = $date;
+ }
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_time = $date;
+ $this->modified_by = $user->get('id');
+ } else {
+ if (!(int) $this->modified_time) {
+ $this->modified_time = $this->created_time;
+ }
+
+ if (empty($this->created_user_id)) {
+ $this->created_user_id = $user->get('id');
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_user_id;
+ }
+ }
+
+ if (empty($this->group_id)) {
+ $this->group_id = 0;
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded store function
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return mixed False on failure, positive integer on success.
+ *
+ * @see Table::store()
+ * @since 4.0.0
+ */
+ public function store($updateNulls = true)
+ {
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to compute the default name of the asset.
+ * The default name is in the form table_name.id
+ * where id is the value of the primary key of the table.
+ *
+ * @return string
+ *
+ * @since 3.7.0
+ */
+ protected function _getAssetName()
+ {
+ $contextArray = explode('.', $this->context);
+
+ return $contextArray[0] . '.field.' . (int) $this->id;
+ }
+
+ /**
+ * Method to return the title to use for the asset table. In
+ * tracking the assets a title is kept for each asset so that there is some
+ * context available in a unified access manager. Usually this would just
+ * return $this->title or $this->name or whatever is being used for the
+ * primary name of the row. If this method is not overridden, the asset name is used.
+ *
+ * @return string The string to use as the title in the asset table.
+ *
+ * @link https://docs.joomla.org/Special:MyLanguage/JTable/getAssetTitle
+ * @since 3.7.0
+ */
+ protected function _getAssetTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Method to get the parent asset under which to register this one.
+ * By default, all assets are registered to the ROOT node with ID,
+ * which will default to 1 if none exists.
+ * The extended class can define a table and id to lookup. If the
+ * asset does not exist it will be created.
+ *
+ * @param Table $table A Table object for the asset parent.
+ * @param integer $id Id to look up
+ *
+ * @return integer
+ *
+ * @since 3.7.0
+ */
+ protected function _getAssetParentId(Table $table = null, $id = null)
+ {
+ $contextArray = explode('.', $this->context);
+ $component = $contextArray[0];
+
+ if ($this->group_id) {
+ $assetId = $this->getAssetId($component . '.fieldgroup.' . (int) $this->group_id);
+
+ if ($assetId) {
+ return $assetId;
+ }
+ } else {
+ $assetId = $this->getAssetId($component);
+
+ if ($assetId) {
+ return $assetId;
+ }
+ }
+
+ return parent::_getAssetParentId($table, $id);
+ }
+
+ /**
+ * Returns an asset id for the given name or false.
+ *
+ * @param string $name The asset name
+ *
+ * @return number|boolean
+ *
+ * @since 3.7.0
+ */
+ private function getAssetId($name)
+ {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('name') . ' = :name')
+ ->bind(':name', $name);
+
+ // Get the asset id from the database.
+ $db->setQuery($query);
+
+ $assetId = null;
+
+ if ($result = $db->loadResult()) {
+ $assetId = (int) $result;
+
+ if ($assetId) {
+ return $assetId;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/administrator/components/com_fields/src/Table/GroupTable.php b/administrator/components/com_fields/src/Table/GroupTable.php
index 474572238e4b6..921cafa8895c3 100644
--- a/administrator/components/com_fields/src/Table/GroupTable.php
+++ b/administrator/components/com_fields/src/Table/GroupTable.php
@@ -1,4 +1,5 @@
setColumnAlias('published', 'state');
- }
-
- /**
- * Method to bind an associative array or object to the JTable instance.This
- * method only binds properties that are publicly accessible and optionally
- * takes an array of properties to ignore when binding.
- *
- * @param mixed $src An associative array or object to bind to the JTable instance.
- * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- * @throws \InvalidArgumentException
- */
- public function bind($src, $ignore = '')
- {
- if (isset($src['params']) && is_array($src['params']))
- {
- $registry = new Registry;
- $registry->loadArray($src['params']);
- $src['params'] = (string) $registry;
- }
-
- // Bind the rules.
- if (isset($src['rules']) && is_array($src['rules']))
- {
- $rules = new Rules($src['rules']);
- $this->setRules($rules);
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Method to perform sanity checks on the JTable instance properties to ensure
- * they are safe to store in the database. Child classes should override this
- * method to make sure the data they are storing in the database is safe and
- * as expected before storage.
- *
- * @return boolean True if the instance is sane and able to be stored in the database.
- *
- * @link https://docs.joomla.org/Special:MyLanguage/JTable/check
- * @since 3.7.0
- */
- public function check()
- {
- // Check for a title.
- if (trim($this->title) == '')
- {
- $this->setError(Text::_('COM_FIELDS_MUSTCONTAIN_A_TITLE_GROUP'));
-
- return false;
- }
-
- $date = Factory::getDate()->toSql();
- $user = Factory::getUser();
-
- // Set created date if not set.
- if (!(int) $this->created)
- {
- $this->created = $date;
- }
-
- if ($this->id)
- {
- $this->modified = $date;
- $this->modified_by = $user->get('id');
- }
- else
- {
- if (!(int) $this->modified)
- {
- $this->modified = $this->created;
- }
-
- if (empty($this->created_by))
- {
- $this->created_by = $user->get('id');
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_by;
- }
- }
-
- if ($this->params === null)
- {
- $this->params = '{}';
- }
-
- return true;
- }
-
- /**
- * Overloaded store function
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return mixed False on failure, positive integer on success.
- *
- * @see Table::store()
- * @since 4.0.0
- */
- public function store($updateNulls = true)
- {
- return parent::store($updateNulls);
- }
-
- /**
- * Method to compute the default name of the asset.
- * The default name is in the form table_name.id
- * where id is the value of the primary key of the table.
- *
- * @return string
- *
- * @since 3.7.0
- */
- protected function _getAssetName()
- {
- $component = explode('.', $this->context);
-
- return $component[0] . '.fieldgroup.' . (int) $this->id;
- }
-
- /**
- * Method to return the title to use for the asset table. In
- * tracking the assets a title is kept for each asset so that there is some
- * context available in a unified access manager. Usually this would just
- * return $this->title or $this->name or whatever is being used for the
- * primary name of the row. If this method is not overridden, the asset name is used.
- *
- * @return string The string to use as the title in the asset table.
- *
- * @link https://docs.joomla.org/Special:MyLanguage/JTable/getAssetTitle
- * @since 3.7.0
- */
- protected function _getAssetTitle()
- {
- return $this->title;
- }
-
- /**
- * Method to get the parent asset under which to register this one.
- * By default, all assets are registered to the ROOT node with ID,
- * which will default to 1 if none exists.
- * The extended class can define a table and id to lookup. If the
- * asset does not exist it will be created.
- *
- * @param Table $table A Table object for the asset parent.
- * @param integer $id Id to look up
- *
- * @return integer
- *
- * @since 3.7.0
- */
- protected function _getAssetParentId(Table $table = null, $id = null)
- {
- $component = explode('.', $this->context);
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('name') . ' = :name')
- ->bind(':name', $component[0]);
- $db->setQuery($query);
-
- if ($assetId = (int) $db->loadResult())
- {
- return $assetId;
- }
-
- return parent::_getAssetParentId($table, $id);
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Class constructor.
+ *
+ * @param DatabaseDriver $db DatabaseDriver object.
+ *
+ * @since 3.7.0
+ */
+ public function __construct($db = null)
+ {
+ parent::__construct('#__fields_groups', 'id', $db);
+
+ $this->setColumnAlias('published', 'state');
+ }
+
+ /**
+ * Method to bind an associative array or object to the JTable instance.This
+ * method only binds properties that are publicly accessible and optionally
+ * takes an array of properties to ignore when binding.
+ *
+ * @param mixed $src An associative array or object to bind to the JTable instance.
+ * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ * @throws \InvalidArgumentException
+ */
+ public function bind($src, $ignore = '')
+ {
+ if (isset($src['params']) && is_array($src['params'])) {
+ $registry = new Registry();
+ $registry->loadArray($src['params']);
+ $src['params'] = (string) $registry;
+ }
+
+ // Bind the rules.
+ if (isset($src['rules']) && is_array($src['rules'])) {
+ $rules = new Rules($src['rules']);
+ $this->setRules($rules);
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Method to perform sanity checks on the JTable instance properties to ensure
+ * they are safe to store in the database. Child classes should override this
+ * method to make sure the data they are storing in the database is safe and
+ * as expected before storage.
+ *
+ * @return boolean True if the instance is sane and able to be stored in the database.
+ *
+ * @link https://docs.joomla.org/Special:MyLanguage/JTable/check
+ * @since 3.7.0
+ */
+ public function check()
+ {
+ // Check for a title.
+ if (trim($this->title) == '') {
+ $this->setError(Text::_('COM_FIELDS_MUSTCONTAIN_A_TITLE_GROUP'));
+
+ return false;
+ }
+
+ $date = Factory::getDate()->toSql();
+ $user = Factory::getUser();
+
+ // Set created date if not set.
+ if (!(int) $this->created) {
+ $this->created = $date;
+ }
+
+ if ($this->id) {
+ $this->modified = $date;
+ $this->modified_by = $user->get('id');
+ } else {
+ if (!(int) $this->modified) {
+ $this->modified = $this->created;
+ }
+
+ if (empty($this->created_by)) {
+ $this->created_by = $user->get('id');
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_by;
+ }
+ }
+
+ if ($this->params === null) {
+ $this->params = '{}';
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded store function
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return mixed False on failure, positive integer on success.
+ *
+ * @see Table::store()
+ * @since 4.0.0
+ */
+ public function store($updateNulls = true)
+ {
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to compute the default name of the asset.
+ * The default name is in the form table_name.id
+ * where id is the value of the primary key of the table.
+ *
+ * @return string
+ *
+ * @since 3.7.0
+ */
+ protected function _getAssetName()
+ {
+ $component = explode('.', $this->context);
+
+ return $component[0] . '.fieldgroup.' . (int) $this->id;
+ }
+
+ /**
+ * Method to return the title to use for the asset table. In
+ * tracking the assets a title is kept for each asset so that there is some
+ * context available in a unified access manager. Usually this would just
+ * return $this->title or $this->name or whatever is being used for the
+ * primary name of the row. If this method is not overridden, the asset name is used.
+ *
+ * @return string The string to use as the title in the asset table.
+ *
+ * @link https://docs.joomla.org/Special:MyLanguage/JTable/getAssetTitle
+ * @since 3.7.0
+ */
+ protected function _getAssetTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Method to get the parent asset under which to register this one.
+ * By default, all assets are registered to the ROOT node with ID,
+ * which will default to 1 if none exists.
+ * The extended class can define a table and id to lookup. If the
+ * asset does not exist it will be created.
+ *
+ * @param Table $table A Table object for the asset parent.
+ * @param integer $id Id to look up
+ *
+ * @return integer
+ *
+ * @since 3.7.0
+ */
+ protected function _getAssetParentId(Table $table = null, $id = null)
+ {
+ $component = explode('.', $this->context);
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('name') . ' = :name')
+ ->bind(':name', $component[0]);
+ $db->setQuery($query);
+
+ if ($assetId = (int) $db->loadResult()) {
+ return $assetId;
+ }
+
+ return parent::_getAssetParentId($table, $id);
+ }
}
diff --git a/administrator/components/com_fields/src/View/Field/HtmlView.php b/administrator/components/com_fields/src/View/Field/HtmlView.php
index 0b8e8a209cb23..0e93d13160fbb 100644
--- a/administrator/components/com_fields/src/View/Field/HtmlView.php
+++ b/administrator/components/com_fields/src/View/Field/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- $this->canDo = ContentHelper::getActions($this->state->get('field.component'), 'field', $this->item->id);
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Adds the toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addToolbar()
- {
- $component = $this->state->get('field.component');
- $section = $this->state->get('field.section');
- $userId = $this->getCurrentUser()->get('id');
- $canDo = $this->canDo;
-
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Avoid nonsense situation.
- if ($component == 'com_fields')
- {
- return;
- }
-
- // Load component language file
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_ADMINISTRATOR)
- || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
-
- $title = Text::sprintf('COM_FIELDS_VIEW_FIELD_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE', Text::_(strtoupper($component)));
-
- // Prepare the toolbar.
- ToolbarHelper::title(
- $title,
- 'puzzle field-' . ($isNew ? 'add' : 'edit') . ' ' . substr($component, 4) . ($section ? "-$section" : '') . '-field-' .
- ($isNew ? 'add' : 'edit')
- );
-
- // For new records, check the create permission.
- if ($isNew)
- {
- ToolbarHelper::apply('field.apply');
-
- ToolbarHelper::saveGroup(
- [
- ['save', 'field.save'],
- ['save2new', 'field.save2new']
- ],
- 'btn-success'
- );
-
- ToolbarHelper::cancel('field.cancel');
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- $toolbarButtons = [];
-
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- ToolbarHelper::apply('field.apply');
-
- $toolbarButtons[] = ['save', 'field.save'];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'field.save2new'];
- }
- }
-
- // If an existing item, can save to a copy.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'field.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel('field.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::help('Component:_New_or_Edit_Field');
- }
+ /**
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.7.0
+ */
+ protected $form;
+
+ /**
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $item;
+
+ /**
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see HtmlView::loadTemplate()
+ *
+ * @since 3.7.0
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ $this->canDo = ContentHelper::getActions($this->state->get('field.component'), 'field', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Adds the toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addToolbar()
+ {
+ $component = $this->state->get('field.component');
+ $section = $this->state->get('field.section');
+ $userId = $this->getCurrentUser()->get('id');
+ $canDo = $this->canDo;
+
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Avoid nonsense situation.
+ if ($component == 'com_fields') {
+ return;
+ }
+
+ // Load component language file
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_ADMINISTRATOR)
+ || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
+
+ $title = Text::sprintf('COM_FIELDS_VIEW_FIELD_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE', Text::_(strtoupper($component)));
+
+ // Prepare the toolbar.
+ ToolbarHelper::title(
+ $title,
+ 'puzzle field-' . ($isNew ? 'add' : 'edit') . ' ' . substr($component, 4) . ($section ? "-$section" : '') . '-field-' .
+ ($isNew ? 'add' : 'edit')
+ );
+
+ // For new records, check the create permission.
+ if ($isNew) {
+ ToolbarHelper::apply('field.apply');
+
+ ToolbarHelper::saveGroup(
+ [
+ ['save', 'field.save'],
+ ['save2new', 'field.save2new']
+ ],
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('field.cancel');
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ $toolbarButtons = [];
+
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ ToolbarHelper::apply('field.apply');
+
+ $toolbarButtons[] = ['save', 'field.save'];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'field.save2new'];
+ }
+ }
+
+ // If an existing item, can save to a copy.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'field.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('field.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::help('Component:_New_or_Edit_Field');
+ }
}
diff --git a/administrator/components/com_fields/src/View/Fields/HtmlView.php b/administrator/components/com_fields/src/View/Fields/HtmlView.php
index e7bb2fdf8d170..1cd02e8e94bc9 100644
--- a/administrator/components/com_fields/src/View/Fields/HtmlView.php
+++ b/administrator/components/com_fields/src/View/Fields/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Display a warning if the fields system plugin is disabled
- if (!PluginHelper::isEnabled('system', 'fields'))
- {
- $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . FieldsHelper::getFieldsPluginId());
- Factory::getApplication()->enqueueMessage(Text::sprintf('COM_FIELDS_SYSTEM_PLUGIN_NOT_ENABLED', $link), 'warning');
- }
-
- // Only add toolbar when not in modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Adds the toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addToolbar()
- {
- $fieldId = $this->state->get('filter.field_id');
- $component = $this->state->get('filter.component');
- $section = $this->state->get('filter.section');
- $canDo = ContentHelper::getActions($component, 'field', $fieldId);
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- // Avoid nonsense situation.
- if ($component == 'com_fields')
- {
- return;
- }
-
- // Load extension language file
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_ADMINISTRATOR)
- || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
-
- $title = Text::sprintf('COM_FIELDS_VIEW_FIELDS_TITLE', Text::_(strtoupper($component)));
-
- // Prepare the toolbar.
- ToolbarHelper::title($title, 'puzzle-piece fields ' . substr($component, 4) . ($section ? "-$section" : '') . '-fields');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('field.add');
- }
-
- if ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('fields.publish')->listCheck(true);
-
- $childBar->unpublish('fields.unpublish')->listCheck(true);
-
- $childBar->archive('fields.archive')->listCheck(true);
- }
-
- if ($this->getCurrentUser()->authorise('core.admin'))
- {
- $childBar->checkin('fields.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && !$this->state->get('filter.state') == -2)
- {
- $childBar->trash('fields.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete', $component))
- {
- $toolbar->delete('fields.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences($component);
- }
-
- $toolbar->help('Component:_Fields');
- }
+ /**
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.7.0
+ */
+ public $filterForm;
+
+ /**
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ public $activeFilters;
+
+ /**
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ protected $items;
+
+ /**
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 3.7.0
+ */
+ protected $pagination;
+
+ /**
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see \Joomla\CMS\MVC\View\HtmlView::loadTemplate()
+ *
+ * @since 3.7.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Display a warning if the fields system plugin is disabled
+ if (!PluginHelper::isEnabled('system', 'fields')) {
+ $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . FieldsHelper::getFieldsPluginId());
+ Factory::getApplication()->enqueueMessage(Text::sprintf('COM_FIELDS_SYSTEM_PLUGIN_NOT_ENABLED', $link), 'warning');
+ }
+
+ // Only add toolbar when not in modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Adds the toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addToolbar()
+ {
+ $fieldId = $this->state->get('filter.field_id');
+ $component = $this->state->get('filter.component');
+ $section = $this->state->get('filter.section');
+ $canDo = ContentHelper::getActions($component, 'field', $fieldId);
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ // Avoid nonsense situation.
+ if ($component == 'com_fields') {
+ return;
+ }
+
+ // Load extension language file
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_ADMINISTRATOR)
+ || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
+
+ $title = Text::sprintf('COM_FIELDS_VIEW_FIELDS_TITLE', Text::_(strtoupper($component)));
+
+ // Prepare the toolbar.
+ ToolbarHelper::title($title, 'puzzle-piece fields ' . substr($component, 4) . ($section ? "-$section" : '') . '-fields');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('field.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('fields.publish')->listCheck(true);
+
+ $childBar->unpublish('fields.unpublish')->listCheck(true);
+
+ $childBar->archive('fields.archive')->listCheck(true);
+ }
+
+ if ($this->getCurrentUser()->authorise('core.admin')) {
+ $childBar->checkin('fields.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && !$this->state->get('filter.state') == -2) {
+ $childBar->trash('fields.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state')) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete', $component)) {
+ $toolbar->delete('fields.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences($component);
+ }
+
+ $toolbar->help('Component:_Fields');
+ }
}
diff --git a/administrator/components/com_fields/src/View/Group/HtmlView.php b/administrator/components/com_fields/src/View/Group/HtmlView.php
index 7e1421f78ee71..754d37c97751c 100644
--- a/administrator/components/com_fields/src/View/Group/HtmlView.php
+++ b/administrator/components/com_fields/src/View/Group/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- $component = '';
- $parts = FieldsHelper::extract($this->state->get('filter.context'));
-
- if ($parts)
- {
- $component = $parts[0];
- }
-
- $this->canDo = ContentHelper::getActions($component, 'fieldgroup', $this->item->id);
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Adds the toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addToolbar()
- {
- $component = '';
- $parts = FieldsHelper::extract($this->state->get('filter.context'));
-
- if ($parts)
- {
- $component = $parts[0];
- }
-
- $userId = $this->getCurrentUser()->get('id');
- $canDo = $this->canDo;
-
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- // Avoid nonsense situation.
- if ($component == 'com_fields')
- {
- return;
- }
-
- // Load component language file
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_ADMINISTRATOR)
- || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
-
- $title = Text::sprintf('COM_FIELDS_VIEW_GROUP_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE', Text::_(strtoupper($component)));
-
- // Prepare the toolbar.
- ToolbarHelper::title(
- $title,
- 'puzzle-piece field-' . ($isNew ? 'add' : 'edit') . ' ' . substr($component, 4) . '-group-' .
- ($isNew ? 'add' : 'edit')
- );
-
- $toolbarButtons = [];
-
- // For new records, check the create permission.
- if ($isNew)
- {
- ToolbarHelper::apply('group.apply');
-
- ToolbarHelper::saveGroup(
- [
- ['save', 'group.save'],
- ['save2new', 'group.save2new']
- ],
- 'btn-success'
- );
-
- ToolbarHelper::cancel('group.cancel');
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- $toolbarButtons = [];
-
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- ToolbarHelper::apply('group.apply');
-
- $toolbarButtons[] = ['save', 'group.save'];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'group.save2new'];
- }
- }
-
- // If an existing item, can save to a copy.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'group.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::help('Component:_New_or_Edit_Field_Group');
- }
+ /**
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.7.0
+ */
+ protected $form;
+
+ /**
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $item;
+
+ /**
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $canDo;
+
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see JViewLegacy::loadTemplate()
+ *
+ * @since 3.7.0
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ $component = '';
+ $parts = FieldsHelper::extract($this->state->get('filter.context'));
+
+ if ($parts) {
+ $component = $parts[0];
+ }
+
+ $this->canDo = ContentHelper::getActions($component, 'fieldgroup', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Adds the toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addToolbar()
+ {
+ $component = '';
+ $parts = FieldsHelper::extract($this->state->get('filter.context'));
+
+ if ($parts) {
+ $component = $parts[0];
+ }
+
+ $userId = $this->getCurrentUser()->get('id');
+ $canDo = $this->canDo;
+
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ // Avoid nonsense situation.
+ if ($component == 'com_fields') {
+ return;
+ }
+
+ // Load component language file
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_ADMINISTRATOR)
+ || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
+
+ $title = Text::sprintf('COM_FIELDS_VIEW_GROUP_' . ($isNew ? 'ADD' : 'EDIT') . '_TITLE', Text::_(strtoupper($component)));
+
+ // Prepare the toolbar.
+ ToolbarHelper::title(
+ $title,
+ 'puzzle-piece field-' . ($isNew ? 'add' : 'edit') . ' ' . substr($component, 4) . '-group-' .
+ ($isNew ? 'add' : 'edit')
+ );
+
+ $toolbarButtons = [];
+
+ // For new records, check the create permission.
+ if ($isNew) {
+ ToolbarHelper::apply('group.apply');
+
+ ToolbarHelper::saveGroup(
+ [
+ ['save', 'group.save'],
+ ['save2new', 'group.save2new']
+ ],
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('group.cancel');
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ $toolbarButtons = [];
+
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ ToolbarHelper::apply('group.apply');
+
+ $toolbarButtons[] = ['save', 'group.save'];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'group.save2new'];
+ }
+ }
+
+ // If an existing item, can save to a copy.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'group.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::help('Component:_New_or_Edit_Field_Group');
+ }
}
diff --git a/administrator/components/com_fields/src/View/Groups/HtmlView.php b/administrator/components/com_fields/src/View/Groups/HtmlView.php
index 2c80ab45f2cec..5ec02db383512 100644
--- a/administrator/components/com_fields/src/View/Groups/HtmlView.php
+++ b/administrator/components/com_fields/src/View/Groups/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Display a warning if the fields system plugin is disabled
- if (!PluginHelper::isEnabled('system', 'fields'))
- {
- $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . FieldsHelper::getFieldsPluginId());
- Factory::getApplication()->enqueueMessage(Text::sprintf('COM_FIELDS_SYSTEM_PLUGIN_NOT_ENABLED', $link), 'warning');
- }
-
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
-
- parent::display($tpl);
- }
-
- /**
- * Adds the toolbar.
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addToolbar()
- {
- $groupId = $this->state->get('filter.group_id');
- $component = '';
- $parts = FieldsHelper::extract($this->state->get('filter.context'));
-
- if ($parts)
- {
- $component = $parts[0];
- }
-
- $canDo = ContentHelper::getActions($component, 'fieldgroup', $groupId);
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- // Avoid nonsense situation.
- if ($component == 'com_fields')
- {
- return;
- }
-
- // Load component language file
- $lang = Factory::getLanguage();
- $lang->load($component, JPATH_ADMINISTRATOR)
- || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
-
- $title = Text::sprintf('COM_FIELDS_VIEW_GROUPS_TITLE', Text::_(strtoupper($component)));
-
- // Prepare the toolbar.
- ToolbarHelper::title($title, 'puzzle-piece fields ' . substr($component, 4) . '-groups');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('group.add');
- }
-
- if ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('groups.publish')->listCheck(true);
-
- $childBar->unpublish('groups.unpublish')->listCheck(true);
-
- $childBar->archive('groups.archive')->listCheck(true);
- }
-
- if ($this->getCurrentUser()->authorise('core.admin'))
- {
- $childBar->checkin('groups.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && !$this->state->get('filter.state') == -2)
- {
- $childBar->trash('groups.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete', $component))
- {
- $toolbar->delete('groups.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences($component);
- }
-
- $toolbar->help('Component:_Field_Groups');
- }
+ /**
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.7.0
+ */
+ public $filterForm;
+
+ /**
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ public $activeFilters;
+
+ /**
+ * @var array
+ *
+ * @since 3.7.0
+ */
+ protected $items;
+
+ /**
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 3.7.0
+ */
+ protected $pagination;
+
+ /**
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 3.7.0
+ */
+ protected $state;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see HtmlView::loadTemplate()
+ *
+ * @since 3.7.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Display a warning if the fields system plugin is disabled
+ if (!PluginHelper::isEnabled('system', 'fields')) {
+ $link = Route::_('index.php?option=com_plugins&task=plugin.edit&extension_id=' . FieldsHelper::getFieldsPluginId());
+ Factory::getApplication()->enqueueMessage(Text::sprintf('COM_FIELDS_SYSTEM_PLUGIN_NOT_ENABLED', $link), 'warning');
+ }
+
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Adds the toolbar.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addToolbar()
+ {
+ $groupId = $this->state->get('filter.group_id');
+ $component = '';
+ $parts = FieldsHelper::extract($this->state->get('filter.context'));
+
+ if ($parts) {
+ $component = $parts[0];
+ }
+
+ $canDo = ContentHelper::getActions($component, 'fieldgroup', $groupId);
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ // Avoid nonsense situation.
+ if ($component == 'com_fields') {
+ return;
+ }
+
+ // Load component language file
+ $lang = Factory::getLanguage();
+ $lang->load($component, JPATH_ADMINISTRATOR)
+ || $lang->load($component, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component));
+
+ $title = Text::sprintf('COM_FIELDS_VIEW_GROUPS_TITLE', Text::_(strtoupper($component)));
+
+ // Prepare the toolbar.
+ ToolbarHelper::title($title, 'puzzle-piece fields ' . substr($component, 4) . '-groups');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('group.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('groups.publish')->listCheck(true);
+
+ $childBar->unpublish('groups.unpublish')->listCheck(true);
+
+ $childBar->archive('groups.archive')->listCheck(true);
+ }
+
+ if ($this->getCurrentUser()->authorise('core.admin')) {
+ $childBar->checkin('groups.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && !$this->state->get('filter.state') == -2) {
+ $childBar->trash('groups.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state')) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete', $component)) {
+ $toolbar->delete('groups.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences($component);
+ }
+
+ $toolbar->help('Component:_Field_Groups');
+ }
}
diff --git a/administrator/components/com_fields/tmpl/field/edit.php b/administrator/components/com_fields/tmpl/field/edit.php
index 1a6f95a144c16..61171f19fbbd9 100644
--- a/administrator/components/com_fields/tmpl/field/edit.php
+++ b/administrator/components/com_fields/tmpl/field/edit.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -22,78 +24,79 @@
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_fields.admin-field-edit');
+ ->useScript('form.validate')
+ ->useScript('com_fields.admin-field-edit');
?>
diff --git a/administrator/components/com_fields/tmpl/fields/default.php b/administrator/components/com_fields/tmpl/fields/default.php
index 39808d489be6a..0a2b0033b6178 100644
--- a/administrator/components/com_fields/tmpl/fields/default.php
+++ b/administrator/components/com_fields/tmpl/fields/default.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Categories\Categories;
@@ -21,7 +23,7 @@
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$app = Factory::getApplication();
$user = Factory::getUser();
@@ -38,184 +40,185 @@
$category = Categories::getInstance(str_replace('com_', '', $component) . '.' . $section);
// If there is no category for the component and section, then check the component only
-if (!$category)
-{
- $category = Categories::getInstance(str_replace('com_', '', $component));
+if (!$category) {
+ $category = Categories::getInstance(str_replace('com_', '', $component));
}
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_fields&task=fields.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_fields&task=fields.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$searchToolsOptions = [];
// Only show field contexts filter if there are more than one option
-if (count($this->filterForm->getField('context')->options) > 1)
-{
- $searchToolsOptions['selectorFieldName'] = 'context';
+if (count($this->filterForm->getField('context')->options) > 1) {
+ $searchToolsOptions['selectorFieldName'] = 'context';
}
?>
diff --git a/administrator/components/com_fields/tmpl/fields/default_batch_body.php b/administrator/components/com_fields/tmpl/fields/default_batch_body.php
index 700f26699adcb..c5b1e0c000253 100644
--- a/administrator/components/com_fields/tmpl/fields/default_batch_body.php
+++ b/administrator/components/com_fields/tmpl/fields/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
@@ -22,43 +24,43 @@
?>
diff --git a/administrator/components/com_fields/tmpl/fields/default_batch_footer.php b/administrator/components/com_fields/tmpl/fields/default_batch_footer.php
index 512cb848f01ad..b48333c6a5466 100644
--- a/administrator/components/com_fields/tmpl/fields/default_batch_footer.php
+++ b/administrator/components/com_fields/tmpl/fields/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_fields/tmpl/fields/modal.php b/administrator/components/com_fields/tmpl/fields/modal.php
index 5f7d1c11620f2..15a39f1c485ee 100644
--- a/administrator/components/com_fields/tmpl/fields/modal.php
+++ b/administrator/components/com_fields/tmpl/fields/modal.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -15,9 +17,8 @@
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
-if (Factory::getApplication()->isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if (Factory::getApplication()->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
@@ -30,93 +31,93 @@
?>
diff --git a/administrator/components/com_fields/tmpl/group/edit.php b/administrator/components/com_fields/tmpl/group/edit.php
index 45761b81a852d..cf6f0c1d9baca 100644
--- a/administrator/components/com_fields/tmpl/group/edit.php
+++ b/administrator/components/com_fields/tmpl/group/edit.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -17,7 +19,7 @@
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$input = $app->input;
@@ -27,56 +29,57 @@
?>
diff --git a/administrator/components/com_fields/tmpl/groups/default.php b/administrator/components/com_fields/tmpl/groups/default.php
index ecc9a5bf4b0e2..49c9477e37219 100644
--- a/administrator/components/com_fields/tmpl/groups/default.php
+++ b/administrator/components/com_fields/tmpl/groups/default.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -20,7 +22,7 @@
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$app = Factory::getApplication();
$user = Factory::getUser();
@@ -29,9 +31,8 @@
$component = '';
$parts = FieldsHelper::extract($this->state->get('filter.context'));
-if ($parts)
-{
- $component = $this->escape($parts[0]);
+if ($parts) {
+ $component = $this->escape($parts[0]);
}
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -39,10 +40,9 @@
$ordering = ($listOrder == 'a.ordering');
$saveOrder = ($listOrder == 'a.ordering' && strtolower($listDirn) == 'asc');
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_fields&task=groups.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_fields&task=groups.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$context = $this->escape($this->state->get('filter.context'));
@@ -50,140 +50,143 @@
$searchToolsOptions = [];
// Only show field contexts filter if there are more than one option
-if (count($this->filterForm->getField('context')->options) > 1)
-{
- $searchToolsOptions['selectorFieldName'] = 'context';
+if (count($this->filterForm->getField('context')->options) > 1) {
+ $searchToolsOptions['selectorFieldName'] = 'context';
}
?>
diff --git a/administrator/components/com_fields/tmpl/groups/default_batch_body.php b/administrator/components/com_fields/tmpl/groups/default_batch_body.php
index 9b44ea54a4396..97e7bad090dc2 100644
--- a/administrator/components/com_fields/tmpl/groups/default_batch_body.php
+++ b/administrator/components/com_fields/tmpl/groups/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Multilanguage;
@@ -13,18 +15,18 @@
?>
diff --git a/administrator/components/com_fields/tmpl/groups/default_batch_footer.php b/administrator/components/com_fields/tmpl/groups/default_batch_footer.php
index a0b7215782176..bc7cd3530c4e6 100644
--- a/administrator/components/com_fields/tmpl/groups/default_batch_footer.php
+++ b/administrator/components/com_fields/tmpl/groups/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_finder/helpers/indexer/adapter.php b/administrator/components/com_finder/helpers/indexer/adapter.php
index 02316348db69a..e00a38c6d6fcf 100644
--- a/administrator/components/com_finder/helpers/indexer/adapter.php
+++ b/administrator/components/com_finder/helpers/indexer/adapter.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
- */
-defined('_JEXEC') or die;
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
+ */
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
diff --git a/administrator/components/com_finder/services/provider.php b/administrator/components/com_finder/services/provider.php
index 7688f99cd8a58..2fc098a563e55 100644
--- a/administrator/components/com_finder/services/provider.php
+++ b/administrator/components/com_finder/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Finder'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Finder'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Finder'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Finder'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Finder'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Finder'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new FinderComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new FinderComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_finder/src/Controller/DisplayController.php b/administrator/components/com_finder/src/Controller/DisplayController.php
index aaa94590f34a9..c878d88db0b6c 100644
--- a/administrator/components/com_finder/src/Controller/DisplayController.php
+++ b/administrator/components/com_finder/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'index', 'word');
- $layout = $this->input->get('layout', 'index', 'word');
- $filterId = $this->input->get('filter_id', null, 'int');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean A Controller object to support chaining or false on failure.
+ *
+ * @since 2.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view', 'index', 'word');
+ $layout = $this->input->get('layout', 'index', 'word');
+ $filterId = $this->input->get('filter_id', null, 'int');
- if ($view === 'index')
- {
- $pluginEnabled = PluginHelper::isEnabled('content', 'finder');
+ if ($view === 'index') {
+ $pluginEnabled = PluginHelper::isEnabled('content', 'finder');
- if (!$pluginEnabled)
- {
- $finderPluginId = FinderHelper::getFinderPluginId();
- $link = HTMLHelper::_(
- 'link',
- '#plugin' . $finderPluginId . 'Modal',
- Text::_('COM_FINDER_CONTENT_PLUGIN'),
- 'class="alert-link" data-bs-toggle="modal" id="title-' . $finderPluginId . '"'
- );
- $this->app->enqueueMessage(Text::sprintf('COM_FINDER_INDEX_PLUGIN_CONTENT_NOT_ENABLED_LINK', $link), 'warning');
- }
- }
+ if (!$pluginEnabled) {
+ $finderPluginId = FinderHelper::getFinderPluginId();
+ $link = HTMLHelper::_(
+ 'link',
+ '#plugin' . $finderPluginId . 'Modal',
+ Text::_('COM_FINDER_CONTENT_PLUGIN'),
+ 'class="alert-link" data-bs-toggle="modal" id="title-' . $finderPluginId . '"'
+ );
+ $this->app->enqueueMessage(Text::sprintf('COM_FINDER_INDEX_PLUGIN_CONTENT_NOT_ENABLED_LINK', $link), 'warning');
+ }
+ }
- // Check for edit form.
- if ($view === 'filter' && $layout === 'edit' && !$this->checkEditId('com_finder.edit.filter', $filterId))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $f_id), 'error');
- }
+ // Check for edit form.
+ if ($view === 'filter' && $layout === 'edit' && !$this->checkEditId('com_finder.edit.filter', $filterId)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $f_id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_finder&view=filters', false));
+ $this->setRedirect(Route::_('index.php?option=com_finder&view=filters', false));
- return false;
- }
+ return false;
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/FilterController.php b/administrator/components/com_finder/src/Controller/FilterController.php
index bd85d98034368..3f66664b38461 100644
--- a/administrator/components/com_finder/src/Controller/FilterController.php
+++ b/administrator/components/com_finder/src/Controller/FilterController.php
@@ -1,4 +1,5 @@
checkToken();
-
- /** @var \Joomla\Component\Finder\Administrator\Model\FilterModel $model */
- $model = $this->getModel();
- $table = $model->getTable();
- $data = $this->input->post->get('jform', array(), 'array');
- $checkin = $table->hasField('checked_out');
- $context = "$this->option.edit.$this->context";
- $task = $this->getTask();
-
- // Determine the name of the primary key for the data.
- if (empty($key))
- {
- $key = $table->getKeyName();
- }
-
- // To avoid data collisions the urlVar may be different from the primary key.
- if (empty($urlVar))
- {
- $urlVar = $key;
- }
-
- $recordId = $this->input->get($urlVar, '', 'int');
-
- if (!$this->checkEditId($context, $recordId))
- {
- // Somehow the person just went to the form and tried to save it. We don't allow that.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId), 'error');
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
-
- return false;
- }
-
- // Populate the row id from the session.
- $data[$key] = $recordId;
-
- // The save2copy task needs to be handled slightly differently.
- if ($task === 'save2copy')
- {
- // Check-in the original row.
- if ($checkin && $model->checkin($data[$key]) === false)
- {
- // Check-in failed. Go back to the item and display a notice.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
- }
-
- $this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $urlVar));
-
- return false;
- }
-
- // Reset the ID and then treat the request as for Apply.
- $data[$key] = 0;
- $task = 'apply';
- }
-
- // Access check.
- if (!$this->allowSave($data, $key))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
-
- return false;
- }
-
- // Validate the posted data.
- // Sometimes the form needs some posted data, such as for plugins and modules.
- $form = $model->getForm($data, false);
-
- if (!$form)
- {
- $this->app->enqueueMessage($model->getError(), 'error');
-
- return false;
- }
-
- // Test whether the data is valid.
- $validData = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($validData === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $this->app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $data);
-
- // Redirect back to the edit screen.
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
- );
-
- return false;
- }
-
- // Get and sanitize the filter data.
- $validData['data'] = $this->input->post->get('t', array(), 'array');
- $validData['data'] = array_unique($validData['data']);
- $validData['data'] = ArrayHelper::toInteger($validData['data']);
-
- // Remove any values of zero.
- if (array_search(0, $validData['data'], true))
- {
- unset($validData['data'][array_search(0, $validData['data'], true)]);
- }
-
- // Attempt to save the data.
- if (!$model->save($validData))
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $validData);
-
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
- );
-
- return false;
- }
-
- // Save succeeded, so check-in the record.
- if ($checkin && $model->checkin($validData[$key]) === false)
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $validData);
-
- // Check-in failed, so go back to the record and display a notice.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
- $this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key));
-
- return false;
- }
-
- $this->setMessage(
- Text::_(
- ($this->app->getLanguage()->hasKey($this->text_prefix . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS')
- ? $this->text_prefix : 'JLIB_APPLICATION') . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'
- )
- );
-
- // Redirect the user and adjust session state based on the chosen task.
- switch ($task)
- {
- case 'apply':
- // Set the record data in the session.
- $recordId = $model->getState($this->context . '.id');
- $this->holdEditId($context, $recordId);
- $this->app->setUserState($context . '.data', null);
- $model->checkout($recordId);
-
- // Redirect back to the edit screen.
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
- );
-
- break;
-
- case 'save2new':
- // Clear the record id and data from the session.
- $this->releaseEditId($context, $recordId);
- $this->app->setUserState($context . '.data', null);
-
- // Redirect back to the edit screen.
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend(null, $key), false)
- );
-
- break;
-
- default:
- // Clear the record id and data from the session.
- $this->releaseEditId($context, $recordId);
- $this->app->setUserState($context . '.data', null);
-
- // Redirect to the list screen.
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false)
- );
-
- break;
- }
-
- // Invoke the postSave method to allow for the child class to access the model.
- $this->postSaveHook($model, $validData);
-
- return true;
- }
+ /**
+ * Method to save a record.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 2.5
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Finder\Administrator\Model\FilterModel $model */
+ $model = $this->getModel();
+ $table = $model->getTable();
+ $data = $this->input->post->get('jform', array(), 'array');
+ $checkin = $table->hasField('checked_out');
+ $context = "$this->option.edit.$this->context";
+ $task = $this->getTask();
+
+ // Determine the name of the primary key for the data.
+ if (empty($key)) {
+ $key = $table->getKeyName();
+ }
+
+ // To avoid data collisions the urlVar may be different from the primary key.
+ if (empty($urlVar)) {
+ $urlVar = $key;
+ }
+
+ $recordId = $this->input->get($urlVar, '', 'int');
+
+ if (!$this->checkEditId($context, $recordId)) {
+ // Somehow the person just went to the form and tried to save it. We don't allow that.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId), 'error');
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
+
+ return false;
+ }
+
+ // Populate the row id from the session.
+ $data[$key] = $recordId;
+
+ // The save2copy task needs to be handled slightly differently.
+ if ($task === 'save2copy') {
+ // Check-in the original row.
+ if ($checkin && $model->checkin($data[$key]) === false) {
+ // Check-in failed. Go back to the item and display a notice.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
+ }
+
+ $this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $urlVar));
+
+ return false;
+ }
+
+ // Reset the ID and then treat the request as for Apply.
+ $data[$key] = 0;
+ $task = 'apply';
+ }
+
+ // Access check.
+ if (!$this->allowSave($data, $key)) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
+
+ return false;
+ }
+
+ // Validate the posted data.
+ // Sometimes the form needs some posted data, such as for plugins and modules.
+ $form = $model->getForm($data, false);
+
+ if (!$form) {
+ $this->app->enqueueMessage($model->getError(), 'error');
+
+ return false;
+ }
+
+ // Test whether the data is valid.
+ $validData = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($validData === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $this->app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
+ );
+
+ return false;
+ }
+
+ // Get and sanitize the filter data.
+ $validData['data'] = $this->input->post->get('t', array(), 'array');
+ $validData['data'] = array_unique($validData['data']);
+ $validData['data'] = ArrayHelper::toInteger($validData['data']);
+
+ // Remove any values of zero.
+ if (array_search(0, $validData['data'], true)) {
+ unset($validData['data'][array_search(0, $validData['data'], true)]);
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($validData)) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $validData);
+
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
+ );
+
+ return false;
+ }
+
+ // Save succeeded, so check-in the record.
+ if ($checkin && $model->checkin($validData[$key]) === false) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $validData);
+
+ // Check-in failed, so go back to the record and display a notice.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
+ $this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key));
+
+ return false;
+ }
+
+ $this->setMessage(
+ Text::_(
+ ($this->app->getLanguage()->hasKey($this->text_prefix . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS')
+ ? $this->text_prefix : 'JLIB_APPLICATION') . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'
+ )
+ );
+
+ // Redirect the user and adjust session state based on the chosen task.
+ switch ($task) {
+ case 'apply':
+ // Set the record data in the session.
+ $recordId = $model->getState($this->context . '.id');
+ $this->holdEditId($context, $recordId);
+ $this->app->setUserState($context . '.data', null);
+ $model->checkout($recordId);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
+ );
+
+ break;
+
+ case 'save2new':
+ // Clear the record id and data from the session.
+ $this->releaseEditId($context, $recordId);
+ $this->app->setUserState($context . '.data', null);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend(null, $key), false)
+ );
+
+ break;
+
+ default:
+ // Clear the record id and data from the session.
+ $this->releaseEditId($context, $recordId);
+ $this->app->setUserState($context . '.data', null);
+
+ // Redirect to the list screen.
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false)
+ );
+
+ break;
+ }
+
+ // Invoke the postSave method to allow for the child class to access the model.
+ $this->postSaveHook($model, $validData);
+
+ return true;
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/FiltersController.php b/administrator/components/com_finder/src/Controller/FiltersController.php
index ade78adb5ffd5..9efc8bedafc2d 100644
--- a/administrator/components/com_finder/src/Controller/FiltersController.php
+++ b/administrator/components/com_finder/src/Controller/FiltersController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 2.5
+ */
+ public function getModel($name = 'Filter', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/IndexController.php b/administrator/components/com_finder/src/Controller/IndexController.php
index 2c620bf8863ac..dc65c2c4a32db 100644
--- a/administrator/components/com_finder/src/Controller/IndexController.php
+++ b/administrator/components/com_finder/src/Controller/IndexController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to optimise the index by removing orphaned entries.
- *
- * @return boolean True on success.
- *
- * @since 4.2.0
- */
- public function optimise()
- {
- $this->checkToken();
-
- // Optimise the index by first running the garbage collection
- PluginHelper::importPlugin('finder');
- $this->app->triggerEvent('onFinderGarbageCollection');
-
- // Now run the optimisation method from the indexer
- $indexer = new Indexer;
- $indexer->optimize();
-
- $message = Text::_('COM_FINDER_INDEX_OPTIMISE_FINISHED');
- $this->setRedirect('index.php?option=com_finder&view=index', $message);
-
- return true;
- }
-
- /**
- * Method to purge all indexed links from the database.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- */
- public function purge()
- {
- $this->checkToken();
-
- // Remove the script time limit.
- @set_time_limit(0);
-
- /** @var \Joomla\Component\Finder\Administrator\Model\IndexModel $model */
- $model = $this->getModel('Index', 'Administrator');
-
- // Attempt to purge the index.
- $return = $model->purge();
-
- if (!$return)
- {
- $message = Text::_('COM_FINDER_INDEX_PURGE_FAILED', $model->getError());
- $this->setRedirect('index.php?option=com_finder&view=index', $message);
-
- return false;
- }
- else
- {
- $message = Text::_('COM_FINDER_INDEX_PURGE_SUCCESS');
- $this->setRedirect('index.php?option=com_finder&view=index', $message);
-
- return true;
- }
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 2.5
+ */
+ public function getModel($name = 'Index', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to optimise the index by removing orphaned entries.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.2.0
+ */
+ public function optimise()
+ {
+ $this->checkToken();
+
+ // Optimise the index by first running the garbage collection
+ PluginHelper::importPlugin('finder');
+ $this->app->triggerEvent('onFinderGarbageCollection');
+
+ // Now run the optimisation method from the indexer
+ $indexer = new Indexer();
+ $indexer->optimize();
+
+ $message = Text::_('COM_FINDER_INDEX_OPTIMISE_FINISHED');
+ $this->setRedirect('index.php?option=com_finder&view=index', $message);
+
+ return true;
+ }
+
+ /**
+ * Method to purge all indexed links from the database.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ */
+ public function purge()
+ {
+ $this->checkToken();
+
+ // Remove the script time limit.
+ @set_time_limit(0);
+
+ /** @var \Joomla\Component\Finder\Administrator\Model\IndexModel $model */
+ $model = $this->getModel('Index', 'Administrator');
+
+ // Attempt to purge the index.
+ $return = $model->purge();
+
+ if (!$return) {
+ $message = Text::_('COM_FINDER_INDEX_PURGE_FAILED', $model->getError());
+ $this->setRedirect('index.php?option=com_finder&view=index', $message);
+
+ return false;
+ } else {
+ $message = Text::_('COM_FINDER_INDEX_PURGE_SUCCESS');
+ $this->setRedirect('index.php?option=com_finder&view=index', $message);
+
+ return true;
+ }
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/IndexerController.php b/administrator/components/com_finder/src/Controller/IndexerController.php
index 28ef045c49fdd..473df50049cc6 100644
--- a/administrator/components/com_finder/src/Controller/IndexerController.php
+++ b/administrator/components/com_finder/src/Controller/IndexerController.php
@@ -1,4 +1,5 @@
get('enable_logging', '0'))
- {
- $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
- $options['text_file'] = 'indexer.php';
- Log::addLogger($options);
- }
-
- // Log the start
- try
- {
- Log::add('Starting the indexer', Log::INFO);
- }
- catch (\RuntimeException $exception)
- {
- // Informational log only
- }
-
- // We don't want this form to be cached.
- $this->app->allowCache(false);
-
- // Put in a buffer to silence noise.
- ob_start();
-
- // Reset the indexer state.
- Indexer::resetState();
-
- // Import the finder plugins.
- PluginHelper::importPlugin('finder');
-
- // Add the indexer language to \JS
- Text::script('COM_FINDER_AN_ERROR_HAS_OCCURRED');
- Text::script('COM_FINDER_NO_ERROR_RETURNED');
-
- // Start the indexer.
- try
- {
- // Trigger the onStartIndex event.
- $this->app->triggerEvent('onStartIndex');
-
- // Get the indexer state.
- $state = Indexer::getState();
- $state->start = 1;
-
- // Send the response.
- static::sendResponse($state);
- }
-
- // Catch an exception and return the response.
- catch (\Exception $e)
- {
- static::sendResponse($e);
- }
- }
-
- /**
- * Method to run the next batch of content through the indexer.
- *
- * @return void
- *
- * @since 2.5
- */
- public function batch()
- {
- // Check for a valid token. If invalid, send a 403 with the error message.
- if (!Session::checkToken('request'))
- {
- static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
-
- return;
- }
-
- $params = ComponentHelper::getParams('com_finder');
-
- if ($params->get('enable_logging', '0'))
- {
- $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
- $options['text_file'] = 'indexer.php';
- Log::addLogger($options);
- }
-
- // Log the start
- try
- {
- Log::add('Starting the indexer batch process', Log::INFO);
- }
- catch (\RuntimeException $exception)
- {
- // Informational log only
- }
-
- // We don't want this form to be cached.
- $this->app->allowCache(false);
-
- // Put in a buffer to silence noise.
- ob_start();
-
- // Remove the script time limit.
- @set_time_limit(0);
-
- // Get the indexer state.
- $state = Indexer::getState();
-
- // Reset the batch offset.
- $state->batchOffset = 0;
-
- // Update the indexer state.
- Indexer::setState($state);
-
- // Import the finder plugins.
- PluginHelper::importPlugin('finder');
-
- /*
- * We are going to swap out the raw document object with an HTML document
- * in order to work around some plugins that don't do proper environment
- * checks before trying to use HTML document functions.
- */
- $lang = Factory::getLanguage();
-
- // Get the document properties.
- $attributes = array (
- 'charset' => 'utf-8',
- 'lineend' => 'unix',
- 'tab' => ' ',
- 'language' => $lang->getTag(),
- 'direction' => $lang->isRtl() ? 'rtl' : 'ltr'
- );
-
- // Start the indexer.
- try
- {
- // Trigger the onBeforeIndex event.
- $this->app->triggerEvent('onBeforeIndex');
-
- // Trigger the onBuildIndex event.
- $this->app->triggerEvent('onBuildIndex');
-
- // Get the indexer state.
- $state = Indexer::getState();
- $state->start = 0;
- $state->complete = 0;
-
- // Log batch completion and memory high-water mark.
- try
- {
- Log::add('Batch completed, peak memory usage: ' . number_format(memory_get_peak_usage(true)) . ' bytes', Log::INFO);
- }
- catch (\RuntimeException $exception)
- {
- // Informational log only
- }
-
- // Send the response.
- static::sendResponse($state);
- }
-
- // Catch an exception and return the response.
- catch (\Exception $e)
- {
- // Send the response.
- static::sendResponse($e);
- }
- }
-
- /**
- * Method to optimize the index and perform any necessary cleanup.
- *
- * @return void
- *
- * @since 2.5
- */
- public function optimize()
- {
- // Check for a valid token. If invalid, send a 403 with the error message.
- if (!Session::checkToken('request'))
- {
- static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
-
- return;
- }
-
- // We don't want this form to be cached.
- $this->app->allowCache(false);
-
- // Put in a buffer to silence noise.
- ob_start();
-
- // Import the finder plugins.
- PluginHelper::importPlugin('finder');
-
- try
- {
- // Optimize the index
- $indexer = new Indexer;
- $indexer->optimize();
-
- // Get the indexer state.
- $state = Indexer::getState();
- $state->start = 0;
- $state->complete = 1;
-
- // Send the response.
- static::sendResponse($state);
- }
-
- // Catch an exception and return the response.
- catch (\Exception $e)
- {
- static::sendResponse($e);
- }
- }
-
- /**
- * Method to handle a send a \JSON response. The body parameter
- * can be an \Exception object for when an error has occurred or
- * a CMSObject for a good response.
- *
- * @param \Joomla\CMS\Object\CMSObject|\Exception $data CMSObject on success, \Exception on error. [optional]
- *
- * @return void
- *
- * @since 2.5
- */
- public static function sendResponse($data = null)
- {
- $app = Factory::getApplication();
-
- $params = ComponentHelper::getParams('com_finder');
-
- if ($params->get('enable_logging', '0'))
- {
- $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
- $options['text_file'] = 'indexer.php';
- Log::addLogger($options);
- }
-
- // Send the assigned error code if we are catching an exception.
- if ($data instanceof \Exception)
- {
- try
- {
- Log::add($data->getMessage(), Log::ERROR);
- }
- catch (\RuntimeException $exception)
- {
- // Informational log only
- }
-
- $app->setHeader('status', $data->getCode());
- }
-
- // Create the response object.
- $response = new Response($data);
-
- if (\JDEBUG)
- {
- // Add the buffer and memory usage
- $response->buffer = ob_get_contents();
- $response->memory = memory_get_usage(true);
- }
-
- // Send the JSON response.
- echo json_encode($response);
- }
+ /**
+ * Method to start the indexer.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public function start()
+ {
+ // Check for a valid token. If invalid, send a 403 with the error message.
+ if (!Session::checkToken('request')) {
+ static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
+
+ return;
+ }
+
+ $params = ComponentHelper::getParams('com_finder');
+
+ if ($params->get('enable_logging', '0')) {
+ $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
+ $options['text_file'] = 'indexer.php';
+ Log::addLogger($options);
+ }
+
+ // Log the start
+ try {
+ Log::add('Starting the indexer', Log::INFO);
+ } catch (\RuntimeException $exception) {
+ // Informational log only
+ }
+
+ // We don't want this form to be cached.
+ $this->app->allowCache(false);
+
+ // Put in a buffer to silence noise.
+ ob_start();
+
+ // Reset the indexer state.
+ Indexer::resetState();
+
+ // Import the finder plugins.
+ PluginHelper::importPlugin('finder');
+
+ // Add the indexer language to \JS
+ Text::script('COM_FINDER_AN_ERROR_HAS_OCCURRED');
+ Text::script('COM_FINDER_NO_ERROR_RETURNED');
+
+ // Start the indexer.
+ try {
+ // Trigger the onStartIndex event.
+ $this->app->triggerEvent('onStartIndex');
+
+ // Get the indexer state.
+ $state = Indexer::getState();
+ $state->start = 1;
+
+ // Send the response.
+ static::sendResponse($state);
+ } catch (\Exception $e) {
+ // Catch an exception and return the response.
+ static::sendResponse($e);
+ }
+ }
+
+ /**
+ * Method to run the next batch of content through the indexer.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public function batch()
+ {
+ // Check for a valid token. If invalid, send a 403 with the error message.
+ if (!Session::checkToken('request')) {
+ static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
+
+ return;
+ }
+
+ $params = ComponentHelper::getParams('com_finder');
+
+ if ($params->get('enable_logging', '0')) {
+ $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
+ $options['text_file'] = 'indexer.php';
+ Log::addLogger($options);
+ }
+
+ // Log the start
+ try {
+ Log::add('Starting the indexer batch process', Log::INFO);
+ } catch (\RuntimeException $exception) {
+ // Informational log only
+ }
+
+ // We don't want this form to be cached.
+ $this->app->allowCache(false);
+
+ // Put in a buffer to silence noise.
+ ob_start();
+
+ // Remove the script time limit.
+ @set_time_limit(0);
+
+ // Get the indexer state.
+ $state = Indexer::getState();
+
+ // Reset the batch offset.
+ $state->batchOffset = 0;
+
+ // Update the indexer state.
+ Indexer::setState($state);
+
+ // Import the finder plugins.
+ PluginHelper::importPlugin('finder');
+
+ /*
+ * We are going to swap out the raw document object with an HTML document
+ * in order to work around some plugins that don't do proper environment
+ * checks before trying to use HTML document functions.
+ */
+ $lang = Factory::getLanguage();
+
+ // Get the document properties.
+ $attributes = array (
+ 'charset' => 'utf-8',
+ 'lineend' => 'unix',
+ 'tab' => ' ',
+ 'language' => $lang->getTag(),
+ 'direction' => $lang->isRtl() ? 'rtl' : 'ltr'
+ );
+
+ // Start the indexer.
+ try {
+ // Trigger the onBeforeIndex event.
+ $this->app->triggerEvent('onBeforeIndex');
+
+ // Trigger the onBuildIndex event.
+ $this->app->triggerEvent('onBuildIndex');
+
+ // Get the indexer state.
+ $state = Indexer::getState();
+ $state->start = 0;
+ $state->complete = 0;
+
+ // Log batch completion and memory high-water mark.
+ try {
+ Log::add('Batch completed, peak memory usage: ' . number_format(memory_get_peak_usage(true)) . ' bytes', Log::INFO);
+ } catch (\RuntimeException $exception) {
+ // Informational log only
+ }
+
+ // Send the response.
+ static::sendResponse($state);
+ } catch (\Exception $e) {
+ // Catch an exception and return the response.
+ // Send the response.
+ static::sendResponse($e);
+ }
+ }
+
+ /**
+ * Method to optimize the index and perform any necessary cleanup.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public function optimize()
+ {
+ // Check for a valid token. If invalid, send a 403 with the error message.
+ if (!Session::checkToken('request')) {
+ static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
+
+ return;
+ }
+
+ // We don't want this form to be cached.
+ $this->app->allowCache(false);
+
+ // Put in a buffer to silence noise.
+ ob_start();
+
+ // Import the finder plugins.
+ PluginHelper::importPlugin('finder');
+
+ try {
+ // Optimize the index
+ $indexer = new Indexer();
+ $indexer->optimize();
+
+ // Get the indexer state.
+ $state = Indexer::getState();
+ $state->start = 0;
+ $state->complete = 1;
+
+ // Send the response.
+ static::sendResponse($state);
+ } catch (\Exception $e) {
+ // Catch an exception and return the response.
+ static::sendResponse($e);
+ }
+ }
+
+ /**
+ * Method to handle a send a \JSON response. The body parameter
+ * can be an \Exception object for when an error has occurred or
+ * a CMSObject for a good response.
+ *
+ * @param \Joomla\CMS\Object\CMSObject|\Exception $data CMSObject on success, \Exception on error. [optional]
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public static function sendResponse($data = null)
+ {
+ $app = Factory::getApplication();
+
+ $params = ComponentHelper::getParams('com_finder');
+
+ if ($params->get('enable_logging', '0')) {
+ $options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
+ $options['text_file'] = 'indexer.php';
+ Log::addLogger($options);
+ }
+
+ // Send the assigned error code if we are catching an exception.
+ if ($data instanceof \Exception) {
+ try {
+ Log::add($data->getMessage(), Log::ERROR);
+ } catch (\RuntimeException $exception) {
+ // Informational log only
+ }
+
+ $app->setHeader('status', $data->getCode());
+ }
+
+ // Create the response object.
+ $response = new Response($data);
+
+ if (\JDEBUG) {
+ // Add the buffer and memory usage
+ $response->buffer = ob_get_contents();
+ $response->memory = memory_get_usage(true);
+ }
+
+ // Send the JSON response.
+ echo json_encode($response);
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/MapsController.php b/administrator/components/com_finder/src/Controller/MapsController.php
index 149520edb4066..e0390f9df66c9 100644
--- a/administrator/components/com_finder/src/Controller/MapsController.php
+++ b/administrator/components/com_finder/src/Controller/MapsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Maps', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_finder/src/Controller/SearchesController.php b/administrator/components/com_finder/src/Controller/SearchesController.php
index 325893b9eede0..a820b2ee784c3 100644
--- a/administrator/components/com_finder/src/Controller/SearchesController.php
+++ b/administrator/components/com_finder/src/Controller/SearchesController.php
@@ -1,4 +1,5 @@
getModel('Searches');
+ $model = $this->getModel('Searches');
- if (!$model->reset())
- {
- $this->app->enqueueMessage($model->getError(), 'error');
- }
+ if (!$model->reset()) {
+ $this->app->enqueueMessage($model->getError(), 'error');
+ }
- $this->setRedirect('index.php?option=com_finder&view=searches');
- }
+ $this->setRedirect('index.php?option=com_finder&view=searches');
+ }
}
diff --git a/administrator/components/com_finder/src/Extension/FinderComponent.php b/administrator/components/com_finder/src/Extension/FinderComponent.php
index 65944b880778a..ab48149a47b49 100644
--- a/administrator/components/com_finder/src/Extension/FinderComponent.php
+++ b/administrator/components/com_finder/src/Extension/FinderComponent.php
@@ -1,4 +1,5 @@
setDatabase($container->get(DatabaseInterface::class));
-
- $this->getRegistry()->register('finder', $finder);
-
- $filter = new Filter;
- $filter->setDatabase($container->get(DatabaseInterface::class));
-
- $this->getRegistry()->register('filter', $filter);
-
- $this->getRegistry()->register('query', new Query);
- }
+ use RouterServiceTrait;
+ use HTMLRegistryAwareTrait;
+
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $finder = new Finder();
+ $finder->setDatabase($container->get(DatabaseInterface::class));
+
+ $this->getRegistry()->register('finder', $finder);
+
+ $filter = new Filter();
+ $filter->setDatabase($container->get(DatabaseInterface::class));
+
+ $this->getRegistry()->register('filter', $filter);
+
+ $this->getRegistry()->register('query', new Query());
+ }
}
diff --git a/administrator/components/com_finder/src/Field/BranchesField.php b/administrator/components/com_finder/src/Field/BranchesField.php
index 6d3994ff76b79..7c22537b75d73 100644
--- a/administrator/components/com_finder/src/Field/BranchesField.php
+++ b/administrator/components/com_finder/src/Field/BranchesField.php
@@ -1,4 +1,5 @@
bootComponent('com_finder');
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.5
+ */
+ public function getOptions()
+ {
+ Factory::getApplication()->bootComponent('com_finder');
- return HTMLHelper::_('finder.mapslist');
- }
+ return HTMLHelper::_('finder.mapslist');
+ }
}
diff --git a/administrator/components/com_finder/src/Field/ContentmapField.php b/administrator/components/com_finder/src/Field/ContentmapField.php
index 8a71a3f710f43..2dece7db516eb 100644
--- a/administrator/components/com_finder/src/Field/ContentmapField.php
+++ b/administrator/components/com_finder/src/Field/ContentmapField.php
@@ -1,4 +1,5 @@
getDatabase();
-
- // Main query.
- $query = $db->getQuery(true)
- ->select($db->quoteName('a.title', 'text'))
- ->select($db->quoteName('a.id', 'value'))
- ->select($db->quoteName('a.parent_id'))
- ->select($db->quoteName('a.level'))
- ->from($db->quoteName('#__finder_taxonomy', 'a'))
- ->where($db->quoteName('a.parent_id') . ' <> 0')
- ->order('a.title ASC');
-
- $db->setQuery($query);
-
- try
- {
- $contentMap = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- return [];
- }
-
- // Build the grouped list array.
- if ($contentMap)
- {
- $parents = [];
-
- foreach ($contentMap as $item)
- {
- if (!isset($parents[$item->parent_id]))
- {
- $parents[$item->parent_id] = [];
- }
-
- $parents[$item->parent_id][] = $item;
- }
-
- foreach ($parents[1] as $branch)
- {
- $groups[$branch->text] = $this->prepareLevel($branch->value, $parents);
- }
- }
-
- // Merge any additional groups in the XML definition.
- $groups = array_merge(parent::getGroups(), $groups);
-
- return $groups;
- }
-
- /**
- * Indenting and translating options for the list
- *
- * @param int $parent Parent ID to process
- * @param array $parents Array of arrays of items with parent IDs as keys
- *
- * @return array The indented list of entries for this branch
- *
- * @since 4.1.5
- */
- private function prepareLevel($parent, $parents)
- {
- $lang = Factory::getLanguage();
- $entries = [];
-
- foreach ($parents[$parent] as $item)
- {
- $levelPrefix = str_repeat('- ', $item->level - 1);
-
- if (trim($item->text, '*') === 'Language')
- {
- $text = LanguageHelper::branchLanguageTitle($item->text);
- }
- else
- {
- $key = LanguageHelper::branchSingular($item->text);
- $text = $lang->hasKey($key) ? Text::_($key) : $item->text;
- }
-
- $entries[] = HTMLHelper::_('select.option', $item->value, $levelPrefix . $text);
-
- if (isset($parents[$item->value]))
- {
- $entries = array_merge($entries, $this->prepareLevel($item->value, $parents));
- }
- }
-
- return $entries;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.6.0
+ */
+ public $type = 'ContentMap';
+
+ /**
+ * Method to get the list of content map options grouped by first level.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 3.6.0
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+
+ // Main query.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('a.title', 'text'))
+ ->select($db->quoteName('a.id', 'value'))
+ ->select($db->quoteName('a.parent_id'))
+ ->select($db->quoteName('a.level'))
+ ->from($db->quoteName('#__finder_taxonomy', 'a'))
+ ->where($db->quoteName('a.parent_id') . ' <> 0')
+ ->order('a.title ASC');
+
+ $db->setQuery($query);
+
+ try {
+ $contentMap = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ return [];
+ }
+
+ // Build the grouped list array.
+ if ($contentMap) {
+ $parents = [];
+
+ foreach ($contentMap as $item) {
+ if (!isset($parents[$item->parent_id])) {
+ $parents[$item->parent_id] = [];
+ }
+
+ $parents[$item->parent_id][] = $item;
+ }
+
+ foreach ($parents[1] as $branch) {
+ $groups[$branch->text] = $this->prepareLevel($branch->value, $parents);
+ }
+ }
+
+ // Merge any additional groups in the XML definition.
+ $groups = array_merge(parent::getGroups(), $groups);
+
+ return $groups;
+ }
+
+ /**
+ * Indenting and translating options for the list
+ *
+ * @param int $parent Parent ID to process
+ * @param array $parents Array of arrays of items with parent IDs as keys
+ *
+ * @return array The indented list of entries for this branch
+ *
+ * @since 4.1.5
+ */
+ private function prepareLevel($parent, $parents)
+ {
+ $lang = Factory::getLanguage();
+ $entries = [];
+
+ foreach ($parents[$parent] as $item) {
+ $levelPrefix = str_repeat('- ', $item->level - 1);
+
+ if (trim($item->text, '*') === 'Language') {
+ $text = LanguageHelper::branchLanguageTitle($item->text);
+ } else {
+ $key = LanguageHelper::branchSingular($item->text);
+ $text = $lang->hasKey($key) ? Text::_($key) : $item->text;
+ }
+
+ $entries[] = HTMLHelper::_('select.option', $item->value, $levelPrefix . $text);
+
+ if (isset($parents[$item->value])) {
+ $entries = array_merge($entries, $this->prepareLevel($item->value, $parents));
+ }
+ }
+
+ return $entries;
+ }
}
diff --git a/administrator/components/com_finder/src/Field/ContenttypesField.php b/administrator/components/com_finder/src/Field/ContenttypesField.php
index cefd638be56c2..3072ec3049904 100644
--- a/administrator/components/com_finder/src/Field/ContenttypesField.php
+++ b/administrator/components/com_finder/src/Field/ContenttypesField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id', 'value'))
- ->select($db->quoteName('title', 'text'))
- ->from($db->quoteName('#__finder_types'));
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id', 'value'))
+ ->select($db->quoteName('title', 'text'))
+ ->from($db->quoteName('#__finder_types'));
- // Get the options.
- $db->setQuery($query);
+ // Get the options.
+ $db->setQuery($query);
- try
- {
- $contentTypes = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
+ try {
+ $contentTypes = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
- // Translate.
- foreach ($contentTypes as $contentType)
- {
- $key = LanguageHelper::branchSingular($contentType->text);
- $contentType->translatedText = $lang->hasKey($key) ? Text::_($key) : $contentType->text;
- }
+ // Translate.
+ foreach ($contentTypes as $contentType) {
+ $key = LanguageHelper::branchSingular($contentType->text);
+ $contentType->translatedText = $lang->hasKey($key) ? Text::_($key) : $contentType->text;
+ }
- // Order by title.
- $contentTypes = ArrayHelper::sortObjects($contentTypes, 'translatedText', 1, true, true);
+ // Order by title.
+ $contentTypes = ArrayHelper::sortObjects($contentTypes, 'translatedText', 1, true, true);
- // Convert the values to options.
- foreach ($contentTypes as $contentType)
- {
- $options[] = HTMLHelper::_('select.option', $contentType->value, $contentType->translatedText);
- }
+ // Convert the values to options.
+ foreach ($contentTypes as $contentType) {
+ $options[] = HTMLHelper::_('select.option', $contentType->value, $contentType->translatedText);
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_finder/src/Field/SearchfilterField.php b/administrator/components/com_finder/src/Field/SearchfilterField.php
index d784b75cc1f7d..d55abe528b5f0 100644
--- a/administrator/components/com_finder/src/Field/SearchfilterField.php
+++ b/administrator/components/com_finder/src/Field/SearchfilterField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('f.title AS text, f.filter_id AS value')
- ->from($db->quoteName('#__finder_filters') . ' AS f')
- ->where('f.state = 1')
- ->order('f.title ASC');
- $db->setQuery($query);
- $options = $db->loadObjectList();
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 2.5
+ */
+ public function getOptions()
+ {
+ // Build the query.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('f.title AS text, f.filter_id AS value')
+ ->from($db->quoteName('#__finder_filters') . ' AS f')
+ ->where('f.state = 1')
+ ->order('f.title ASC');
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
- array_unshift($options, HTMLHelper::_('select.option', '', Text::_('COM_FINDER_SELECT_SEARCH_FILTER'), 'value', 'text'));
+ array_unshift($options, HTMLHelper::_('select.option', '', Text::_('COM_FINDER_SELECT_SEARCH_FILTER'), 'value', 'text'));
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_finder/src/Helper/FinderHelper.php b/administrator/components/com_finder/src/Helper/FinderHelper.php
index ca51836df7381..d3b76042482ec 100644
--- a/administrator/components/com_finder/src/Helper/FinderHelper.php
+++ b/administrator/components/com_finder/src/Helper/FinderHelper.php
@@ -1,4 +1,5 @@
extension_id : 0;
- }
+ return $pluginRecord !== null ? $pluginRecord->extension_id : 0;
+ }
}
diff --git a/administrator/components/com_finder/src/Helper/LanguageHelper.php b/administrator/components/com_finder/src/Helper/LanguageHelper.php
index a6e5edf8e3ab7..dc388b1d4ec53 100644
--- a/administrator/components/com_finder/src/Helper/LanguageHelper.php
+++ b/administrator/components/com_finder/src/Helper/LanguageHelper.php
@@ -1,4 +1,5 @@
getLanguage();
-
- if ($language->hasKey('PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return) || JDEBUG)
- {
- return 'PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return;
- }
-
- return $branchName;
- }
-
- /**
- * Method to return the language name for a language taxonomy branch.
- *
- * @param string $branchName Language branch name.
- *
- * @return string The language title.
- *
- * @since 3.6.0
- */
- public static function branchLanguageTitle($branchName)
- {
- $title = $branchName;
-
- if ($branchName === '*')
- {
- $title = Text::_('JALL_LANGUAGE');
- }
- else
- {
- $languages = CMSLanguageHelper::getLanguages('lang_code');
-
- if (isset($languages[$branchName]))
- {
- $title = $languages[$branchName]->title;
- }
- }
-
- return $title;
- }
-
- /**
- * Method to load Smart Search component language file.
- *
- * @return void
- *
- * @since 2.5
- */
- public static function loadComponentLanguage()
- {
- Factory::getLanguage()->load('com_finder', JPATH_SITE);
- }
-
- /**
- * Method to load Smart Search plugin language files.
- *
- * @return void
- *
- * @since 2.5
- */
- public static function loadPluginLanguage()
- {
- static $loaded = false;
-
- // If already loaded, don't load again.
- if ($loaded)
- {
- return;
- }
-
- $loaded = true;
-
- // Get array of all the enabled Smart Search plugin names.
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(array($db->quoteName('name'), $db->quoteName('element')))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('finder'))
- ->where($db->quoteName('enabled') . ' = 1');
- $db->setQuery($query);
- $plugins = $db->loadObjectList();
-
- if (empty($plugins))
- {
- return;
- }
-
- // Load generic language strings.
- $lang = Factory::getLanguage();
- $lang->load('plg_content_finder', JPATH_ADMINISTRATOR);
-
- // Load language file for each plugin.
- foreach ($plugins as $plugin)
- {
- $lang->load($plugin->name, JPATH_ADMINISTRATOR)
- || $lang->load($plugin->name, JPATH_PLUGINS . '/finder/' . $plugin->element);
- }
- }
+ /**
+ * Method to return a plural language code for a taxonomy branch.
+ *
+ * @param string $branchName Branch title.
+ *
+ * @return string Language key code.
+ *
+ * @since 2.5
+ */
+ public static function branchPlural($branchName)
+ {
+ $return = preg_replace('/[^a-zA-Z0-9]+/', '_', strtoupper($branchName));
+
+ if ($return !== '_') {
+ return 'PLG_FINDER_QUERY_FILTER_BRANCH_P_' . $return;
+ }
+
+ return $branchName;
+ }
+
+ /**
+ * Method to return a singular language code for a taxonomy branch.
+ *
+ * @param string $branchName Branch name.
+ *
+ * @return string Language key code.
+ *
+ * @since 2.5
+ */
+ public static function branchSingular($branchName)
+ {
+ $return = preg_replace('/[^a-zA-Z0-9]+/', '_', strtoupper($branchName));
+ $language = Factory::getApplication()->getLanguage();
+
+ if ($language->hasKey('PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return) || JDEBUG) {
+ return 'PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return;
+ }
+
+ return $branchName;
+ }
+
+ /**
+ * Method to return the language name for a language taxonomy branch.
+ *
+ * @param string $branchName Language branch name.
+ *
+ * @return string The language title.
+ *
+ * @since 3.6.0
+ */
+ public static function branchLanguageTitle($branchName)
+ {
+ $title = $branchName;
+
+ if ($branchName === '*') {
+ $title = Text::_('JALL_LANGUAGE');
+ } else {
+ $languages = CMSLanguageHelper::getLanguages('lang_code');
+
+ if (isset($languages[$branchName])) {
+ $title = $languages[$branchName]->title;
+ }
+ }
+
+ return $title;
+ }
+
+ /**
+ * Method to load Smart Search component language file.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public static function loadComponentLanguage()
+ {
+ Factory::getLanguage()->load('com_finder', JPATH_SITE);
+ }
+
+ /**
+ * Method to load Smart Search plugin language files.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public static function loadPluginLanguage()
+ {
+ static $loaded = false;
+
+ // If already loaded, don't load again.
+ if ($loaded) {
+ return;
+ }
+
+ $loaded = true;
+
+ // Get array of all the enabled Smart Search plugin names.
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(array($db->quoteName('name'), $db->quoteName('element')))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('finder'))
+ ->where($db->quoteName('enabled') . ' = 1');
+ $db->setQuery($query);
+ $plugins = $db->loadObjectList();
+
+ if (empty($plugins)) {
+ return;
+ }
+
+ // Load generic language strings.
+ $lang = Factory::getLanguage();
+ $lang->load('plg_content_finder', JPATH_ADMINISTRATOR);
+
+ // Load language file for each plugin.
+ foreach ($plugins as $plugin) {
+ $lang->load($plugin->name, JPATH_ADMINISTRATOR)
+ || $lang->load($plugin->name, JPATH_PLUGINS . '/finder/' . $plugin->element);
+ }
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Adapter.php b/administrator/components/com_finder/src/Indexer/Adapter.php
index 0d7d9b341bbe9..5ae4656181dfe 100644
--- a/administrator/components/com_finder/src/Indexer/Adapter.php
+++ b/administrator/components/com_finder/src/Indexer/Adapter.php
@@ -1,4 +1,5 @@
type_id = $this->getTypeId();
-
- // Add the content type if it doesn't exist and is set.
- if (empty($this->type_id) && !empty($this->type_title))
- {
- $this->type_id = Helper::addContentType($this->type_title, $this->mime);
- }
-
- // Check for a layout override.
- if ($this->params->get('layout'))
- {
- $this->layout = $this->params->get('layout');
- }
-
- // Get the indexer object
- $this->indexer = new Indexer($this->db);
- }
-
- /**
- * Method to get the adapter state and push it into the indexer.
- *
- * @return void
- *
- * @since 2.5
- * @throws Exception on error.
- */
- public function onStartIndex()
- {
- // Get the indexer state.
- $iState = Indexer::getState();
-
- // Get the number of content items.
- $total = (int) $this->getContentCount();
-
- // Add the content count to the total number of items.
- $iState->totalItems += $total;
-
- // Populate the indexer state information for the adapter.
- $iState->pluginState[$this->context]['total'] = $total;
- $iState->pluginState[$this->context]['offset'] = 0;
-
- // Set the indexer state.
- Indexer::setState($iState);
- }
-
- /**
- * Method to prepare for the indexer to be run. This method will often
- * be used to include dependencies and things of that nature.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on error.
- */
- public function onBeforeIndex()
- {
- // Get the indexer and adapter state.
- $iState = Indexer::getState();
- $aState = $iState->pluginState[$this->context];
-
- // Check the progress of the indexer and the adapter.
- if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total'])
- {
- return true;
- }
-
- // Run the setup method.
- return $this->setup();
- }
-
- /**
- * Method to index a batch of content items. This method can be called by
- * the indexer many times throughout the indexing process depending on how
- * much content is available for indexing. It is important to track the
- * progress correctly so we can display it to the user.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on error.
- */
- public function onBuildIndex()
- {
- // Get the indexer and adapter state.
- $iState = Indexer::getState();
- $aState = $iState->pluginState[$this->context];
-
- // Check the progress of the indexer and the adapter.
- if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total'])
- {
- return true;
- }
-
- // Get the batch offset and size.
- $offset = (int) $aState['offset'];
- $limit = (int) ($iState->batchSize - $iState->batchOffset);
-
- // Get the content items to index.
- $items = $this->getItems($offset, $limit);
-
- // Iterate through the items and index them.
- for ($i = 0, $n = count($items); $i < $n; $i++)
- {
- // Index the item.
- $this->index($items[$i]);
-
- // Adjust the offsets.
- $offset++;
- $iState->batchOffset++;
- $iState->totalItems--;
- }
-
- // Update the indexer state.
- $aState['offset'] = $offset;
- $iState->pluginState[$this->context] = $aState;
- Indexer::setState($iState);
-
- return true;
- }
-
- /**
- * Method to remove outdated index entries
- *
- * @return integer
- *
- * @since 4.2.0
- */
- public function onFinderGarbageCollection()
- {
- $db = $this->db;
- $type_id = $this->getTypeId();
-
- $query = $db->getQuery(true);
- $subquery = $db->getQuery(true);
- $subquery->select('CONCAT(' . $db->quote($this->getUrl('', $this->extension, $this->layout)) . ', id)')
- ->from($db->quoteName($this->table));
- $query->select($db->quoteName('l.link_id'))
- ->from($db->quoteName('#__finder_links', 'l'))
- ->where($db->quoteName('l.type_id') . ' = ' . $type_id)
- ->where($db->quoteName('l.url') . ' LIKE ' . $db->quote($this->getUrl('%', $this->extension, $this->layout)))
- ->where($db->quoteName('l.url') . ' NOT IN (' . $subquery . ')');
- $db->setQuery($query);
- $items = $db->loadColumn();
-
- foreach ($items as $item)
- {
- $this->indexer->remove($item);
- }
-
- return count($items);
- }
-
- /**
- * Method to change the value of a content item's property in the links
- * table. This is used to synchronize published and access states that
- * are changed when not editing an item directly.
- *
- * @param string $id The ID of the item to change.
- * @param string $property The property that is being changed.
- * @param integer $value The new value of that property.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function change($id, $property, $value)
- {
- // Check for a property we know how to handle.
- if ($property !== 'state' && $property !== 'access')
- {
- return true;
- }
-
- // Get the URL for the content id.
- $item = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
-
- // Update the content items.
- $query = $this->db->getQuery(true)
- ->update($this->db->quoteName('#__finder_links'))
- ->set($this->db->quoteName($property) . ' = ' . (int) $value)
- ->where($this->db->quoteName('url') . ' = ' . $item);
- $this->db->setQuery($query);
- $this->db->execute();
-
- return true;
- }
-
- /**
- * Method to index an item.
- *
- * @param Result $item The item to index as a Result object.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- abstract protected function index(Result $item);
-
- /**
- * Method to reindex an item.
- *
- * @param integer $id The ID of the item to reindex.
- *
- * @return void
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function reindex($id)
- {
- // Run the setup method.
- $this->setup();
-
- // Remove the old item.
- $this->remove($id, false);
-
- // Get the item.
- $item = $this->getItem($id);
-
- // Index the item.
- $this->index($item);
-
- Taxonomy::removeOrphanNodes();
- }
-
- /**
- * Method to remove an item from the index.
- *
- * @param string $id The ID of the item to remove.
- * @param bool $removeTaxonomies Remove empty taxonomies
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function remove($id, $removeTaxonomies = true)
- {
- // Get the item's URL
- $url = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
-
- // Get the link ids for the content items.
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('link_id'))
- ->from($this->db->quoteName('#__finder_links'))
- ->where($this->db->quoteName('url') . ' = ' . $url);
- $this->db->setQuery($query);
- $items = $this->db->loadColumn();
-
- // Check the items.
- if (empty($items))
- {
- Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', array($id));
-
- return true;
- }
-
- // Remove the items.
- foreach ($items as $item)
- {
- $this->indexer->remove($item, $removeTaxonomies);
- }
-
- return true;
- }
-
- /**
- * Method to setup the adapter before indexing.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- abstract protected function setup();
-
- /**
- * Method to update index data on category access level changes
- *
- * @param Table $row A Table object
- *
- * @return void
- *
- * @since 2.5
- */
- protected function categoryAccessChange($row)
- {
- $query = clone $this->getStateQuery();
- $query->where('c.id = ' . (int) $row->id);
-
- // Get the access level.
- $this->db->setQuery($query);
- $items = $this->db->loadObjectList();
-
- // Adjust the access level for each item within the category.
- foreach ($items as $item)
- {
- // Set the access level.
- $temp = max($item->access, $row->access);
-
- // Update the item.
- $this->change((int) $item->id, 'access', $temp);
- }
- }
-
- /**
- * Method to update index data on category access level changes
- *
- * @param array $pks A list of primary key ids of the content that has changed state.
- * @param integer $value The value of the state that the content has been changed to.
- *
- * @return void
- *
- * @since 2.5
- */
- protected function categoryStateChange($pks, $value)
- {
- /*
- * The item's published state is tied to the category
- * published state so we need to look up all published states
- * before we change anything.
- */
- foreach ($pks as $pk)
- {
- $query = clone $this->getStateQuery();
- $query->where('c.id = ' . (int) $pk);
-
- // Get the published states.
- $this->db->setQuery($query);
- $items = $this->db->loadObjectList();
-
- // Adjust the state for each item within the category.
- foreach ($items as $item)
- {
- // Translate the state.
- $temp = $this->translateState($item->state, $value);
-
- // Update the item.
- $this->change($item->id, 'state', $temp);
- }
- }
- }
-
- /**
- * Method to check the existing access level for categories
- *
- * @param Table $row A Table object
- *
- * @return void
- *
- * @since 2.5
- */
- protected function checkCategoryAccess($row)
- {
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('access'))
- ->from($this->db->quoteName('#__categories'))
- ->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
- $this->db->setQuery($query);
-
- // Store the access level to determine if it changes
- $this->old_cataccess = $this->db->loadResult();
- }
-
- /**
- * Method to check the existing access level for items
- *
- * @param Table $row A Table object
- *
- * @return void
- *
- * @since 2.5
- */
- protected function checkItemAccess($row)
- {
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('access'))
- ->from($this->db->quoteName($this->table))
- ->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
- $this->db->setQuery($query);
-
- // Store the access level to determine if it changes
- $this->old_access = $this->db->loadResult();
- }
-
- /**
- * Method to get the number of content items available to index.
- *
- * @return integer The number of content items available to index.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function getContentCount()
- {
- $return = 0;
-
- // Get the list query.
- $query = $this->getListQuery();
-
- // Check if the query is valid.
- if (empty($query))
- {
- return $return;
- }
-
- // Tweak the SQL query to make the total lookup faster.
- if ($query instanceof QueryInterface)
- {
- $query = clone $query;
- $query->clear('select')
- ->select('COUNT(*)')
- ->clear('order');
- }
-
- // Get the total number of content items to index.
- $this->db->setQuery($query);
-
- return (int) $this->db->loadResult();
- }
-
- /**
- * Method to get a content item to index.
- *
- * @param integer $id The id of the content item.
- *
- * @return Result A Result object.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function getItem($id)
- {
- // Get the list query and add the extra WHERE clause.
- $query = $this->getListQuery();
- $query->where('a.id = ' . (int) $id);
-
- // Get the item to index.
- $this->db->setQuery($query);
- $item = $this->db->loadAssoc();
-
- // Convert the item to a result object.
- $item = ArrayHelper::toObject((array) $item, Result::class);
-
- // Set the item type.
- $item->type_id = $this->type_id;
-
- // Set the item layout.
- $item->layout = $this->layout;
-
- return $item;
- }
-
- /**
- * Method to get a list of content items to index.
- *
- * @param integer $offset The list offset.
- * @param integer $limit The list limit.
- * @param QueryInterface $query A QueryInterface object. [optional]
- *
- * @return Result[] An array of Result objects.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function getItems($offset, $limit, $query = null)
- {
- // Get the content items to index.
- $this->db->setQuery($this->getListQuery($query)->setLimit($limit, $offset));
- $items = $this->db->loadAssocList();
-
- foreach ($items as &$item)
- {
- $item = ArrayHelper::toObject($item, Result::class);
-
- // Set the item type.
- $item->type_id = $this->type_id;
-
- // Set the mime type.
- $item->mime = $this->mime;
-
- // Set the item layout.
- $item->layout = $this->layout;
- }
-
- return $items;
- }
-
- /**
- * Method to get the SQL query used to retrieve the list of content items.
- *
- * @param mixed $query A QueryInterface object. [optional]
- *
- * @return QueryInterface A database object.
- *
- * @since 2.5
- */
- protected function getListQuery($query = null)
- {
- // Check if we can use the supplied SQL query.
- return $query instanceof QueryInterface ? $query : $this->db->getQuery(true);
- }
-
- /**
- * Method to get the plugin type
- *
- * @param integer $id The plugin ID
- *
- * @return string The plugin type
- *
- * @since 2.5
- */
- protected function getPluginType($id)
- {
- // Prepare the query
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('element'))
- ->from($this->db->quoteName('#__extensions'))
- ->where($this->db->quoteName('extension_id') . ' = ' . (int) $id);
- $this->db->setQuery($query);
-
- return $this->db->loadResult();
- }
-
- /**
- * Method to get a SQL query to load the published and access states for
- * an article and category.
- *
- * @return QueryInterface A database object.
- *
- * @since 2.5
- */
- protected function getStateQuery()
- {
- $query = $this->db->getQuery(true);
-
- // Item ID
- $query->select('a.id');
-
- // Item and category published state
- $query->select('a.' . $this->state_field . ' AS state, c.published AS cat_state');
-
- // Item and category access levels
- $query->select('a.access, c.access AS cat_access')
- ->from($this->table . ' AS a')
- ->join('LEFT', '#__categories AS c ON c.id = a.catid');
-
- return $query;
- }
-
- /**
- * Method to get the query clause for getting items to update by time.
- *
- * @param string $time The modified timestamp.
- *
- * @return QueryInterface A database object.
- *
- * @since 2.5
- */
- protected function getUpdateQueryByTime($time)
- {
- // Build an SQL query based on the modified time.
- $query = $this->db->getQuery(true)
- ->where('a.modified >= ' . $this->db->quote($time));
-
- return $query;
- }
-
- /**
- * Method to get the query clause for getting items to update by id.
- *
- * @param array $ids The ids to load.
- *
- * @return QueryInterface A database object.
- *
- * @since 2.5
- */
- protected function getUpdateQueryByIds($ids)
- {
- // Build an SQL query based on the item ids.
- $query = $this->db->getQuery(true)
- ->where('a.id IN(' . implode(',', $ids) . ')');
-
- return $query;
- }
-
- /**
- * Method to get the type id for the adapter content.
- *
- * @return integer The numeric type id for the content.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function getTypeId()
- {
- // Get the type id from the database.
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('id'))
- ->from($this->db->quoteName('#__finder_types'))
- ->where($this->db->quoteName('title') . ' = ' . $this->db->quote($this->type_title));
- $this->db->setQuery($query);
-
- return (int) $this->db->loadResult();
- }
-
- /**
- * Method to get the URL for the item. The URL is how we look up the link
- * in the Finder index.
- *
- * @param integer $id The id of the item.
- * @param string $extension The extension the category is in.
- * @param string $view The view for the URL.
- *
- * @return string The URL of the item.
- *
- * @since 2.5
- */
- protected function getUrl($id, $extension, $view)
- {
- return 'index.php?option=' . $extension . '&view=' . $view . '&id=' . $id;
- }
-
- /**
- * Method to get the page title of any menu item that is linked to the
- * content item, if it exists and is set.
- *
- * @param string $url The URL of the item.
- *
- * @return mixed The title on success, null if not found.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function getItemMenuTitle($url)
- {
- $return = null;
-
- // Set variables
- $user = Factory::getUser();
- $groups = implode(',', $user->getAuthorisedViewLevels());
-
- // Build a query to get the menu params.
- $query = $this->db->getQuery(true)
- ->select($this->db->quoteName('params'))
- ->from($this->db->quoteName('#__menu'))
- ->where($this->db->quoteName('link') . ' = ' . $this->db->quote($url))
- ->where($this->db->quoteName('published') . ' = 1')
- ->where($this->db->quoteName('access') . ' IN (' . $groups . ')');
-
- // Get the menu params from the database.
- $this->db->setQuery($query);
- $params = $this->db->loadResult();
-
- // Check the results.
- if (empty($params))
- {
- return $return;
- }
-
- // Instantiate the params.
- $params = json_decode($params);
-
- // Get the page title if it is set.
- if (isset($params->page_title) && $params->page_title)
- {
- $return = $params->page_title;
- }
-
- return $return;
- }
-
- /**
- * Method to update index data on access level changes
- *
- * @param Table $row A Table object
- *
- * @return void
- *
- * @since 2.5
- */
- protected function itemAccessChange($row)
- {
- $query = clone $this->getStateQuery();
- $query->where('a.id = ' . (int) $row->id);
-
- // Get the access level.
- $this->db->setQuery($query);
- $item = $this->db->loadObject();
-
- // Set the access level.
- $temp = max($row->access, $item->cat_access);
-
- // Update the item.
- $this->change((int) $row->id, 'access', $temp);
- }
-
- /**
- * Method to update index data on published state changes
- *
- * @param array $pks A list of primary key ids of the content that has changed state.
- * @param integer $value The value of the state that the content has been changed to.
- *
- * @return void
- *
- * @since 2.5
- */
- protected function itemStateChange($pks, $value)
- {
- /*
- * The item's published state is tied to the category
- * published state so we need to look up all published states
- * before we change anything.
- */
- foreach ($pks as $pk)
- {
- $query = clone $this->getStateQuery();
- $query->where('a.id = ' . (int) $pk);
-
- // Get the published states.
- $this->db->setQuery($query);
- $item = $this->db->loadObject();
-
- // Translate the state.
- $temp = $this->translateState($value, $item->cat_state);
-
- // Update the item.
- $this->change($pk, 'state', $temp);
- }
- }
-
- /**
- * Method to update index data when a plugin is disabled
- *
- * @param array $pks A list of primary key ids of the content that has changed state.
- *
- * @return void
- *
- * @since 2.5
- */
- protected function pluginDisable($pks)
- {
- // Since multiple plugins may be disabled at a time, we need to check first
- // that we're handling the appropriate one for the context
- foreach ($pks as $pk)
- {
- if ($this->getPluginType($pk) == strtolower($this->context))
- {
- // Get all of the items to unindex them
- $query = clone $this->getStateQuery();
- $this->db->setQuery($query);
- $items = $this->db->loadColumn();
-
- // Remove each item
- foreach ($items as $item)
- {
- $this->remove($item);
- }
- }
- }
- }
-
- /**
- * Method to translate the native content states into states that the
- * indexer can use.
- *
- * @param integer $item The item state.
- * @param integer $category The category state. [optional]
- *
- * @return integer The translated indexer state.
- *
- * @since 2.5
- */
- protected function translateState($item, $category = null)
- {
- // If category is present, factor in its states as well
- if ($category !== null && $category == 0)
- {
- $item = 0;
- }
-
- // Translate the state
- switch ($item)
- {
- // Published and archived items only should return a published state
- case 1:
- case 2:
- return 1;
-
- // All other states should return an unpublished state
- default:
- return 0;
- }
- }
+ /**
+ * The context is somewhat arbitrary but it must be unique or there will be
+ * conflicts when managing plugin/indexer state. A good best practice is to
+ * use the plugin name suffix as the context. For example, if the plugin is
+ * named 'plgFinderContent', the context could be 'Content'.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $context;
+
+ /**
+ * The extension name.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $extension;
+
+ /**
+ * The sublayout to use when rendering the results.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $layout;
+
+ /**
+ * The mime type of the content the adapter indexes.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $mime;
+
+ /**
+ * The access level of an item before save.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ protected $old_access;
+
+ /**
+ * The access level of a category before save.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ protected $old_cataccess;
+
+ /**
+ * The type of content the adapter indexes.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $type_title;
+
+ /**
+ * The type id of the content.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ protected $type_id;
+
+ /**
+ * The database object.
+ *
+ * @var DatabaseInterface
+ * @since 2.5
+ */
+ protected $db;
+
+ /**
+ * The table name.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $table;
+
+ /**
+ * The indexer object.
+ *
+ * @var Indexer
+ * @since 3.0
+ */
+ protected $indexer;
+
+ /**
+ * The field the published state is stored in.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $state_field = 'state';
+
+ /**
+ * Method to instantiate the indexer adapter.
+ *
+ * @param object $subject The object to observe.
+ * @param array $config An array that holds the plugin configuration.
+ *
+ * @since 2.5
+ */
+ public function __construct(&$subject, $config)
+ {
+ // Call the parent constructor.
+ parent::__construct($subject, $config);
+
+ // Get the type id.
+ $this->type_id = $this->getTypeId();
+
+ // Add the content type if it doesn't exist and is set.
+ if (empty($this->type_id) && !empty($this->type_title)) {
+ $this->type_id = Helper::addContentType($this->type_title, $this->mime);
+ }
+
+ // Check for a layout override.
+ if ($this->params->get('layout')) {
+ $this->layout = $this->params->get('layout');
+ }
+
+ // Get the indexer object
+ $this->indexer = new Indexer($this->db);
+ }
+
+ /**
+ * Method to get the adapter state and push it into the indexer.
+ *
+ * @return void
+ *
+ * @since 2.5
+ * @throws Exception on error.
+ */
+ public function onStartIndex()
+ {
+ // Get the indexer state.
+ $iState = Indexer::getState();
+
+ // Get the number of content items.
+ $total = (int) $this->getContentCount();
+
+ // Add the content count to the total number of items.
+ $iState->totalItems += $total;
+
+ // Populate the indexer state information for the adapter.
+ $iState->pluginState[$this->context]['total'] = $total;
+ $iState->pluginState[$this->context]['offset'] = 0;
+
+ // Set the indexer state.
+ Indexer::setState($iState);
+ }
+
+ /**
+ * Method to prepare for the indexer to be run. This method will often
+ * be used to include dependencies and things of that nature.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on error.
+ */
+ public function onBeforeIndex()
+ {
+ // Get the indexer and adapter state.
+ $iState = Indexer::getState();
+ $aState = $iState->pluginState[$this->context];
+
+ // Check the progress of the indexer and the adapter.
+ if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
+ return true;
+ }
+
+ // Run the setup method.
+ return $this->setup();
+ }
+
+ /**
+ * Method to index a batch of content items. This method can be called by
+ * the indexer many times throughout the indexing process depending on how
+ * much content is available for indexing. It is important to track the
+ * progress correctly so we can display it to the user.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on error.
+ */
+ public function onBuildIndex()
+ {
+ // Get the indexer and adapter state.
+ $iState = Indexer::getState();
+ $aState = $iState->pluginState[$this->context];
+
+ // Check the progress of the indexer and the adapter.
+ if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
+ return true;
+ }
+
+ // Get the batch offset and size.
+ $offset = (int) $aState['offset'];
+ $limit = (int) ($iState->batchSize - $iState->batchOffset);
+
+ // Get the content items to index.
+ $items = $this->getItems($offset, $limit);
+
+ // Iterate through the items and index them.
+ for ($i = 0, $n = count($items); $i < $n; $i++) {
+ // Index the item.
+ $this->index($items[$i]);
+
+ // Adjust the offsets.
+ $offset++;
+ $iState->batchOffset++;
+ $iState->totalItems--;
+ }
+
+ // Update the indexer state.
+ $aState['offset'] = $offset;
+ $iState->pluginState[$this->context] = $aState;
+ Indexer::setState($iState);
+
+ return true;
+ }
+
+ /**
+ * Method to remove outdated index entries
+ *
+ * @return integer
+ *
+ * @since 4.2.0
+ */
+ public function onFinderGarbageCollection()
+ {
+ $db = $this->db;
+ $type_id = $this->getTypeId();
+
+ $query = $db->getQuery(true);
+ $subquery = $db->getQuery(true);
+ $subquery->select('CONCAT(' . $db->quote($this->getUrl('', $this->extension, $this->layout)) . ', id)')
+ ->from($db->quoteName($this->table));
+ $query->select($db->quoteName('l.link_id'))
+ ->from($db->quoteName('#__finder_links', 'l'))
+ ->where($db->quoteName('l.type_id') . ' = ' . $type_id)
+ ->where($db->quoteName('l.url') . ' LIKE ' . $db->quote($this->getUrl('%', $this->extension, $this->layout)))
+ ->where($db->quoteName('l.url') . ' NOT IN (' . $subquery . ')');
+ $db->setQuery($query);
+ $items = $db->loadColumn();
+
+ foreach ($items as $item) {
+ $this->indexer->remove($item);
+ }
+
+ return count($items);
+ }
+
+ /**
+ * Method to change the value of a content item's property in the links
+ * table. This is used to synchronize published and access states that
+ * are changed when not editing an item directly.
+ *
+ * @param string $id The ID of the item to change.
+ * @param string $property The property that is being changed.
+ * @param integer $value The new value of that property.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function change($id, $property, $value)
+ {
+ // Check for a property we know how to handle.
+ if ($property !== 'state' && $property !== 'access') {
+ return true;
+ }
+
+ // Get the URL for the content id.
+ $item = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
+
+ // Update the content items.
+ $query = $this->db->getQuery(true)
+ ->update($this->db->quoteName('#__finder_links'))
+ ->set($this->db->quoteName($property) . ' = ' . (int) $value)
+ ->where($this->db->quoteName('url') . ' = ' . $item);
+ $this->db->setQuery($query);
+ $this->db->execute();
+
+ return true;
+ }
+
+ /**
+ * Method to index an item.
+ *
+ * @param Result $item The item to index as a Result object.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ abstract protected function index(Result $item);
+
+ /**
+ * Method to reindex an item.
+ *
+ * @param integer $id The ID of the item to reindex.
+ *
+ * @return void
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function reindex($id)
+ {
+ // Run the setup method.
+ $this->setup();
+
+ // Remove the old item.
+ $this->remove($id, false);
+
+ // Get the item.
+ $item = $this->getItem($id);
+
+ // Index the item.
+ $this->index($item);
+
+ Taxonomy::removeOrphanNodes();
+ }
+
+ /**
+ * Method to remove an item from the index.
+ *
+ * @param string $id The ID of the item to remove.
+ * @param bool $removeTaxonomies Remove empty taxonomies
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function remove($id, $removeTaxonomies = true)
+ {
+ // Get the item's URL
+ $url = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
+
+ // Get the link ids for the content items.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('link_id'))
+ ->from($this->db->quoteName('#__finder_links'))
+ ->where($this->db->quoteName('url') . ' = ' . $url);
+ $this->db->setQuery($query);
+ $items = $this->db->loadColumn();
+
+ // Check the items.
+ if (empty($items)) {
+ Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', array($id));
+
+ return true;
+ }
+
+ // Remove the items.
+ foreach ($items as $item) {
+ $this->indexer->remove($item, $removeTaxonomies);
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to setup the adapter before indexing.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ abstract protected function setup();
+
+ /**
+ * Method to update index data on category access level changes
+ *
+ * @param Table $row A Table object
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function categoryAccessChange($row)
+ {
+ $query = clone $this->getStateQuery();
+ $query->where('c.id = ' . (int) $row->id);
+
+ // Get the access level.
+ $this->db->setQuery($query);
+ $items = $this->db->loadObjectList();
+
+ // Adjust the access level for each item within the category.
+ foreach ($items as $item) {
+ // Set the access level.
+ $temp = max($item->access, $row->access);
+
+ // Update the item.
+ $this->change((int) $item->id, 'access', $temp);
+ }
+ }
+
+ /**
+ * Method to update index data on category access level changes
+ *
+ * @param array $pks A list of primary key ids of the content that has changed state.
+ * @param integer $value The value of the state that the content has been changed to.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function categoryStateChange($pks, $value)
+ {
+ /*
+ * The item's published state is tied to the category
+ * published state so we need to look up all published states
+ * before we change anything.
+ */
+ foreach ($pks as $pk) {
+ $query = clone $this->getStateQuery();
+ $query->where('c.id = ' . (int) $pk);
+
+ // Get the published states.
+ $this->db->setQuery($query);
+ $items = $this->db->loadObjectList();
+
+ // Adjust the state for each item within the category.
+ foreach ($items as $item) {
+ // Translate the state.
+ $temp = $this->translateState($item->state, $value);
+
+ // Update the item.
+ $this->change($item->id, 'state', $temp);
+ }
+ }
+ }
+
+ /**
+ * Method to check the existing access level for categories
+ *
+ * @param Table $row A Table object
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function checkCategoryAccess($row)
+ {
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('access'))
+ ->from($this->db->quoteName('#__categories'))
+ ->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
+ $this->db->setQuery($query);
+
+ // Store the access level to determine if it changes
+ $this->old_cataccess = $this->db->loadResult();
+ }
+
+ /**
+ * Method to check the existing access level for items
+ *
+ * @param Table $row A Table object
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function checkItemAccess($row)
+ {
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('access'))
+ ->from($this->db->quoteName($this->table))
+ ->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
+ $this->db->setQuery($query);
+
+ // Store the access level to determine if it changes
+ $this->old_access = $this->db->loadResult();
+ }
+
+ /**
+ * Method to get the number of content items available to index.
+ *
+ * @return integer The number of content items available to index.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function getContentCount()
+ {
+ $return = 0;
+
+ // Get the list query.
+ $query = $this->getListQuery();
+
+ // Check if the query is valid.
+ if (empty($query)) {
+ return $return;
+ }
+
+ // Tweak the SQL query to make the total lookup faster.
+ if ($query instanceof QueryInterface) {
+ $query = clone $query;
+ $query->clear('select')
+ ->select('COUNT(*)')
+ ->clear('order');
+ }
+
+ // Get the total number of content items to index.
+ $this->db->setQuery($query);
+
+ return (int) $this->db->loadResult();
+ }
+
+ /**
+ * Method to get a content item to index.
+ *
+ * @param integer $id The id of the content item.
+ *
+ * @return Result A Result object.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function getItem($id)
+ {
+ // Get the list query and add the extra WHERE clause.
+ $query = $this->getListQuery();
+ $query->where('a.id = ' . (int) $id);
+
+ // Get the item to index.
+ $this->db->setQuery($query);
+ $item = $this->db->loadAssoc();
+
+ // Convert the item to a result object.
+ $item = ArrayHelper::toObject((array) $item, Result::class);
+
+ // Set the item type.
+ $item->type_id = $this->type_id;
+
+ // Set the item layout.
+ $item->layout = $this->layout;
+
+ return $item;
+ }
+
+ /**
+ * Method to get a list of content items to index.
+ *
+ * @param integer $offset The list offset.
+ * @param integer $limit The list limit.
+ * @param QueryInterface $query A QueryInterface object. [optional]
+ *
+ * @return Result[] An array of Result objects.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function getItems($offset, $limit, $query = null)
+ {
+ // Get the content items to index.
+ $this->db->setQuery($this->getListQuery($query)->setLimit($limit, $offset));
+ $items = $this->db->loadAssocList();
+
+ foreach ($items as &$item) {
+ $item = ArrayHelper::toObject($item, Result::class);
+
+ // Set the item type.
+ $item->type_id = $this->type_id;
+
+ // Set the mime type.
+ $item->mime = $this->mime;
+
+ // Set the item layout.
+ $item->layout = $this->layout;
+ }
+
+ return $items;
+ }
+
+ /**
+ * Method to get the SQL query used to retrieve the list of content items.
+ *
+ * @param mixed $query A QueryInterface object. [optional]
+ *
+ * @return QueryInterface A database object.
+ *
+ * @since 2.5
+ */
+ protected function getListQuery($query = null)
+ {
+ // Check if we can use the supplied SQL query.
+ return $query instanceof QueryInterface ? $query : $this->db->getQuery(true);
+ }
+
+ /**
+ * Method to get the plugin type
+ *
+ * @param integer $id The plugin ID
+ *
+ * @return string The plugin type
+ *
+ * @since 2.5
+ */
+ protected function getPluginType($id)
+ {
+ // Prepare the query
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('element'))
+ ->from($this->db->quoteName('#__extensions'))
+ ->where($this->db->quoteName('extension_id') . ' = ' . (int) $id);
+ $this->db->setQuery($query);
+
+ return $this->db->loadResult();
+ }
+
+ /**
+ * Method to get a SQL query to load the published and access states for
+ * an article and category.
+ *
+ * @return QueryInterface A database object.
+ *
+ * @since 2.5
+ */
+ protected function getStateQuery()
+ {
+ $query = $this->db->getQuery(true);
+
+ // Item ID
+ $query->select('a.id');
+
+ // Item and category published state
+ $query->select('a.' . $this->state_field . ' AS state, c.published AS cat_state');
+
+ // Item and category access levels
+ $query->select('a.access, c.access AS cat_access')
+ ->from($this->table . ' AS a')
+ ->join('LEFT', '#__categories AS c ON c.id = a.catid');
+
+ return $query;
+ }
+
+ /**
+ * Method to get the query clause for getting items to update by time.
+ *
+ * @param string $time The modified timestamp.
+ *
+ * @return QueryInterface A database object.
+ *
+ * @since 2.5
+ */
+ protected function getUpdateQueryByTime($time)
+ {
+ // Build an SQL query based on the modified time.
+ $query = $this->db->getQuery(true)
+ ->where('a.modified >= ' . $this->db->quote($time));
+
+ return $query;
+ }
+
+ /**
+ * Method to get the query clause for getting items to update by id.
+ *
+ * @param array $ids The ids to load.
+ *
+ * @return QueryInterface A database object.
+ *
+ * @since 2.5
+ */
+ protected function getUpdateQueryByIds($ids)
+ {
+ // Build an SQL query based on the item ids.
+ $query = $this->db->getQuery(true)
+ ->where('a.id IN(' . implode(',', $ids) . ')');
+
+ return $query;
+ }
+
+ /**
+ * Method to get the type id for the adapter content.
+ *
+ * @return integer The numeric type id for the content.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function getTypeId()
+ {
+ // Get the type id from the database.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('id'))
+ ->from($this->db->quoteName('#__finder_types'))
+ ->where($this->db->quoteName('title') . ' = ' . $this->db->quote($this->type_title));
+ $this->db->setQuery($query);
+
+ return (int) $this->db->loadResult();
+ }
+
+ /**
+ * Method to get the URL for the item. The URL is how we look up the link
+ * in the Finder index.
+ *
+ * @param integer $id The id of the item.
+ * @param string $extension The extension the category is in.
+ * @param string $view The view for the URL.
+ *
+ * @return string The URL of the item.
+ *
+ * @since 2.5
+ */
+ protected function getUrl($id, $extension, $view)
+ {
+ return 'index.php?option=' . $extension . '&view=' . $view . '&id=' . $id;
+ }
+
+ /**
+ * Method to get the page title of any menu item that is linked to the
+ * content item, if it exists and is set.
+ *
+ * @param string $url The URL of the item.
+ *
+ * @return mixed The title on success, null if not found.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function getItemMenuTitle($url)
+ {
+ $return = null;
+
+ // Set variables
+ $user = Factory::getUser();
+ $groups = implode(',', $user->getAuthorisedViewLevels());
+
+ // Build a query to get the menu params.
+ $query = $this->db->getQuery(true)
+ ->select($this->db->quoteName('params'))
+ ->from($this->db->quoteName('#__menu'))
+ ->where($this->db->quoteName('link') . ' = ' . $this->db->quote($url))
+ ->where($this->db->quoteName('published') . ' = 1')
+ ->where($this->db->quoteName('access') . ' IN (' . $groups . ')');
+
+ // Get the menu params from the database.
+ $this->db->setQuery($query);
+ $params = $this->db->loadResult();
+
+ // Check the results.
+ if (empty($params)) {
+ return $return;
+ }
+
+ // Instantiate the params.
+ $params = json_decode($params);
+
+ // Get the page title if it is set.
+ if (isset($params->page_title) && $params->page_title) {
+ $return = $params->page_title;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to update index data on access level changes
+ *
+ * @param Table $row A Table object
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function itemAccessChange($row)
+ {
+ $query = clone $this->getStateQuery();
+ $query->where('a.id = ' . (int) $row->id);
+
+ // Get the access level.
+ $this->db->setQuery($query);
+ $item = $this->db->loadObject();
+
+ // Set the access level.
+ $temp = max($row->access, $item->cat_access);
+
+ // Update the item.
+ $this->change((int) $row->id, 'access', $temp);
+ }
+
+ /**
+ * Method to update index data on published state changes
+ *
+ * @param array $pks A list of primary key ids of the content that has changed state.
+ * @param integer $value The value of the state that the content has been changed to.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function itemStateChange($pks, $value)
+ {
+ /*
+ * The item's published state is tied to the category
+ * published state so we need to look up all published states
+ * before we change anything.
+ */
+ foreach ($pks as $pk) {
+ $query = clone $this->getStateQuery();
+ $query->where('a.id = ' . (int) $pk);
+
+ // Get the published states.
+ $this->db->setQuery($query);
+ $item = $this->db->loadObject();
+
+ // Translate the state.
+ $temp = $this->translateState($value, $item->cat_state);
+
+ // Update the item.
+ $this->change($pk, 'state', $temp);
+ }
+ }
+
+ /**
+ * Method to update index data when a plugin is disabled
+ *
+ * @param array $pks A list of primary key ids of the content that has changed state.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function pluginDisable($pks)
+ {
+ // Since multiple plugins may be disabled at a time, we need to check first
+ // that we're handling the appropriate one for the context
+ foreach ($pks as $pk) {
+ if ($this->getPluginType($pk) == strtolower($this->context)) {
+ // Get all of the items to unindex them
+ $query = clone $this->getStateQuery();
+ $this->db->setQuery($query);
+ $items = $this->db->loadColumn();
+
+ // Remove each item
+ foreach ($items as $item) {
+ $this->remove($item);
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to translate the native content states into states that the
+ * indexer can use.
+ *
+ * @param integer $item The item state.
+ * @param integer $category The category state. [optional]
+ *
+ * @return integer The translated indexer state.
+ *
+ * @since 2.5
+ */
+ protected function translateState($item, $category = null)
+ {
+ // If category is present, factor in its states as well
+ if ($category !== null && $category == 0) {
+ $item = 0;
+ }
+
+ // Translate the state
+ switch ($item) {
+ // Published and archived items only should return a published state
+ case 1:
+ case 2:
+ return 1;
+
+ // All other states should return an unpublished state
+ default:
+ return 0;
+ }
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Helper.php b/administrator/components/com_finder/src/Indexer/Helper.php
index 12042f11cabeb..ad79234248ad4 100644
--- a/administrator/components/com_finder/src/Indexer/Helper.php
+++ b/administrator/components/com_finder/src/Indexer/Helper.php
@@ -1,4 +1,5 @@
parse($input);
- }
-
- /**
- * Method to tokenize a text string.
- *
- * @param string $input The input to tokenize.
- * @param string $lang The language of the input.
- * @param boolean $phrase Flag to indicate whether input could be a phrase. [optional]
- *
- * @return Token[] An array of Token objects.
- *
- * @since 2.5
- */
- public static function tokenize($input, $lang, $phrase = false)
- {
- static $cache = [], $tuplecount;
- static $multilingual;
- static $defaultLanguage;
-
- if (!$tuplecount)
- {
- $params = ComponentHelper::getParams('com_finder');
- $tuplecount = $params->get('tuplecount', 1);
- }
-
- if (is_null($multilingual))
- {
- $multilingual = Multilanguage::isEnabled();
- $config = ComponentHelper::getParams('com_finder');
-
- if ($config->get('language_default', '') == '')
- {
- $defaultLang = '*';
- }
- elseif ($config->get('language_default', '') == '-1')
- {
- $defaultLang = self::getDefaultLanguage();
- }
- else
- {
- $defaultLang = $config->get('language_default');
- }
-
- /*
- * The default language always has the language code '*'.
- * In order to not overwrite the language code of the language
- * object that we are using, we are cloning it here.
- */
- $obj = Language::getInstance($defaultLang);
- $defaultLanguage = clone $obj;
- $defaultLanguage->language = '*';
- }
-
- if (!$multilingual || $lang == '*')
- {
- $language = $defaultLanguage;
- }
- else
- {
- $language = Language::getInstance($lang);
- }
-
- if (!isset($cache[$lang]))
- {
- $cache[$lang] = [];
- }
-
- $tokens = array();
- $terms = $language->tokenise($input);
-
- // @todo: array_filter removes any number 0's from the terms. Not sure this is entirely intended
- $terms = array_filter($terms);
- $terms = array_values($terms);
-
- /*
- * If we have to handle the input as a phrase, that means we don't
- * tokenize the individual terms and we do not create the two and three
- * term combinations. The phrase must contain more than one word!
- */
- if ($phrase === true && count($terms) > 1)
- {
- // Create tokens from the phrase.
- $tokens[] = new Token($terms, $language->language, $language->spacer);
- }
- else
- {
- // Create tokens from the terms.
- for ($i = 0, $n = count($terms); $i < $n; $i++)
- {
- if (isset($cache[$lang][$terms[$i]]))
- {
- $tokens[] = $cache[$lang][$terms[$i]];
- }
- else
- {
- $token = new Token($terms[$i], $language->language);
- $tokens[] = $token;
- $cache[$lang][$terms[$i]] = $token;
- }
- }
-
- // Create multi-word phrase tokens from the individual words.
- if ($tuplecount > 1)
- {
- for ($i = 0, $n = count($tokens); $i < $n; $i++)
- {
- $temp = array($tokens[$i]->term);
-
- // Create tokens for 2 to $tuplecount length phrases
- for ($j = 1; $j < $tuplecount; $j++)
- {
- if ($i + $j >= $n || !isset($tokens[$i + $j]))
- {
- break;
- }
-
- $temp[] = $tokens[$i + $j]->term;
- $key = implode('::', $temp);
-
- if (isset($cache[$lang][$key]))
- {
- $tokens[] = $cache[$lang][$key];
- }
- else
- {
- $token = new Token($temp, $language->language, $language->spacer);
- $token->derived = true;
- $tokens[] = $token;
- $cache[$lang][$key] = $token;
- }
- }
- }
- }
- }
-
- // Prevent the cache to fill up the memory
- while (count($cache[$lang]) > 1024)
- {
- /**
- * We want to cache the most common words/tokens. At the same time
- * we don't want to cache too much. The most common words will also
- * be early in the text, so we are dropping all terms/tokens which
- * have been cached later.
- */
- array_pop($cache[$lang]);
- }
-
- return $tokens;
- }
-
- /**
- * Method to get the base word of a token.
- *
- * @param string $token The token to stem.
- * @param string $lang The language of the token.
- *
- * @return string The root token.
- *
- * @since 2.5
- */
- public static function stem($token, $lang)
- {
- static $multilingual;
- static $defaultStemmer;
-
- if (is_null($multilingual))
- {
- $multilingual = Multilanguage::isEnabled();
- $config = ComponentHelper::getParams('com_finder');
-
- if ($config->get('language_default', '') == '')
- {
- $defaultStemmer = Language::getInstance('*');
- }
- elseif ($config->get('language_default', '') == '-1')
- {
- $defaultStemmer = Language::getInstance(self::getDefaultLanguage());
- }
- else
- {
- $defaultStemmer = Language::getInstance($config->get('language_default'));
- }
- }
-
- if (!$multilingual || $lang == '*')
- {
- $language = $defaultStemmer;
- }
- else
- {
- $language = Language::getInstance($lang);
- }
-
- return $language->stem($token);
- }
-
- /**
- * Method to add a content type to the database.
- *
- * @param string $title The type of content. For example: PDF
- * @param string $mime The mime type of the content. For example: PDF [optional]
- *
- * @return integer The id of the content type.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- public static function addContentType($title, $mime = null)
- {
- static $types;
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
-
- // Check if the types are loaded.
- if (empty($types))
- {
- // Build the query to get the types.
- $query->select('*')
- ->from($db->quoteName('#__finder_types'));
-
- // Get the types.
- $db->setQuery($query);
- $types = $db->loadObjectList('title');
- }
-
- // Check if the type already exists.
- if (isset($types[$title]))
- {
- return (int) $types[$title]->id;
- }
-
- // Add the type.
- $query->clear()
- ->insert($db->quoteName('#__finder_types'))
- ->columns(array($db->quoteName('title'), $db->quoteName('mime')))
- ->values($db->quote($title) . ', ' . $db->quote($mime));
- $db->setQuery($query);
- $db->execute();
-
- // Return the new id.
- return (int) $db->insertid();
- }
-
- /**
- * Method to check if a token is common in a language.
- *
- * @param string $token The token to test.
- * @param string $lang The language to reference.
- *
- * @return boolean True if common, false otherwise.
- *
- * @since 2.5
- */
- public static function isCommon($token, $lang)
- {
- static $data, $default, $multilingual;
-
- if (is_null($multilingual))
- {
- $multilingual = Multilanguage::isEnabled();
- $config = ComponentHelper::getParams('com_finder');
-
- if ($config->get('language_default', '') == '')
- {
- $default = '*';
- }
- elseif ($config->get('language_default', '') == '-1')
- {
- $default = self::getPrimaryLanguage(self::getDefaultLanguage());
- }
- else
- {
- $default = self::getPrimaryLanguage($config->get('language_default'));
- }
- }
-
- if (!$multilingual || $lang == '*')
- {
- $lang = $default;
- }
-
- // Load the common tokens for the language if necessary.
- if (!isset($data[$lang]))
- {
- $data[$lang] = self::getCommonWords($lang);
- }
-
- // Check if the token is in the common array.
- return in_array($token, $data[$lang], true);
- }
-
- /**
- * Method to get an array of common terms for a language.
- *
- * @param string $lang The language to use.
- *
- * @return array Array of common terms.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- public static function getCommonWords($lang)
- {
- $db = Factory::getDbo();
-
- // Create the query to load all the common terms for the language.
- $query = $db->getQuery(true)
- ->select($db->quoteName('term'))
- ->from($db->quoteName('#__finder_terms_common'))
- ->where($db->quoteName('language') . ' = ' . $db->quote($lang));
-
- // Load all of the common terms for the language.
- $db->setQuery($query);
-
- return $db->loadColumn();
- }
-
- /**
- * Method to get the default language for the site.
- *
- * @return string The default language string.
- *
- * @since 2.5
- */
- public static function getDefaultLanguage()
- {
- static $lang;
-
- // We need to go to com_languages to get the site default language, it's the best we can guess.
- if (empty($lang))
- {
- $lang = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
- }
-
- return $lang;
- }
-
- /**
- * Method to parse a language/locale key and return a simple language string.
- *
- * @param string $lang The language/locale key. For example: en-GB
- *
- * @return string The simple language string. For example: en
- *
- * @since 2.5
- */
- public static function getPrimaryLanguage($lang)
- {
- static $data;
-
- // Only parse the identifier if necessary.
- if (!isset($data[$lang]))
- {
- if (is_callable(array('Locale', 'getPrimaryLanguage')))
- {
- // Get the language key using the Locale package.
- $data[$lang] = \Locale::getPrimaryLanguage($lang);
- }
- else
- {
- // Get the language key using string position.
- $data[$lang] = StringHelper::substr($lang, 0, StringHelper::strpos($lang, '-'));
- }
- }
-
- return $data[$lang];
- }
-
- /**
- * Method to get extra data for a content before being indexed. This is how
- * we add Comments, Tags, Labels, etc. that should be available to Finder.
- *
- * @param Result $item The item to index as a Result object.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- public static function getContentExtras(Result $item)
- {
- // Load the finder plugin group.
- PluginHelper::importPlugin('finder');
-
- Factory::getApplication()->triggerEvent('onPrepareFinderContent', array(&$item));
-
- return true;
- }
-
- /**
- * Add custom fields for the item to the Result object
- *
- * @param Result $item Result object to add the custom fields to
- * @param string $context Context of the item in the custom fields
- *
- * @return void
- *
- * @since 4.2.0
- */
- public static function addCustomFields(Result $item, $context)
- {
- $obj = new \stdClass;
- $obj->id = $item->id;
-
- $fields = FieldsHelper::getFields($context, $obj, true);
-
- foreach ($fields as $field)
- {
- $searchindex = $field->params->get('searchindex', 0);
-
- // We want to add this field to the search index
- if ($searchindex == 1 || $searchindex == 3)
- {
- $name = 'jsfield_' . $field->name;
- $item->$name = $field->value;
- $item->addInstruction(Indexer::META_CONTEXT, $name);
- }
-
- // We want to add this field as a taxonomy
- if (($searchindex == 2 || $searchindex == 3) && $field->value)
- {
- $item->addTaxonomy($field->title, $field->value, $field->state, $field->access, $field->language);
- }
- }
- }
-
- /**
- * Method to process content text using the onContentPrepare event trigger.
- *
- * @param string $text The content to process.
- * @param Registry $params The parameters object. [optional]
- * @param Result $item The item which get prepared. [optional]
- *
- * @return string The processed content.
- *
- * @since 2.5
- */
- public static function prepareContent($text, $params = null, Result $item = null)
- {
- static $loaded;
-
- // Load the content plugins if necessary.
- if (empty($loaded))
- {
- PluginHelper::importPlugin('content');
- $loaded = true;
- }
-
- // Instantiate the parameter object if necessary.
- if (!($params instanceof Registry))
- {
- $registry = new Registry($params);
- $params = $registry;
- }
-
- // Create a mock content object.
- $content = Table::getInstance('Content');
- $content->text = $text;
-
- if ($item)
- {
- $content->bind((array) $item);
- $content->bind($item->getElements());
- }
-
- if ($item && !empty($item->context))
- {
- $content->context = $item->context;
- }
-
- // Fire the onContentPrepare event.
- Factory::getApplication()->triggerEvent('onContentPrepare', array('com_finder.indexer', &$content, &$params, 0));
-
- return $content->text;
- }
+ /**
+ * Method to parse input into plain text.
+ *
+ * @param string $input The raw input.
+ * @param string $format The format of the input. [optional]
+ *
+ * @return string The parsed input.
+ *
+ * @since 2.5
+ * @throws Exception on invalid parser.
+ */
+ public static function parse($input, $format = 'html')
+ {
+ // Get a parser for the specified format and parse the input.
+ return Parser::getInstance($format)->parse($input);
+ }
+
+ /**
+ * Method to tokenize a text string.
+ *
+ * @param string $input The input to tokenize.
+ * @param string $lang The language of the input.
+ * @param boolean $phrase Flag to indicate whether input could be a phrase. [optional]
+ *
+ * @return Token[] An array of Token objects.
+ *
+ * @since 2.5
+ */
+ public static function tokenize($input, $lang, $phrase = false)
+ {
+ static $cache = [], $tuplecount;
+ static $multilingual;
+ static $defaultLanguage;
+
+ if (!$tuplecount) {
+ $params = ComponentHelper::getParams('com_finder');
+ $tuplecount = $params->get('tuplecount', 1);
+ }
+
+ if (is_null($multilingual)) {
+ $multilingual = Multilanguage::isEnabled();
+ $config = ComponentHelper::getParams('com_finder');
+
+ if ($config->get('language_default', '') == '') {
+ $defaultLang = '*';
+ } elseif ($config->get('language_default', '') == '-1') {
+ $defaultLang = self::getDefaultLanguage();
+ } else {
+ $defaultLang = $config->get('language_default');
+ }
+
+ /*
+ * The default language always has the language code '*'.
+ * In order to not overwrite the language code of the language
+ * object that we are using, we are cloning it here.
+ */
+ $obj = Language::getInstance($defaultLang);
+ $defaultLanguage = clone $obj;
+ $defaultLanguage->language = '*';
+ }
+
+ if (!$multilingual || $lang == '*') {
+ $language = $defaultLanguage;
+ } else {
+ $language = Language::getInstance($lang);
+ }
+
+ if (!isset($cache[$lang])) {
+ $cache[$lang] = [];
+ }
+
+ $tokens = array();
+ $terms = $language->tokenise($input);
+
+ // @todo: array_filter removes any number 0's from the terms. Not sure this is entirely intended
+ $terms = array_filter($terms);
+ $terms = array_values($terms);
+
+ /*
+ * If we have to handle the input as a phrase, that means we don't
+ * tokenize the individual terms and we do not create the two and three
+ * term combinations. The phrase must contain more than one word!
+ */
+ if ($phrase === true && count($terms) > 1) {
+ // Create tokens from the phrase.
+ $tokens[] = new Token($terms, $language->language, $language->spacer);
+ } else {
+ // Create tokens from the terms.
+ for ($i = 0, $n = count($terms); $i < $n; $i++) {
+ if (isset($cache[$lang][$terms[$i]])) {
+ $tokens[] = $cache[$lang][$terms[$i]];
+ } else {
+ $token = new Token($terms[$i], $language->language);
+ $tokens[] = $token;
+ $cache[$lang][$terms[$i]] = $token;
+ }
+ }
+
+ // Create multi-word phrase tokens from the individual words.
+ if ($tuplecount > 1) {
+ for ($i = 0, $n = count($tokens); $i < $n; $i++) {
+ $temp = array($tokens[$i]->term);
+
+ // Create tokens for 2 to $tuplecount length phrases
+ for ($j = 1; $j < $tuplecount; $j++) {
+ if ($i + $j >= $n || !isset($tokens[$i + $j])) {
+ break;
+ }
+
+ $temp[] = $tokens[$i + $j]->term;
+ $key = implode('::', $temp);
+
+ if (isset($cache[$lang][$key])) {
+ $tokens[] = $cache[$lang][$key];
+ } else {
+ $token = new Token($temp, $language->language, $language->spacer);
+ $token->derived = true;
+ $tokens[] = $token;
+ $cache[$lang][$key] = $token;
+ }
+ }
+ }
+ }
+ }
+
+ // Prevent the cache to fill up the memory
+ while (count($cache[$lang]) > 1024) {
+ /**
+ * We want to cache the most common words/tokens. At the same time
+ * we don't want to cache too much. The most common words will also
+ * be early in the text, so we are dropping all terms/tokens which
+ * have been cached later.
+ */
+ array_pop($cache[$lang]);
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Method to get the base word of a token.
+ *
+ * @param string $token The token to stem.
+ * @param string $lang The language of the token.
+ *
+ * @return string The root token.
+ *
+ * @since 2.5
+ */
+ public static function stem($token, $lang)
+ {
+ static $multilingual;
+ static $defaultStemmer;
+
+ if (is_null($multilingual)) {
+ $multilingual = Multilanguage::isEnabled();
+ $config = ComponentHelper::getParams('com_finder');
+
+ if ($config->get('language_default', '') == '') {
+ $defaultStemmer = Language::getInstance('*');
+ } elseif ($config->get('language_default', '') == '-1') {
+ $defaultStemmer = Language::getInstance(self::getDefaultLanguage());
+ } else {
+ $defaultStemmer = Language::getInstance($config->get('language_default'));
+ }
+ }
+
+ if (!$multilingual || $lang == '*') {
+ $language = $defaultStemmer;
+ } else {
+ $language = Language::getInstance($lang);
+ }
+
+ return $language->stem($token);
+ }
+
+ /**
+ * Method to add a content type to the database.
+ *
+ * @param string $title The type of content. For example: PDF
+ * @param string $mime The mime type of the content. For example: PDF [optional]
+ *
+ * @return integer The id of the content type.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ public static function addContentType($title, $mime = null)
+ {
+ static $types;
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ // Check if the types are loaded.
+ if (empty($types)) {
+ // Build the query to get the types.
+ $query->select('*')
+ ->from($db->quoteName('#__finder_types'));
+
+ // Get the types.
+ $db->setQuery($query);
+ $types = $db->loadObjectList('title');
+ }
+
+ // Check if the type already exists.
+ if (isset($types[$title])) {
+ return (int) $types[$title]->id;
+ }
+
+ // Add the type.
+ $query->clear()
+ ->insert($db->quoteName('#__finder_types'))
+ ->columns(array($db->quoteName('title'), $db->quoteName('mime')))
+ ->values($db->quote($title) . ', ' . $db->quote($mime));
+ $db->setQuery($query);
+ $db->execute();
+
+ // Return the new id.
+ return (int) $db->insertid();
+ }
+
+ /**
+ * Method to check if a token is common in a language.
+ *
+ * @param string $token The token to test.
+ * @param string $lang The language to reference.
+ *
+ * @return boolean True if common, false otherwise.
+ *
+ * @since 2.5
+ */
+ public static function isCommon($token, $lang)
+ {
+ static $data, $default, $multilingual;
+
+ if (is_null($multilingual)) {
+ $multilingual = Multilanguage::isEnabled();
+ $config = ComponentHelper::getParams('com_finder');
+
+ if ($config->get('language_default', '') == '') {
+ $default = '*';
+ } elseif ($config->get('language_default', '') == '-1') {
+ $default = self::getPrimaryLanguage(self::getDefaultLanguage());
+ } else {
+ $default = self::getPrimaryLanguage($config->get('language_default'));
+ }
+ }
+
+ if (!$multilingual || $lang == '*') {
+ $lang = $default;
+ }
+
+ // Load the common tokens for the language if necessary.
+ if (!isset($data[$lang])) {
+ $data[$lang] = self::getCommonWords($lang);
+ }
+
+ // Check if the token is in the common array.
+ return in_array($token, $data[$lang], true);
+ }
+
+ /**
+ * Method to get an array of common terms for a language.
+ *
+ * @param string $lang The language to use.
+ *
+ * @return array Array of common terms.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ public static function getCommonWords($lang)
+ {
+ $db = Factory::getDbo();
+
+ // Create the query to load all the common terms for the language.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('term'))
+ ->from($db->quoteName('#__finder_terms_common'))
+ ->where($db->quoteName('language') . ' = ' . $db->quote($lang));
+
+ // Load all of the common terms for the language.
+ $db->setQuery($query);
+
+ return $db->loadColumn();
+ }
+
+ /**
+ * Method to get the default language for the site.
+ *
+ * @return string The default language string.
+ *
+ * @since 2.5
+ */
+ public static function getDefaultLanguage()
+ {
+ static $lang;
+
+ // We need to go to com_languages to get the site default language, it's the best we can guess.
+ if (empty($lang)) {
+ $lang = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
+ }
+
+ return $lang;
+ }
+
+ /**
+ * Method to parse a language/locale key and return a simple language string.
+ *
+ * @param string $lang The language/locale key. For example: en-GB
+ *
+ * @return string The simple language string. For example: en
+ *
+ * @since 2.5
+ */
+ public static function getPrimaryLanguage($lang)
+ {
+ static $data;
+
+ // Only parse the identifier if necessary.
+ if (!isset($data[$lang])) {
+ if (is_callable(array('Locale', 'getPrimaryLanguage'))) {
+ // Get the language key using the Locale package.
+ $data[$lang] = \Locale::getPrimaryLanguage($lang);
+ } else {
+ // Get the language key using string position.
+ $data[$lang] = StringHelper::substr($lang, 0, StringHelper::strpos($lang, '-'));
+ }
+ }
+
+ return $data[$lang];
+ }
+
+ /**
+ * Method to get extra data for a content before being indexed. This is how
+ * we add Comments, Tags, Labels, etc. that should be available to Finder.
+ *
+ * @param Result $item The item to index as a Result object.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ public static function getContentExtras(Result $item)
+ {
+ // Load the finder plugin group.
+ PluginHelper::importPlugin('finder');
+
+ Factory::getApplication()->triggerEvent('onPrepareFinderContent', array(&$item));
+
+ return true;
+ }
+
+ /**
+ * Add custom fields for the item to the Result object
+ *
+ * @param Result $item Result object to add the custom fields to
+ * @param string $context Context of the item in the custom fields
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public static function addCustomFields(Result $item, $context)
+ {
+ $obj = new \stdClass();
+ $obj->id = $item->id;
+
+ $fields = FieldsHelper::getFields($context, $obj, true);
+
+ foreach ($fields as $field) {
+ $searchindex = $field->params->get('searchindex', 0);
+
+ // We want to add this field to the search index
+ if ($searchindex == 1 || $searchindex == 3) {
+ $name = 'jsfield_' . $field->name;
+ $item->$name = $field->value;
+ $item->addInstruction(Indexer::META_CONTEXT, $name);
+ }
+
+ // We want to add this field as a taxonomy
+ if (($searchindex == 2 || $searchindex == 3) && $field->value) {
+ $item->addTaxonomy($field->title, $field->value, $field->state, $field->access, $field->language);
+ }
+ }
+ }
+
+ /**
+ * Method to process content text using the onContentPrepare event trigger.
+ *
+ * @param string $text The content to process.
+ * @param Registry $params The parameters object. [optional]
+ * @param Result $item The item which get prepared. [optional]
+ *
+ * @return string The processed content.
+ *
+ * @since 2.5
+ */
+ public static function prepareContent($text, $params = null, Result $item = null)
+ {
+ static $loaded;
+
+ // Load the content plugins if necessary.
+ if (empty($loaded)) {
+ PluginHelper::importPlugin('content');
+ $loaded = true;
+ }
+
+ // Instantiate the parameter object if necessary.
+ if (!($params instanceof Registry)) {
+ $registry = new Registry($params);
+ $params = $registry;
+ }
+
+ // Create a mock content object.
+ $content = Table::getInstance('Content');
+ $content->text = $text;
+
+ if ($item) {
+ $content->bind((array) $item);
+ $content->bind($item->getElements());
+ }
+
+ if ($item && !empty($item->context)) {
+ $content->context = $item->context;
+ }
+
+ // Fire the onContentPrepare event.
+ Factory::getApplication()->triggerEvent('onContentPrepare', array('com_finder.indexer', &$content, &$params, 0));
+
+ return $content->text;
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Indexer.php b/administrator/components/com_finder/src/Indexer/Indexer.php
index 9625c424b94b2..a5d86d55568c0 100644
--- a/administrator/components/com_finder/src/Indexer/Indexer.php
+++ b/administrator/components/com_finder/src/Indexer/Indexer.php
@@ -1,4 +1,5 @@
get(DatabaseInterface::class);
- }
-
- $this->db = $db;
-
- // Set up query template for addTokensToDb
- $this->addTokensToDbQueryTemplate = $db->getQuery(true)->insert($db->quoteName('#__finder_tokens'))
- ->columns(
- array(
- $db->quoteName('term'),
- $db->quoteName('stem'),
- $db->quoteName('common'),
- $db->quoteName('phrase'),
- $db->quoteName('weight'),
- $db->quoteName('context'),
- $db->quoteName('language')
- )
- );
- }
-
- /**
- * Method to get the indexer state.
- *
- * @return object The indexer state object.
- *
- * @since 2.5
- */
- public static function getState()
- {
- // First, try to load from the internal state.
- if ((bool) static::$state)
- {
- return static::$state;
- }
-
- // If we couldn't load from the internal state, try the session.
- $session = Factory::getSession();
- $data = $session->get('_finder.state', null);
-
- // If the state is empty, load the values for the first time.
- if (empty($data))
- {
- $data = new CMSObject;
-
- // Load the default configuration options.
- $data->options = ComponentHelper::getParams('com_finder');
-
- // Setup the weight lookup information.
- $data->weights = array(
- self::TITLE_CONTEXT => round($data->options->get('title_multiplier', 1.7), 2),
- self::TEXT_CONTEXT => round($data->options->get('text_multiplier', 0.7), 2),
- self::META_CONTEXT => round($data->options->get('meta_multiplier', 1.2), 2),
- self::PATH_CONTEXT => round($data->options->get('path_multiplier', 2.0), 2),
- self::MISC_CONTEXT => round($data->options->get('misc_multiplier', 0.3), 2)
- );
-
- // Set the current time as the start time.
- $data->startTime = Factory::getDate()->toSql();
-
- // Set the remaining default values.
- $data->batchSize = (int) $data->options->get('batch_size', 50);
- $data->batchOffset = 0;
- $data->totalItems = 0;
- $data->pluginState = array();
- }
-
- // Setup the profiler if debugging is enabled.
- if (Factory::getApplication()->get('debug'))
- {
- static::$profiler = Profiler::getInstance('FinderIndexer');
- }
-
- // Set the state.
- static::$state = $data;
-
- return static::$state;
- }
-
- /**
- * Method to set the indexer state.
- *
- * @param CMSObject $data A new indexer state object.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 2.5
- */
- public static function setState($data)
- {
- // Check the state object.
- if (empty($data) || !$data instanceof CMSObject)
- {
- return false;
- }
-
- // Set the new internal state.
- static::$state = $data;
-
- // Set the new session state.
- Factory::getSession()->set('_finder.state', $data);
-
- return true;
- }
-
- /**
- * Method to reset the indexer state.
- *
- * @return void
- *
- * @since 2.5
- */
- public static function resetState()
- {
- // Reset the internal state to null.
- self::$state = null;
-
- // Reset the session state to null.
- Factory::getSession()->set('_finder.state', null);
- }
-
- /**
- * Method to index a content item.
- *
- * @param Result $item The content item to index.
- * @param string $format The format of the content. [optional]
- *
- * @return integer The ID of the record in the links table.
- *
- * @since 2.5
- * @throws \Exception on database error.
- */
- public function index($item, $format = 'html')
- {
- // Mark beforeIndexing in the profiler.
- static::$profiler ? static::$profiler->mark('beforeIndexing') : null;
- $db = $this->db;
- $serverType = strtolower($db->getServerType());
-
- // Check if the item is in the database.
- $query = $db->getQuery(true)
- ->select($db->quoteName('link_id') . ', ' . $db->quoteName('md5sum'))
- ->from($db->quoteName('#__finder_links'))
- ->where($db->quoteName('url') . ' = ' . $db->quote($item->url));
-
- // Load the item from the database.
- $db->setQuery($query);
- $link = $db->loadObject();
-
- // Get the indexer state.
- $state = static::getState();
-
- // Get the signatures of the item.
- $curSig = static::getSignature($item);
- $oldSig = $link->md5sum ?? null;
-
- // Get the other item information.
- $linkId = empty($link->link_id) ? null : $link->link_id;
- $isNew = empty($link->link_id);
-
- // Check the signatures. If they match, the item is up to date.
- if (!$isNew && $curSig == $oldSig)
- {
- return $linkId;
- }
-
- /*
- * If the link already exists, flush all the term maps for the item.
- * Maps are stored in 16 tables so we need to iterate through and flush
- * each table one at a time.
- */
- if (!$isNew)
- {
- // Flush the maps for the link.
- $query->clear()
- ->delete($db->quoteName('#__finder_links_terms'))
- ->where($db->quoteName('link_id') . ' = ' . (int) $linkId);
- $db->setQuery($query);
- $db->execute();
-
- // Remove the taxonomy maps.
- Taxonomy::removeMaps($linkId);
- }
-
- // Mark afterUnmapping in the profiler.
- static::$profiler ? static::$profiler->mark('afterUnmapping') : null;
-
- // Perform cleanup on the item data.
- $item->publish_start_date = (int) $item->publish_start_date != 0 ? $item->publish_start_date : null;
- $item->publish_end_date = (int) $item->publish_end_date != 0 ? $item->publish_end_date : null;
- $item->start_date = (int) $item->start_date != 0 ? $item->start_date : null;
- $item->end_date = (int) $item->end_date != 0 ? $item->end_date : null;
-
- // Prepare the item description.
- $item->description = Helper::parse($item->summary ?? '');
-
- /*
- * Now, we need to enter the item into the links table. If the item
- * already exists in the database, we need to use an UPDATE query.
- * Otherwise, we need to use an INSERT to get the link id back.
- */
- $entry = new \stdClass;
- $entry->url = $item->url;
- $entry->route = $item->route;
- $entry->title = $item->title;
-
- // We are shortening the description in order to not run into length issues with this field
- $entry->description = StringHelper::substr($item->description, 0, 32000);
- $entry->indexdate = Factory::getDate()->toSql();
- $entry->state = (int) $item->state;
- $entry->access = (int) $item->access;
- $entry->language = $item->language;
- $entry->type_id = (int) $item->type_id;
- $entry->object = '';
- $entry->publish_start_date = $item->publish_start_date;
- $entry->publish_end_date = $item->publish_end_date;
- $entry->start_date = $item->start_date;
- $entry->end_date = $item->end_date;
- $entry->list_price = (double) ($item->list_price ?: 0);
- $entry->sale_price = (double) ($item->sale_price ?: 0);
-
- if ($isNew)
- {
- // Insert the link and get its id.
- $db->insertObject('#__finder_links', $entry);
- $linkId = (int) $db->insertid();
- }
- else
- {
- // Update the link.
- $entry->link_id = $linkId;
- $db->updateObject('#__finder_links', $entry, 'link_id');
- }
-
- // Set up the variables we will need during processing.
- $count = 0;
-
- // Mark afterLinking in the profiler.
- static::$profiler ? static::$profiler->mark('afterLinking') : null;
-
- // Truncate the tokens tables.
- $db->truncateTable('#__finder_tokens');
-
- // Truncate the tokens aggregate table.
- $db->truncateTable('#__finder_tokens_aggregate');
-
- /*
- * Process the item's content. The items can customize their
- * processing instructions to define extra properties to process
- * or rearrange how properties are weighted.
- */
- foreach ($item->getInstructions() as $group => $properties)
- {
- // Iterate through the properties of the group.
- foreach ($properties as $property)
- {
- // Check if the property exists in the item.
- if (empty($item->$property))
- {
- continue;
- }
-
- // Tokenize the property.
- if (is_array($item->$property))
- {
- // Tokenize an array of content and add it to the database.
- foreach ($item->$property as $ip)
- {
- /*
- * If the group is path, we need to a few extra processing
- * steps to strip the extension and convert slashes and dashes
- * to spaces.
- */
- if ($group === static::PATH_CONTEXT)
- {
- $ip = File::stripExt($ip);
- $ip = str_replace(array('/', '-'), ' ', $ip);
- }
-
- // Tokenize a string of content and add it to the database.
- $count += $this->tokenizeToDb($ip, $group, $item->language, $format);
-
- // Check if we're approaching the memory limit of the token table.
- if ($count > static::$state->options->get('memory_table_limit', 30000))
- {
- $this->toggleTables(false);
- }
- }
- }
- else
- {
- /*
- * If the group is path, we need to a few extra processing
- * steps to strip the extension and convert slashes and dashes
- * to spaces.
- */
- if ($group === static::PATH_CONTEXT)
- {
- $item->$property = File::stripExt($item->$property);
- $item->$property = str_replace('/', ' ', $item->$property);
- $item->$property = str_replace('-', ' ', $item->$property);
- }
-
- // Tokenize a string of content and add it to the database.
- $count += $this->tokenizeToDb($item->$property, $group, $item->language, $format);
-
- // Check if we're approaching the memory limit of the token table.
- if ($count > static::$state->options->get('memory_table_limit', 30000))
- {
- $this->toggleTables(false);
- }
- }
- }
- }
-
- /*
- * Process the item's taxonomy. The items can customize their
- * taxonomy mappings to define extra properties to map.
- */
- foreach ($item->getTaxonomy() as $branch => $nodes)
- {
- // Iterate through the nodes and map them to the branch.
- foreach ($nodes as $node)
- {
- // Add the node to the tree.
- if ($node->nested)
- {
- $nodeId = Taxonomy::addNestedNode($branch, $node->node, $node->state, $node->access, $node->language);
- }
- else
- {
- $nodeId = Taxonomy::addNode($branch, $node->title, $node->state, $node->access, $node->language);
- }
-
- // Add the link => node map.
- Taxonomy::addMap($linkId, $nodeId);
- $node->id = $nodeId;
- }
- }
-
- // Mark afterProcessing in the profiler.
- static::$profiler ? static::$profiler->mark('afterProcessing') : null;
-
- /*
- * At this point, all of the item's content has been parsed, tokenized
- * and inserted into the #__finder_tokens table. Now, we need to
- * aggregate all the data into that table into a more usable form. The
- * aggregated data will be inserted into #__finder_tokens_aggregate
- * table.
- */
- $query = 'INSERT INTO ' . $db->quoteName('#__finder_tokens_aggregate') .
- ' (' . $db->quoteName('term_id') .
- ', ' . $db->quoteName('term') .
- ', ' . $db->quoteName('stem') .
- ', ' . $db->quoteName('common') .
- ', ' . $db->quoteName('phrase') .
- ', ' . $db->quoteName('term_weight') .
- ', ' . $db->quoteName('context') .
- ', ' . $db->quoteName('context_weight') .
- ', ' . $db->quoteName('total_weight') .
- ', ' . $db->quoteName('language') . ')' .
- ' SELECT' .
- ' COALESCE(t.term_id, 0), t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context,' .
- ' ROUND( t1.weight * COUNT( t2.term ) * %F, 8 ) AS context_weight, 0, t1.language' .
- ' FROM (' .
- ' SELECT DISTINCT t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context, t1.language' .
- ' FROM ' . $db->quoteName('#__finder_tokens') . ' AS t1' .
- ' WHERE t1.context = %d' .
- ' ) AS t1' .
- ' JOIN ' . $db->quoteName('#__finder_tokens') . ' AS t2 ON t2.term = t1.term AND t2.language = t1.language' .
- ' LEFT JOIN ' . $db->quoteName('#__finder_terms') . ' AS t ON t.term = t1.term AND t.language = t1.language' .
- ' WHERE t2.context = %d' .
- ' GROUP BY t1.term, t.term_id, t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context, t1.language' .
- ' ORDER BY t1.term DESC';
-
- // Iterate through the contexts and aggregate the tokens per context.
- foreach ($state->weights as $context => $multiplier)
- {
- // Run the query to aggregate the tokens for this context..
- $db->setQuery(sprintf($query, $multiplier, $context, $context));
- $db->execute();
- }
-
- // Mark afterAggregating in the profiler.
- static::$profiler ? static::$profiler->mark('afterAggregating') : null;
-
- /*
- * When we pulled down all of the aggregate data, we did a LEFT JOIN
- * over the terms table to try to find all the term ids that
- * already exist for our tokens. If any of the rows in the aggregate
- * table have a term of 0, then no term record exists for that
- * term so we need to add it to the terms table.
- */
- $db->setQuery(
- 'INSERT INTO ' . $db->quoteName('#__finder_terms') .
- ' (' . $db->quoteName('term') .
- ', ' . $db->quoteName('stem') .
- ', ' . $db->quoteName('common') .
- ', ' . $db->quoteName('phrase') .
- ', ' . $db->quoteName('weight') .
- ', ' . $db->quoteName('soundex') .
- ', ' . $db->quoteName('language') . ')' .
- ' SELECT ta.term, ta.stem, ta.common, ta.phrase, ta.term_weight, SOUNDEX(ta.term), ta.language' .
- ' FROM ' . $db->quoteName('#__finder_tokens_aggregate') . ' AS ta' .
- ' WHERE ta.term_id = 0' .
- ' GROUP BY ta.term, ta.stem, ta.common, ta.phrase, ta.term_weight, SOUNDEX(ta.term), ta.language'
- );
- $db->execute();
-
- /*
- * Now, we just inserted a bunch of new records into the terms table
- * so we need to go back and update the aggregate table with all the
- * new term ids.
- */
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__finder_tokens_aggregate', 'ta'))
- ->innerJoin($db->quoteName('#__finder_terms', 't'), 't.term = ta.term AND t.language = ta.language')
- ->where('ta.term_id = 0');
-
- if ($serverType == 'mysql')
- {
- $query->set($db->quoteName('ta.term_id') . ' = ' . $db->quoteName('t.term_id'));
- }
- else
- {
- $query->set($db->quoteName('term_id') . ' = ' . $db->quoteName('t.term_id'));
- }
-
- $db->setQuery($query);
- $db->execute();
-
- // Mark afterTerms in the profiler.
- static::$profiler ? static::$profiler->mark('afterTerms') : null;
-
- /*
- * After we've made sure that all of the terms are in the terms table
- * and the aggregate table has the correct term ids, we need to update
- * the links counter for each term by one.
- */
- $query->clear()
- ->update($db->quoteName('#__finder_terms', 't'))
- ->innerJoin($db->quoteName('#__finder_tokens_aggregate', 'ta'), 'ta.term_id = t.term_id');
-
- if ($serverType == 'mysql')
- {
- $query->set($db->quoteName('t.links') . ' = t.links + 1');
- }
- else
- {
- $query->set($db->quoteName('links') . ' = t.links + 1');
- }
-
- $db->setQuery($query);
- $db->execute();
-
- // Mark afterTerms in the profiler.
- static::$profiler ? static::$profiler->mark('afterTerms') : null;
-
- /*
- * At this point, the aggregate table contains a record for each
- * term in each context. So, we're going to pull down all of that
- * data while grouping the records by term and add all of the
- * sub-totals together to arrive at the final total for each token for
- * this link. Then, we insert all of that data into the mapping table.
- */
- $db->setQuery(
- 'INSERT INTO ' . $db->quoteName('#__finder_links_terms') .
- ' (' . $db->quoteName('link_id') .
- ', ' . $db->quoteName('term_id') .
- ', ' . $db->quoteName('weight') . ')' .
- ' SELECT ' . (int) $linkId . ', ' . $db->quoteName('term_id') . ',' .
- ' ROUND(SUM(' . $db->quoteName('context_weight') . '), 8)' .
- ' FROM ' . $db->quoteName('#__finder_tokens_aggregate') .
- ' GROUP BY ' . $db->quoteName('term') . ', ' . $db->quoteName('term_id') .
- ' ORDER BY ' . $db->quoteName('term') . ' DESC'
- );
- $db->execute();
-
- // Mark afterMapping in the profiler.
- static::$profiler ? static::$profiler->mark('afterMapping') : null;
-
- // Update the signature.
- $object = serialize($item);
- $query->clear()
- ->update($db->quoteName('#__finder_links'))
- ->set($db->quoteName('md5sum') . ' = :md5sum')
- ->set($db->quoteName('object') . ' = :object')
- ->where($db->quoteName('link_id') . ' = :linkid')
- ->bind(':md5sum', $curSig)
- ->bind(':object', $object, ParameterType::LARGE_OBJECT)
- ->bind(':linkid', $linkId, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- // Mark afterSigning in the profiler.
- static::$profiler ? static::$profiler->mark('afterSigning') : null;
-
- // Truncate the tokens tables.
- $db->truncateTable('#__finder_tokens');
-
- // Truncate the tokens aggregate table.
- $db->truncateTable('#__finder_tokens_aggregate');
-
- // Toggle the token tables back to memory tables.
- $this->toggleTables(true);
-
- // Mark afterTruncating in the profiler.
- static::$profiler ? static::$profiler->mark('afterTruncating') : null;
-
- // Trigger a plugin event after indexing
- PluginHelper::importPlugin('finder');
- Factory::getApplication()->triggerEvent('onFinderIndexAfterIndex', array($item, $linkId));
-
- return $linkId;
- }
-
- /**
- * Method to remove a link from the index.
- *
- * @param integer $linkId The id of the link.
- * @param bool $removeTaxonomies Remove empty taxonomies
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- public function remove($linkId, $removeTaxonomies = true)
- {
- $db = $this->db;
- $query = $db->getQuery(true);
- $linkId = (int) $linkId;
-
- // Update the link counts for the terms.
- $query->clear()
- ->update($db->quoteName('#__finder_terms', 't'))
- ->join('INNER', $db->quoteName('#__finder_links_terms', 'm'), $db->quoteName('m.term_id') . ' = ' . $db->quoteName('t.term_id'))
- ->set($db->quoteName('links') . ' = ' . $db->quoteName('links') . ' - 1')
- ->where($db->quoteName('m.link_id') . ' = :linkid')
- ->bind(':linkid', $linkId, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
-
- // Remove all records from the mapping tables.
- $query->clear()
- ->delete($db->quoteName('#__finder_links_terms'))
- ->where($db->quoteName('link_id') . ' = :linkid')
- ->bind(':linkid', $linkId, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
-
- // Delete all orphaned terms.
- $query->clear()
- ->delete($db->quoteName('#__finder_terms'))
- ->where($db->quoteName('links') . ' <= 0');
- $db->setQuery($query)->execute();
-
- // Delete the link from the index.
- $query->clear()
- ->delete($db->quoteName('#__finder_links'))
- ->where($db->quoteName('link_id') . ' = :linkid')
- ->bind(':linkid', $linkId, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
-
- // Remove the taxonomy maps.
- Taxonomy::removeMaps($linkId);
-
- // Remove the orphaned taxonomy nodes.
- if ($removeTaxonomies)
- {
- Taxonomy::removeOrphanNodes();
- }
-
- PluginHelper::importPlugin('finder');
- Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', array($linkId));
-
- return true;
- }
-
- /**
- * Method to optimize the index. We use this method to remove unused terms
- * and any other optimizations that might be necessary.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- public function optimize()
- {
- // Get the database object.
- $db = $this->db;
- $serverType = strtolower($db->getServerType());
- $query = $db->getQuery(true);
-
- // Delete all orphaned terms.
- $query->delete($db->quoteName('#__finder_terms'))
- ->where($db->quoteName('links') . ' <= 0');
- $db->setQuery($query);
- $db->execute();
-
- // Delete all broken links. (Links missing the object)
- $query = $db->getQuery(true)
- ->delete('#__finder_links')
- ->where($db->quoteName('object') . ' = ' . $db->quote(''));
- $db->setQuery($query);
- $db->execute();
-
- // Delete all orphaned mappings of terms to links
- $query2 = $db->getQuery(true)
- ->select($db->quoteName('link_id'))
- ->from($db->quoteName('#__finder_links'));
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__finder_links_terms'))
- ->where($db->quoteName('link_id') . ' NOT IN (' . $query2 . ')');
- $db->setQuery($query);
- $db->execute();
-
- // Delete all orphaned terms
- $query2 = $db->getQuery(true)
- ->select($db->quoteName('term_id'))
- ->from($db->quoteName('#__finder_links_terms'));
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__finder_terms'))
- ->where($db->quoteName('term_id') . ' NOT IN (' . $query2 . ')');
- $db->setQuery($query);
- $db->execute();
-
- // Delete all orphaned taxonomies
- Taxonomy::removeOrphanMaps();
- Taxonomy::removeOrphanNodes();
-
- // Optimize the tables.
- $tables = [
- '#__finder_links',
- '#__finder_links_terms',
- '#__finder_filters',
- '#__finder_terms_common',
- '#__finder_types',
- '#__finder_taxonomy_map',
- '#__finder_taxonomy'
- ];
-
- foreach ($tables as $table)
- {
- if ($serverType == 'mysql')
- {
- $db->setQuery('OPTIMIZE TABLE ' . $db->quoteName($table));
- $db->execute();
- }
- else
- {
- $db->setQuery('VACUUM ' . $db->quoteName($table));
- $db->execute();
- $db->setQuery('REINDEX TABLE ' . $db->quoteName($table));
- $db->execute();
- }
- }
-
- return true;
- }
-
- /**
- * Method to get a content item's signature.
- *
- * @param object $item The content item to index.
- *
- * @return string The content item's signature.
- *
- * @since 2.5
- */
- protected static function getSignature($item)
- {
- // Get the indexer state.
- $state = static::getState();
-
- // Get the relevant configuration variables.
- $config = array(
- $state->weights,
- $state->options->get('stem', 1),
- $state->options->get('stemmer', 'porter_en')
- );
-
- return md5(serialize(array($item, $config)));
- }
-
- /**
- * Method to parse input, tokenize it, and then add it to the database.
- *
- * @param mixed $input String or resource to use as input. A resource input will automatically be chunked to conserve
- * memory. Strings will be chunked if longer than 2K in size.
- * @param integer $context The context of the input. See context constants.
- * @param string $lang The language of the input.
- * @param string $format The format of the input.
- *
- * @return integer The number of tokens extracted from the input.
- *
- * @since 2.5
- */
- protected function tokenizeToDb($input, $context, $lang, $format)
- {
- $count = 0;
- $buffer = null;
-
- if (empty($input))
- {
- return $count;
- }
-
- // If the input is a resource, batch the process out.
- if (is_resource($input))
- {
- // Batch the process out to avoid memory limits.
- while (!feof($input))
- {
- // Read into the buffer.
- $buffer .= fread($input, 2048);
-
- /*
- * If we haven't reached the end of the file, seek to the last
- * space character and drop whatever is after that to make sure
- * we didn't truncate a term while reading the input.
- */
- if (!feof($input))
- {
- // Find the last space character.
- $ls = strrpos($buffer, ' ');
-
- // Adjust string based on the last space character.
- if ($ls)
- {
- // Truncate the string to the last space character.
- $string = substr($buffer, 0, $ls);
-
- // Adjust the buffer based on the last space for the next iteration and trim.
- $buffer = StringHelper::trim(substr($buffer, $ls));
- }
- // No space character was found.
- else
- {
- $string = $buffer;
- }
- }
- // We've reached the end of the file, so parse whatever remains.
- else
- {
- $string = $buffer;
- }
-
- // Parse, tokenise and add tokens to the database.
- $count = $this->tokenizeToDbShort($string, $context, $lang, $format, $count);
-
- unset($string);
- }
-
- return $count;
- }
-
- // Parse, tokenise and add tokens to the database.
- $count = $this->tokenizeToDbShort($input, $context, $lang, $format, $count);
-
- return $count;
- }
-
- /**
- * Method to parse input, tokenise it, then add the tokens to the database.
- *
- * @param string $input String to parse, tokenise and add to database.
- * @param integer $context The context of the input. See context constants.
- * @param string $lang The language of the input.
- * @param string $format The format of the input.
- * @param integer $count The number of tokens processed so far.
- *
- * @return integer Cumulative number of tokens extracted from the input so far.
- *
- * @since 3.7.0
- */
- private function tokenizeToDbShort($input, $context, $lang, $format, $count)
- {
- // Parse the input.
- $input = Helper::parse($input, $format);
-
- // Check the input.
- if (empty($input))
- {
- return $count;
- }
-
- // Tokenize the input.
- $tokens = Helper::tokenize($input, $lang);
-
- if (count($tokens) == 0)
- {
- return $count;
- }
-
- // Add the tokens to the database.
- $count += $this->addTokensToDb($tokens, $context);
-
- // Check if we're approaching the memory limit of the token table.
- if ($count > static::$state->options->get('memory_table_limit', 10000))
- {
- $this->toggleTables(false);
- }
-
- return $count;
- }
-
- /**
- * Method to add a set of tokens to the database.
- *
- * @param Token[]|Token $tokens An array or single Token object.
- * @param mixed $context The context of the tokens. See context constants. [optional]
- *
- * @return integer The number of tokens inserted into the database.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function addTokensToDb($tokens, $context = '')
- {
- static $filterCommon, $filterNumeric;
-
- if (is_null($filterCommon))
- {
- $params = ComponentHelper::getParams('com_finder');
- $filterCommon = $params->get('filter_commonwords', false);
- $filterNumeric = $params->get('filter_numerics', false);
- }
-
- // Get the database object.
- $db = $this->db;
-
- $query = clone $this->addTokensToDbQueryTemplate;
-
- // Check if a single FinderIndexerToken object was given and make it to be an array of FinderIndexerToken objects
- $tokens = is_array($tokens) ? $tokens : array($tokens);
-
- // Count the number of token values.
- $values = 0;
-
- // Break into chunks of no more than 128 items
- $chunks = array_chunk($tokens, 128);
-
- foreach ($chunks as $tokens)
- {
- $query->clear('values');
-
- foreach ($tokens as $token)
- {
- // Database size for a term field
- if ($token->length > 75)
- {
- continue;
- }
-
- if ($filterCommon && $token->common)
- {
- continue;
- }
-
- if ($filterNumeric && $token->numeric)
- {
- continue;
- }
-
- $query->values(
- $db->quote($token->term) . ', '
- . $db->quote($token->stem) . ', '
- . (int) $token->common . ', '
- . (int) $token->phrase . ', '
- . $db->quote($token->weight) . ', '
- . (int) $context . ', '
- . $db->quote($token->language)
- );
- ++$values;
- }
-
- // Only execute the query if there are tokens to insert
- if ($query->values !== null)
- {
- $db->setQuery($query)->execute();
- }
-
- // Check if we're approaching the memory limit of the token table.
- if ($values > static::$state->options->get('memory_table_limit', 10000))
- {
- $this->toggleTables(false);
- }
- }
-
- return $values;
- }
-
- /**
- * Method to switch the token tables from Memory tables to Disk tables
- * when they are close to running out of memory.
- * Since this is not supported/implemented in all DB-drivers, the default is a stub method, which simply returns true.
- *
- * @param boolean $memory Flag to control how they should be toggled.
- *
- * @return boolean True on success.
- *
- * @since 2.5
- * @throws Exception on database error.
- */
- protected function toggleTables($memory)
- {
- if (strtolower($this->db->getServerType()) != 'mysql')
- {
- return true;
- }
-
- static $state;
-
- // Get the database adapter.
- $db = $this->db;
-
- // Check if we are setting the tables to the Memory engine.
- if ($memory === true && $state !== true)
- {
- // Set the tokens table to Memory.
- $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens') . ' ENGINE = MEMORY');
- $db->execute();
-
- // Set the tokens aggregate table to Memory.
- $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens_aggregate') . ' ENGINE = MEMORY');
- $db->execute();
-
- // Set the internal state.
- $state = $memory;
- }
- // We must be setting the tables to the InnoDB engine.
- elseif ($memory === false && $state !== false)
- {
- // Set the tokens table to InnoDB.
- $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens') . ' ENGINE = INNODB');
- $db->execute();
-
- // Set the tokens aggregate table to InnoDB.
- $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens_aggregate') . ' ENGINE = INNODB');
- $db->execute();
-
- // Set the internal state.
- $state = $memory;
- }
-
- return true;
- }
+ /**
+ * The title context identifier.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ public const TITLE_CONTEXT = 1;
+
+ /**
+ * The text context identifier.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ public const TEXT_CONTEXT = 2;
+
+ /**
+ * The meta context identifier.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ public const META_CONTEXT = 3;
+
+ /**
+ * The path context identifier.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ public const PATH_CONTEXT = 4;
+
+ /**
+ * The misc context identifier.
+ *
+ * @var integer
+ * @since 2.5
+ */
+ public const MISC_CONTEXT = 5;
+
+ /**
+ * The indexer state object.
+ *
+ * @var CMSObject
+ * @since 2.5
+ */
+ public static $state;
+
+ /**
+ * The indexer profiler object.
+ *
+ * @var Profiler
+ * @since 2.5
+ */
+ public static $profiler;
+
+ /**
+ * Database driver cache.
+ *
+ * @var \Joomla\Database\DatabaseDriver
+ * @since 3.8.0
+ */
+ protected $db;
+
+ /**
+ * Reusable Query Template. To be used with clone.
+ *
+ * @var QueryInterface
+ * @since 3.8.0
+ */
+ protected $addTokensToDbQueryTemplate;
+
+ /**
+ * Indexer constructor.
+ *
+ * @param DatabaseInterface $db The database
+ *
+ * @since 3.8.0
+ */
+ public function __construct(DatabaseInterface $db = null)
+ {
+ if ($db === null) {
+ @trigger_error(sprintf('Database will be mandatory in 5.0.'), E_USER_DEPRECATED);
+ $db = Factory::getContainer()->get(DatabaseInterface::class);
+ }
+
+ $this->db = $db;
+
+ // Set up query template for addTokensToDb
+ $this->addTokensToDbQueryTemplate = $db->getQuery(true)->insert($db->quoteName('#__finder_tokens'))
+ ->columns(
+ array(
+ $db->quoteName('term'),
+ $db->quoteName('stem'),
+ $db->quoteName('common'),
+ $db->quoteName('phrase'),
+ $db->quoteName('weight'),
+ $db->quoteName('context'),
+ $db->quoteName('language')
+ )
+ );
+ }
+
+ /**
+ * Method to get the indexer state.
+ *
+ * @return object The indexer state object.
+ *
+ * @since 2.5
+ */
+ public static function getState()
+ {
+ // First, try to load from the internal state.
+ if ((bool) static::$state) {
+ return static::$state;
+ }
+
+ // If we couldn't load from the internal state, try the session.
+ $session = Factory::getSession();
+ $data = $session->get('_finder.state', null);
+
+ // If the state is empty, load the values for the first time.
+ if (empty($data)) {
+ $data = new CMSObject();
+
+ // Load the default configuration options.
+ $data->options = ComponentHelper::getParams('com_finder');
+
+ // Setup the weight lookup information.
+ $data->weights = array(
+ self::TITLE_CONTEXT => round($data->options->get('title_multiplier', 1.7), 2),
+ self::TEXT_CONTEXT => round($data->options->get('text_multiplier', 0.7), 2),
+ self::META_CONTEXT => round($data->options->get('meta_multiplier', 1.2), 2),
+ self::PATH_CONTEXT => round($data->options->get('path_multiplier', 2.0), 2),
+ self::MISC_CONTEXT => round($data->options->get('misc_multiplier', 0.3), 2)
+ );
+
+ // Set the current time as the start time.
+ $data->startTime = Factory::getDate()->toSql();
+
+ // Set the remaining default values.
+ $data->batchSize = (int) $data->options->get('batch_size', 50);
+ $data->batchOffset = 0;
+ $data->totalItems = 0;
+ $data->pluginState = array();
+ }
+
+ // Setup the profiler if debugging is enabled.
+ if (Factory::getApplication()->get('debug')) {
+ static::$profiler = Profiler::getInstance('FinderIndexer');
+ }
+
+ // Set the state.
+ static::$state = $data;
+
+ return static::$state;
+ }
+
+ /**
+ * Method to set the indexer state.
+ *
+ * @param CMSObject $data A new indexer state object.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 2.5
+ */
+ public static function setState($data)
+ {
+ // Check the state object.
+ if (empty($data) || !$data instanceof CMSObject) {
+ return false;
+ }
+
+ // Set the new internal state.
+ static::$state = $data;
+
+ // Set the new session state.
+ Factory::getSession()->set('_finder.state', $data);
+
+ return true;
+ }
+
+ /**
+ * Method to reset the indexer state.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public static function resetState()
+ {
+ // Reset the internal state to null.
+ self::$state = null;
+
+ // Reset the session state to null.
+ Factory::getSession()->set('_finder.state', null);
+ }
+
+ /**
+ * Method to index a content item.
+ *
+ * @param Result $item The content item to index.
+ * @param string $format The format of the content. [optional]
+ *
+ * @return integer The ID of the record in the links table.
+ *
+ * @since 2.5
+ * @throws \Exception on database error.
+ */
+ public function index($item, $format = 'html')
+ {
+ // Mark beforeIndexing in the profiler.
+ static::$profiler ? static::$profiler->mark('beforeIndexing') : null;
+ $db = $this->db;
+ $serverType = strtolower($db->getServerType());
+
+ // Check if the item is in the database.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('link_id') . ', ' . $db->quoteName('md5sum'))
+ ->from($db->quoteName('#__finder_links'))
+ ->where($db->quoteName('url') . ' = ' . $db->quote($item->url));
+
+ // Load the item from the database.
+ $db->setQuery($query);
+ $link = $db->loadObject();
+
+ // Get the indexer state.
+ $state = static::getState();
+
+ // Get the signatures of the item.
+ $curSig = static::getSignature($item);
+ $oldSig = $link->md5sum ?? null;
+
+ // Get the other item information.
+ $linkId = empty($link->link_id) ? null : $link->link_id;
+ $isNew = empty($link->link_id);
+
+ // Check the signatures. If they match, the item is up to date.
+ if (!$isNew && $curSig == $oldSig) {
+ return $linkId;
+ }
+
+ /*
+ * If the link already exists, flush all the term maps for the item.
+ * Maps are stored in 16 tables so we need to iterate through and flush
+ * each table one at a time.
+ */
+ if (!$isNew) {
+ // Flush the maps for the link.
+ $query->clear()
+ ->delete($db->quoteName('#__finder_links_terms'))
+ ->where($db->quoteName('link_id') . ' = ' . (int) $linkId);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Remove the taxonomy maps.
+ Taxonomy::removeMaps($linkId);
+ }
+
+ // Mark afterUnmapping in the profiler.
+ static::$profiler ? static::$profiler->mark('afterUnmapping') : null;
+
+ // Perform cleanup on the item data.
+ $item->publish_start_date = (int) $item->publish_start_date != 0 ? $item->publish_start_date : null;
+ $item->publish_end_date = (int) $item->publish_end_date != 0 ? $item->publish_end_date : null;
+ $item->start_date = (int) $item->start_date != 0 ? $item->start_date : null;
+ $item->end_date = (int) $item->end_date != 0 ? $item->end_date : null;
+
+ // Prepare the item description.
+ $item->description = Helper::parse($item->summary ?? '');
+
+ /*
+ * Now, we need to enter the item into the links table. If the item
+ * already exists in the database, we need to use an UPDATE query.
+ * Otherwise, we need to use an INSERT to get the link id back.
+ */
+ $entry = new \stdClass();
+ $entry->url = $item->url;
+ $entry->route = $item->route;
+ $entry->title = $item->title;
+
+ // We are shortening the description in order to not run into length issues with this field
+ $entry->description = StringHelper::substr($item->description, 0, 32000);
+ $entry->indexdate = Factory::getDate()->toSql();
+ $entry->state = (int) $item->state;
+ $entry->access = (int) $item->access;
+ $entry->language = $item->language;
+ $entry->type_id = (int) $item->type_id;
+ $entry->object = '';
+ $entry->publish_start_date = $item->publish_start_date;
+ $entry->publish_end_date = $item->publish_end_date;
+ $entry->start_date = $item->start_date;
+ $entry->end_date = $item->end_date;
+ $entry->list_price = (double) ($item->list_price ?: 0);
+ $entry->sale_price = (double) ($item->sale_price ?: 0);
+
+ if ($isNew) {
+ // Insert the link and get its id.
+ $db->insertObject('#__finder_links', $entry);
+ $linkId = (int) $db->insertid();
+ } else {
+ // Update the link.
+ $entry->link_id = $linkId;
+ $db->updateObject('#__finder_links', $entry, 'link_id');
+ }
+
+ // Set up the variables we will need during processing.
+ $count = 0;
+
+ // Mark afterLinking in the profiler.
+ static::$profiler ? static::$profiler->mark('afterLinking') : null;
+
+ // Truncate the tokens tables.
+ $db->truncateTable('#__finder_tokens');
+
+ // Truncate the tokens aggregate table.
+ $db->truncateTable('#__finder_tokens_aggregate');
+
+ /*
+ * Process the item's content. The items can customize their
+ * processing instructions to define extra properties to process
+ * or rearrange how properties are weighted.
+ */
+ foreach ($item->getInstructions() as $group => $properties) {
+ // Iterate through the properties of the group.
+ foreach ($properties as $property) {
+ // Check if the property exists in the item.
+ if (empty($item->$property)) {
+ continue;
+ }
+
+ // Tokenize the property.
+ if (is_array($item->$property)) {
+ // Tokenize an array of content and add it to the database.
+ foreach ($item->$property as $ip) {
+ /*
+ * If the group is path, we need to a few extra processing
+ * steps to strip the extension and convert slashes and dashes
+ * to spaces.
+ */
+ if ($group === static::PATH_CONTEXT) {
+ $ip = File::stripExt($ip);
+ $ip = str_replace(array('/', '-'), ' ', $ip);
+ }
+
+ // Tokenize a string of content and add it to the database.
+ $count += $this->tokenizeToDb($ip, $group, $item->language, $format);
+
+ // Check if we're approaching the memory limit of the token table.
+ if ($count > static::$state->options->get('memory_table_limit', 30000)) {
+ $this->toggleTables(false);
+ }
+ }
+ } else {
+ /*
+ * If the group is path, we need to a few extra processing
+ * steps to strip the extension and convert slashes and dashes
+ * to spaces.
+ */
+ if ($group === static::PATH_CONTEXT) {
+ $item->$property = File::stripExt($item->$property);
+ $item->$property = str_replace('/', ' ', $item->$property);
+ $item->$property = str_replace('-', ' ', $item->$property);
+ }
+
+ // Tokenize a string of content and add it to the database.
+ $count += $this->tokenizeToDb($item->$property, $group, $item->language, $format);
+
+ // Check if we're approaching the memory limit of the token table.
+ if ($count > static::$state->options->get('memory_table_limit', 30000)) {
+ $this->toggleTables(false);
+ }
+ }
+ }
+ }
+
+ /*
+ * Process the item's taxonomy. The items can customize their
+ * taxonomy mappings to define extra properties to map.
+ */
+ foreach ($item->getTaxonomy() as $branch => $nodes) {
+ // Iterate through the nodes and map them to the branch.
+ foreach ($nodes as $node) {
+ // Add the node to the tree.
+ if ($node->nested) {
+ $nodeId = Taxonomy::addNestedNode($branch, $node->node, $node->state, $node->access, $node->language);
+ } else {
+ $nodeId = Taxonomy::addNode($branch, $node->title, $node->state, $node->access, $node->language);
+ }
+
+ // Add the link => node map.
+ Taxonomy::addMap($linkId, $nodeId);
+ $node->id = $nodeId;
+ }
+ }
+
+ // Mark afterProcessing in the profiler.
+ static::$profiler ? static::$profiler->mark('afterProcessing') : null;
+
+ /*
+ * At this point, all of the item's content has been parsed, tokenized
+ * and inserted into the #__finder_tokens table. Now, we need to
+ * aggregate all the data into that table into a more usable form. The
+ * aggregated data will be inserted into #__finder_tokens_aggregate
+ * table.
+ */
+ $query = 'INSERT INTO ' . $db->quoteName('#__finder_tokens_aggregate') .
+ ' (' . $db->quoteName('term_id') .
+ ', ' . $db->quoteName('term') .
+ ', ' . $db->quoteName('stem') .
+ ', ' . $db->quoteName('common') .
+ ', ' . $db->quoteName('phrase') .
+ ', ' . $db->quoteName('term_weight') .
+ ', ' . $db->quoteName('context') .
+ ', ' . $db->quoteName('context_weight') .
+ ', ' . $db->quoteName('total_weight') .
+ ', ' . $db->quoteName('language') . ')' .
+ ' SELECT' .
+ ' COALESCE(t.term_id, 0), t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context,' .
+ ' ROUND( t1.weight * COUNT( t2.term ) * %F, 8 ) AS context_weight, 0, t1.language' .
+ ' FROM (' .
+ ' SELECT DISTINCT t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context, t1.language' .
+ ' FROM ' . $db->quoteName('#__finder_tokens') . ' AS t1' .
+ ' WHERE t1.context = %d' .
+ ' ) AS t1' .
+ ' JOIN ' . $db->quoteName('#__finder_tokens') . ' AS t2 ON t2.term = t1.term AND t2.language = t1.language' .
+ ' LEFT JOIN ' . $db->quoteName('#__finder_terms') . ' AS t ON t.term = t1.term AND t.language = t1.language' .
+ ' WHERE t2.context = %d' .
+ ' GROUP BY t1.term, t.term_id, t1.term, t1.stem, t1.common, t1.phrase, t1.weight, t1.context, t1.language' .
+ ' ORDER BY t1.term DESC';
+
+ // Iterate through the contexts and aggregate the tokens per context.
+ foreach ($state->weights as $context => $multiplier) {
+ // Run the query to aggregate the tokens for this context..
+ $db->setQuery(sprintf($query, $multiplier, $context, $context));
+ $db->execute();
+ }
+
+ // Mark afterAggregating in the profiler.
+ static::$profiler ? static::$profiler->mark('afterAggregating') : null;
+
+ /*
+ * When we pulled down all of the aggregate data, we did a LEFT JOIN
+ * over the terms table to try to find all the term ids that
+ * already exist for our tokens. If any of the rows in the aggregate
+ * table have a term of 0, then no term record exists for that
+ * term so we need to add it to the terms table.
+ */
+ $db->setQuery(
+ 'INSERT INTO ' . $db->quoteName('#__finder_terms') .
+ ' (' . $db->quoteName('term') .
+ ', ' . $db->quoteName('stem') .
+ ', ' . $db->quoteName('common') .
+ ', ' . $db->quoteName('phrase') .
+ ', ' . $db->quoteName('weight') .
+ ', ' . $db->quoteName('soundex') .
+ ', ' . $db->quoteName('language') . ')' .
+ ' SELECT ta.term, ta.stem, ta.common, ta.phrase, ta.term_weight, SOUNDEX(ta.term), ta.language' .
+ ' FROM ' . $db->quoteName('#__finder_tokens_aggregate') . ' AS ta' .
+ ' WHERE ta.term_id = 0' .
+ ' GROUP BY ta.term, ta.stem, ta.common, ta.phrase, ta.term_weight, SOUNDEX(ta.term), ta.language'
+ );
+ $db->execute();
+
+ /*
+ * Now, we just inserted a bunch of new records into the terms table
+ * so we need to go back and update the aggregate table with all the
+ * new term ids.
+ */
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__finder_tokens_aggregate', 'ta'))
+ ->innerJoin($db->quoteName('#__finder_terms', 't'), 't.term = ta.term AND t.language = ta.language')
+ ->where('ta.term_id = 0');
+
+ if ($serverType == 'mysql') {
+ $query->set($db->quoteName('ta.term_id') . ' = ' . $db->quoteName('t.term_id'));
+ } else {
+ $query->set($db->quoteName('term_id') . ' = ' . $db->quoteName('t.term_id'));
+ }
+
+ $db->setQuery($query);
+ $db->execute();
+
+ // Mark afterTerms in the profiler.
+ static::$profiler ? static::$profiler->mark('afterTerms') : null;
+
+ /*
+ * After we've made sure that all of the terms are in the terms table
+ * and the aggregate table has the correct term ids, we need to update
+ * the links counter for each term by one.
+ */
+ $query->clear()
+ ->update($db->quoteName('#__finder_terms', 't'))
+ ->innerJoin($db->quoteName('#__finder_tokens_aggregate', 'ta'), 'ta.term_id = t.term_id');
+
+ if ($serverType == 'mysql') {
+ $query->set($db->quoteName('t.links') . ' = t.links + 1');
+ } else {
+ $query->set($db->quoteName('links') . ' = t.links + 1');
+ }
+
+ $db->setQuery($query);
+ $db->execute();
+
+ // Mark afterTerms in the profiler.
+ static::$profiler ? static::$profiler->mark('afterTerms') : null;
+
+ /*
+ * At this point, the aggregate table contains a record for each
+ * term in each context. So, we're going to pull down all of that
+ * data while grouping the records by term and add all of the
+ * sub-totals together to arrive at the final total for each token for
+ * this link. Then, we insert all of that data into the mapping table.
+ */
+ $db->setQuery(
+ 'INSERT INTO ' . $db->quoteName('#__finder_links_terms') .
+ ' (' . $db->quoteName('link_id') .
+ ', ' . $db->quoteName('term_id') .
+ ', ' . $db->quoteName('weight') . ')' .
+ ' SELECT ' . (int) $linkId . ', ' . $db->quoteName('term_id') . ',' .
+ ' ROUND(SUM(' . $db->quoteName('context_weight') . '), 8)' .
+ ' FROM ' . $db->quoteName('#__finder_tokens_aggregate') .
+ ' GROUP BY ' . $db->quoteName('term') . ', ' . $db->quoteName('term_id') .
+ ' ORDER BY ' . $db->quoteName('term') . ' DESC'
+ );
+ $db->execute();
+
+ // Mark afterMapping in the profiler.
+ static::$profiler ? static::$profiler->mark('afterMapping') : null;
+
+ // Update the signature.
+ $object = serialize($item);
+ $query->clear()
+ ->update($db->quoteName('#__finder_links'))
+ ->set($db->quoteName('md5sum') . ' = :md5sum')
+ ->set($db->quoteName('object') . ' = :object')
+ ->where($db->quoteName('link_id') . ' = :linkid')
+ ->bind(':md5sum', $curSig)
+ ->bind(':object', $object, ParameterType::LARGE_OBJECT)
+ ->bind(':linkid', $linkId, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Mark afterSigning in the profiler.
+ static::$profiler ? static::$profiler->mark('afterSigning') : null;
+
+ // Truncate the tokens tables.
+ $db->truncateTable('#__finder_tokens');
+
+ // Truncate the tokens aggregate table.
+ $db->truncateTable('#__finder_tokens_aggregate');
+
+ // Toggle the token tables back to memory tables.
+ $this->toggleTables(true);
+
+ // Mark afterTruncating in the profiler.
+ static::$profiler ? static::$profiler->mark('afterTruncating') : null;
+
+ // Trigger a plugin event after indexing
+ PluginHelper::importPlugin('finder');
+ Factory::getApplication()->triggerEvent('onFinderIndexAfterIndex', array($item, $linkId));
+
+ return $linkId;
+ }
+
+ /**
+ * Method to remove a link from the index.
+ *
+ * @param integer $linkId The id of the link.
+ * @param bool $removeTaxonomies Remove empty taxonomies
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ public function remove($linkId, $removeTaxonomies = true)
+ {
+ $db = $this->db;
+ $query = $db->getQuery(true);
+ $linkId = (int) $linkId;
+
+ // Update the link counts for the terms.
+ $query->clear()
+ ->update($db->quoteName('#__finder_terms', 't'))
+ ->join('INNER', $db->quoteName('#__finder_links_terms', 'm'), $db->quoteName('m.term_id') . ' = ' . $db->quoteName('t.term_id'))
+ ->set($db->quoteName('links') . ' = ' . $db->quoteName('links') . ' - 1')
+ ->where($db->quoteName('m.link_id') . ' = :linkid')
+ ->bind(':linkid', $linkId, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+
+ // Remove all records from the mapping tables.
+ $query->clear()
+ ->delete($db->quoteName('#__finder_links_terms'))
+ ->where($db->quoteName('link_id') . ' = :linkid')
+ ->bind(':linkid', $linkId, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+
+ // Delete all orphaned terms.
+ $query->clear()
+ ->delete($db->quoteName('#__finder_terms'))
+ ->where($db->quoteName('links') . ' <= 0');
+ $db->setQuery($query)->execute();
+
+ // Delete the link from the index.
+ $query->clear()
+ ->delete($db->quoteName('#__finder_links'))
+ ->where($db->quoteName('link_id') . ' = :linkid')
+ ->bind(':linkid', $linkId, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+
+ // Remove the taxonomy maps.
+ Taxonomy::removeMaps($linkId);
+
+ // Remove the orphaned taxonomy nodes.
+ if ($removeTaxonomies) {
+ Taxonomy::removeOrphanNodes();
+ }
+
+ PluginHelper::importPlugin('finder');
+ Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', array($linkId));
+
+ return true;
+ }
+
+ /**
+ * Method to optimize the index. We use this method to remove unused terms
+ * and any other optimizations that might be necessary.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ public function optimize()
+ {
+ // Get the database object.
+ $db = $this->db;
+ $serverType = strtolower($db->getServerType());
+ $query = $db->getQuery(true);
+
+ // Delete all orphaned terms.
+ $query->delete($db->quoteName('#__finder_terms'))
+ ->where($db->quoteName('links') . ' <= 0');
+ $db->setQuery($query);
+ $db->execute();
+
+ // Delete all broken links. (Links missing the object)
+ $query = $db->getQuery(true)
+ ->delete('#__finder_links')
+ ->where($db->quoteName('object') . ' = ' . $db->quote(''));
+ $db->setQuery($query);
+ $db->execute();
+
+ // Delete all orphaned mappings of terms to links
+ $query2 = $db->getQuery(true)
+ ->select($db->quoteName('link_id'))
+ ->from($db->quoteName('#__finder_links'));
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__finder_links_terms'))
+ ->where($db->quoteName('link_id') . ' NOT IN (' . $query2 . ')');
+ $db->setQuery($query);
+ $db->execute();
+
+ // Delete all orphaned terms
+ $query2 = $db->getQuery(true)
+ ->select($db->quoteName('term_id'))
+ ->from($db->quoteName('#__finder_links_terms'));
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__finder_terms'))
+ ->where($db->quoteName('term_id') . ' NOT IN (' . $query2 . ')');
+ $db->setQuery($query);
+ $db->execute();
+
+ // Delete all orphaned taxonomies
+ Taxonomy::removeOrphanMaps();
+ Taxonomy::removeOrphanNodes();
+
+ // Optimize the tables.
+ $tables = [
+ '#__finder_links',
+ '#__finder_links_terms',
+ '#__finder_filters',
+ '#__finder_terms_common',
+ '#__finder_types',
+ '#__finder_taxonomy_map',
+ '#__finder_taxonomy'
+ ];
+
+ foreach ($tables as $table) {
+ if ($serverType == 'mysql') {
+ $db->setQuery('OPTIMIZE TABLE ' . $db->quoteName($table));
+ $db->execute();
+ } else {
+ $db->setQuery('VACUUM ' . $db->quoteName($table));
+ $db->execute();
+ $db->setQuery('REINDEX TABLE ' . $db->quoteName($table));
+ $db->execute();
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to get a content item's signature.
+ *
+ * @param object $item The content item to index.
+ *
+ * @return string The content item's signature.
+ *
+ * @since 2.5
+ */
+ protected static function getSignature($item)
+ {
+ // Get the indexer state.
+ $state = static::getState();
+
+ // Get the relevant configuration variables.
+ $config = array(
+ $state->weights,
+ $state->options->get('stem', 1),
+ $state->options->get('stemmer', 'porter_en')
+ );
+
+ return md5(serialize(array($item, $config)));
+ }
+
+ /**
+ * Method to parse input, tokenize it, and then add it to the database.
+ *
+ * @param mixed $input String or resource to use as input. A resource input will automatically be chunked to conserve
+ * memory. Strings will be chunked if longer than 2K in size.
+ * @param integer $context The context of the input. See context constants.
+ * @param string $lang The language of the input.
+ * @param string $format The format of the input.
+ *
+ * @return integer The number of tokens extracted from the input.
+ *
+ * @since 2.5
+ */
+ protected function tokenizeToDb($input, $context, $lang, $format)
+ {
+ $count = 0;
+ $buffer = null;
+
+ if (empty($input)) {
+ return $count;
+ }
+
+ // If the input is a resource, batch the process out.
+ if (is_resource($input)) {
+ // Batch the process out to avoid memory limits.
+ while (!feof($input)) {
+ // Read into the buffer.
+ $buffer .= fread($input, 2048);
+
+ /*
+ * If we haven't reached the end of the file, seek to the last
+ * space character and drop whatever is after that to make sure
+ * we didn't truncate a term while reading the input.
+ */
+ if (!feof($input)) {
+ // Find the last space character.
+ $ls = strrpos($buffer, ' ');
+
+ // Adjust string based on the last space character.
+ if ($ls) {
+ // Truncate the string to the last space character.
+ $string = substr($buffer, 0, $ls);
+
+ // Adjust the buffer based on the last space for the next iteration and trim.
+ $buffer = StringHelper::trim(substr($buffer, $ls));
+ } else {
+ // No space character was found.
+ $string = $buffer;
+ }
+ } else {
+ // We've reached the end of the file, so parse whatever remains.
+ $string = $buffer;
+ }
+
+ // Parse, tokenise and add tokens to the database.
+ $count = $this->tokenizeToDbShort($string, $context, $lang, $format, $count);
+
+ unset($string);
+ }
+
+ return $count;
+ }
+
+ // Parse, tokenise and add tokens to the database.
+ $count = $this->tokenizeToDbShort($input, $context, $lang, $format, $count);
+
+ return $count;
+ }
+
+ /**
+ * Method to parse input, tokenise it, then add the tokens to the database.
+ *
+ * @param string $input String to parse, tokenise and add to database.
+ * @param integer $context The context of the input. See context constants.
+ * @param string $lang The language of the input.
+ * @param string $format The format of the input.
+ * @param integer $count The number of tokens processed so far.
+ *
+ * @return integer Cumulative number of tokens extracted from the input so far.
+ *
+ * @since 3.7.0
+ */
+ private function tokenizeToDbShort($input, $context, $lang, $format, $count)
+ {
+ // Parse the input.
+ $input = Helper::parse($input, $format);
+
+ // Check the input.
+ if (empty($input)) {
+ return $count;
+ }
+
+ // Tokenize the input.
+ $tokens = Helper::tokenize($input, $lang);
+
+ if (count($tokens) == 0) {
+ return $count;
+ }
+
+ // Add the tokens to the database.
+ $count += $this->addTokensToDb($tokens, $context);
+
+ // Check if we're approaching the memory limit of the token table.
+ if ($count > static::$state->options->get('memory_table_limit', 10000)) {
+ $this->toggleTables(false);
+ }
+
+ return $count;
+ }
+
+ /**
+ * Method to add a set of tokens to the database.
+ *
+ * @param Token[]|Token $tokens An array or single Token object.
+ * @param mixed $context The context of the tokens. See context constants. [optional]
+ *
+ * @return integer The number of tokens inserted into the database.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function addTokensToDb($tokens, $context = '')
+ {
+ static $filterCommon, $filterNumeric;
+
+ if (is_null($filterCommon)) {
+ $params = ComponentHelper::getParams('com_finder');
+ $filterCommon = $params->get('filter_commonwords', false);
+ $filterNumeric = $params->get('filter_numerics', false);
+ }
+
+ // Get the database object.
+ $db = $this->db;
+
+ $query = clone $this->addTokensToDbQueryTemplate;
+
+ // Check if a single FinderIndexerToken object was given and make it to be an array of FinderIndexerToken objects
+ $tokens = is_array($tokens) ? $tokens : array($tokens);
+
+ // Count the number of token values.
+ $values = 0;
+
+ // Break into chunks of no more than 128 items
+ $chunks = array_chunk($tokens, 128);
+
+ foreach ($chunks as $tokens) {
+ $query->clear('values');
+
+ foreach ($tokens as $token) {
+ // Database size for a term field
+ if ($token->length > 75) {
+ continue;
+ }
+
+ if ($filterCommon && $token->common) {
+ continue;
+ }
+
+ if ($filterNumeric && $token->numeric) {
+ continue;
+ }
+
+ $query->values(
+ $db->quote($token->term) . ', '
+ . $db->quote($token->stem) . ', '
+ . (int) $token->common . ', '
+ . (int) $token->phrase . ', '
+ . $db->quote($token->weight) . ', '
+ . (int) $context . ', '
+ . $db->quote($token->language)
+ );
+ ++$values;
+ }
+
+ // Only execute the query if there are tokens to insert
+ if ($query->values !== null) {
+ $db->setQuery($query)->execute();
+ }
+
+ // Check if we're approaching the memory limit of the token table.
+ if ($values > static::$state->options->get('memory_table_limit', 10000)) {
+ $this->toggleTables(false);
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Method to switch the token tables from Memory tables to Disk tables
+ * when they are close to running out of memory.
+ * Since this is not supported/implemented in all DB-drivers, the default is a stub method, which simply returns true.
+ *
+ * @param boolean $memory Flag to control how they should be toggled.
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ * @throws Exception on database error.
+ */
+ protected function toggleTables($memory)
+ {
+ if (strtolower($this->db->getServerType()) != 'mysql') {
+ return true;
+ }
+
+ static $state;
+
+ // Get the database adapter.
+ $db = $this->db;
+
+ // Check if we are setting the tables to the Memory engine.
+ if ($memory === true && $state !== true) {
+ // Set the tokens table to Memory.
+ $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens') . ' ENGINE = MEMORY');
+ $db->execute();
+
+ // Set the tokens aggregate table to Memory.
+ $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens_aggregate') . ' ENGINE = MEMORY');
+ $db->execute();
+
+ // Set the internal state.
+ $state = $memory;
+ } elseif ($memory === false && $state !== false) {
+ // We must be setting the tables to the InnoDB engine.
+ // Set the tokens table to InnoDB.
+ $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens') . ' ENGINE = INNODB');
+ $db->execute();
+
+ // Set the tokens aggregate table to InnoDB.
+ $db->setQuery('ALTER TABLE ' . $db->quoteName('#__finder_tokens_aggregate') . ' ENGINE = INNODB');
+ $db->execute();
+
+ // Set the internal state.
+ $state = $memory;
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Language.php b/administrator/components/com_finder/src/Indexer/Language.php
index 6f78889df6cf2..4b87c8d7aaec5 100644
--- a/administrator/components/com_finder/src/Indexer/Language.php
+++ b/administrator/components/com_finder/src/Indexer/Language.php
@@ -1,4 +1,5 @@
language = $locale;
- }
-
- // Use our generic language handler if no language is set
- if ($this->language === null)
- {
- $this->language = '*';
- }
-
- try
- {
- $this->stemmer = StemmerFactory::create($this->language);
- }
- catch (NotFoundException $e)
- {
- // We don't have a stemmer for the language
- }
- }
-
- /**
- * Method to get a language support object.
- *
- * @param string $language The language of the support object.
- *
- * @return Language A Language instance.
- *
- * @since 4.0.0
- */
- public static function getInstance($language)
- {
- if (isset(self::$instances[$language]))
- {
- return self::$instances[$language];
- }
-
- $locale = '*';
-
- if ($language !== '*')
- {
- $locale = Helper::getPrimaryLanguage($language);
- $class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Language\\' . ucfirst($locale);
-
- if (class_exists($class))
- {
- self::$instances[$language] = new $class;
-
- return self::$instances[$language];
- }
- }
-
- self::$instances[$language] = new self($locale);
-
- return self::$instances[$language];
- }
-
- /**
- * Method to tokenise a text string.
- *
- * @param string $input The input to tokenise.
- *
- * @return array An array of term strings.
- *
- * @since 4.0.0
- */
- public function tokenise($input)
- {
- $quotes = html_entity_decode('‘’'', ENT_QUOTES, 'UTF-8');
-
- /*
- * Parsing the string input into terms is a multi-step process.
- *
- * Regexes:
- * 1. Remove everything except letters, numbers, quotes, apostrophe, plus, dash, period, and comma.
- * 2. Remove plus, dash, period, and comma characters located before letter characters.
- * 3. Remove plus, dash, period, and comma characters located after other characters.
- * 4. Remove plus, period, and comma characters enclosed in alphabetical characters. Ungreedy.
- * 5. Remove orphaned apostrophe, plus, dash, period, and comma characters.
- * 6. Remove orphaned quote characters.
- * 7. Replace the assorted single quotation marks with the ASCII standard single quotation.
- * 8. Remove multiple space characters and replaces with a single space.
- */
- $input = StringHelper::strtolower($input);
- $input = preg_replace('#[^\pL\pM\pN\p{Pi}\p{Pf}\'+-.,]+#mui', ' ', $input);
- $input = preg_replace('#(^|\s)[+-.,]+([\pL\pM]+)#mui', ' $1', $input);
- $input = preg_replace('#([\pL\pM\pN]+)[+-.,]+(\s|$)#mui', '$1 ', $input);
- $input = preg_replace('#([\pL\pM]+)[+.,]+([\pL\pM]+)#muiU', '$1 $2', $input);
- $input = preg_replace('#(^|\s)[\'+-.,]+(\s|$)#mui', ' ', $input);
- $input = preg_replace('#(^|\s)[\p{Pi}\p{Pf}]+(\s|$)#mui', ' ', $input);
- $input = preg_replace('#[' . $quotes . ']+#mui', '\'', $input);
- $input = preg_replace('#\s+#mui', ' ', $input);
- $input = trim($input);
-
- // Explode the normalized string to get the terms.
- $terms = explode(' ', $input);
-
- return $terms;
- }
-
- /**
- * Method to stem a token.
- *
- * @param string $token The token to stem.
- *
- * @return string The stemmed token.
- *
- * @since 4.0.0
- */
- public function stem($token)
- {
- if ($this->stemmer !== null)
- {
- return $this->stemmer->stem($token);
- }
-
- return $token;
- }
+ /**
+ * Language support instances container.
+ *
+ * @var Language[]
+ * @since 4.0.0
+ */
+ protected static $instances = array();
+
+ /**
+ * Language locale of the class
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ public $language;
+
+ /**
+ * Spacer to use between terms
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ public $spacer = ' ';
+
+ /**
+ * The stemmer object.
+ *
+ * @var Stemmer
+ * @since 4.0.0
+ */
+ protected $stemmer = null;
+
+ /**
+ * Method to construct the language object.
+ *
+ * @since 4.0.0
+ */
+ public function __construct($locale = null)
+ {
+ if ($locale !== null) {
+ $this->language = $locale;
+ }
+
+ // Use our generic language handler if no language is set
+ if ($this->language === null) {
+ $this->language = '*';
+ }
+
+ try {
+ $this->stemmer = StemmerFactory::create($this->language);
+ } catch (NotFoundException $e) {
+ // We don't have a stemmer for the language
+ }
+ }
+
+ /**
+ * Method to get a language support object.
+ *
+ * @param string $language The language of the support object.
+ *
+ * @return Language A Language instance.
+ *
+ * @since 4.0.0
+ */
+ public static function getInstance($language)
+ {
+ if (isset(self::$instances[$language])) {
+ return self::$instances[$language];
+ }
+
+ $locale = '*';
+
+ if ($language !== '*') {
+ $locale = Helper::getPrimaryLanguage($language);
+ $class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Language\\' . ucfirst($locale);
+
+ if (class_exists($class)) {
+ self::$instances[$language] = new $class();
+
+ return self::$instances[$language];
+ }
+ }
+
+ self::$instances[$language] = new self($locale);
+
+ return self::$instances[$language];
+ }
+
+ /**
+ * Method to tokenise a text string.
+ *
+ * @param string $input The input to tokenise.
+ *
+ * @return array An array of term strings.
+ *
+ * @since 4.0.0
+ */
+ public function tokenise($input)
+ {
+ $quotes = html_entity_decode('‘’'', ENT_QUOTES, 'UTF-8');
+
+ /*
+ * Parsing the string input into terms is a multi-step process.
+ *
+ * Regexes:
+ * 1. Remove everything except letters, numbers, quotes, apostrophe, plus, dash, period, and comma.
+ * 2. Remove plus, dash, period, and comma characters located before letter characters.
+ * 3. Remove plus, dash, period, and comma characters located after other characters.
+ * 4. Remove plus, period, and comma characters enclosed in alphabetical characters. Ungreedy.
+ * 5. Remove orphaned apostrophe, plus, dash, period, and comma characters.
+ * 6. Remove orphaned quote characters.
+ * 7. Replace the assorted single quotation marks with the ASCII standard single quotation.
+ * 8. Remove multiple space characters and replaces with a single space.
+ */
+ $input = StringHelper::strtolower($input);
+ $input = preg_replace('#[^\pL\pM\pN\p{Pi}\p{Pf}\'+-.,]+#mui', ' ', $input);
+ $input = preg_replace('#(^|\s)[+-.,]+([\pL\pM]+)#mui', ' $1', $input);
+ $input = preg_replace('#([\pL\pM\pN]+)[+-.,]+(\s|$)#mui', '$1 ', $input);
+ $input = preg_replace('#([\pL\pM]+)[+.,]+([\pL\pM]+)#muiU', '$1 $2', $input);
+ $input = preg_replace('#(^|\s)[\'+-.,]+(\s|$)#mui', ' ', $input);
+ $input = preg_replace('#(^|\s)[\p{Pi}\p{Pf}]+(\s|$)#mui', ' ', $input);
+ $input = preg_replace('#[' . $quotes . ']+#mui', '\'', $input);
+ $input = preg_replace('#\s+#mui', ' ', $input);
+ $input = trim($input);
+
+ // Explode the normalized string to get the terms.
+ $terms = explode(' ', $input);
+
+ return $terms;
+ }
+
+ /**
+ * Method to stem a token.
+ *
+ * @param string $token The token to stem.
+ *
+ * @return string The stemmed token.
+ *
+ * @since 4.0.0
+ */
+ public function stem($token)
+ {
+ if ($this->stemmer !== null) {
+ return $this->stemmer->stem($token);
+ }
+
+ return $token;
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Language/El.php b/administrator/components/com_finder/src/Indexer/Language/El.php
index 895feecba0c2d..25385cce066db 100644
--- a/administrator/components/com_finder/src/Indexer/Language/El.php
+++ b/administrator/components/com_finder/src/Indexer/Language/El.php
@@ -1,4 +1,5 @@
toUpperCase($token, $wCase);
-
- // Stop-word removal
- $stop_words = '/^(ΕΚΟ|ΑΒΑ|ΑΓΑ|ΑΓΗ|ΑΓΩ|ΑΔΗ|ΑΔΩ|ΑΕ|ΑΕΙ|ΑΘΩ|ΑΙ|ΑΙΚ|ΑΚΗ|ΑΚΟΜΑ|ΑΚΟΜΗ|ΑΚΡΙΒΩΣ|ΑΛΑ|ΑΛΗΘΕΙΑ|ΑΛΗΘΙΝΑ|ΑΛΛΑΧΟΥ|ΑΛΛΙΩΣ|ΑΛΛΙΩΤΙΚΑ|'
- . 'ΑΛΛΟΙΩΣ|ΑΛΛΟΙΩΤΙΚΑ|ΑΛΛΟΤΕ|ΑΛΤ|ΑΛΩ|ΑΜΑ|ΑΜΕ|ΑΜΕΣΑ|ΑΜΕΣΩΣ|ΑΜΩ|ΑΝ|ΑΝΑ|ΑΝΑΜΕΣΑ|ΑΝΑΜΕΤΑΞΥ|ΑΝΕΥ|ΑΝΤΙ|ΑΝΤΙΠΕΡΑ|ΑΝΤΙΣ|ΑΝΩ|ΑΝΩΤΕΡΩ|ΑΞΑΦΝΑ|'
- . 'ΑΠ|ΑΠΕΝΑΝΤΙ|ΑΠΟ|ΑΠΟΨΕ|ΑΠΩ|ΑΡΑ|ΑΡΑΓΕ|ΑΡΕ|ΑΡΚ|ΑΡΚΕΤΑ|ΑΡΛ|ΑΡΜ|ΑΡΤ|ΑΡΥ|ΑΡΩ|ΑΣ|ΑΣΑ|ΑΣΟ|ΑΤΑ|ΑΤΕ|ΑΤΗ|ΑΤΙ|ΑΤΜ|ΑΤΟ|ΑΥΡΙΟ|ΑΦΗ|ΑΦΟΤΟΥ|ΑΦΟΥ|'
- . 'ΑΧ|ΑΧΕ|ΑΧΟ|ΑΨΑ|ΑΨΕ|ΑΨΗ|ΑΨΥ|ΑΩΕ|ΑΩΟ|ΒΑΝ|ΒΑΤ|ΒΑΧ|ΒΕΑ|ΒΕΒΑΙΟΤΑΤΑ|ΒΗΞ|ΒΙΑ|ΒΙΕ|ΒΙΗ|ΒΙΟ|ΒΟΗ|ΒΟΩ|ΒΡΕ|ΓΑ|ΓΑΒ|ΓΑΡ|ΓΕΝ|ΓΕΣ||ΓΗ|ΓΗΝ|ΓΙ|ΓΙΑ|'
- . 'ΓΙΕ|ΓΙΝ|ΓΙΟ|ΓΚΙ|ΓΙΑΤΙ|ΓΚΥ|ΓΟΗ|ΓΟΟ|ΓΡΗΓΟΡΑ|ΓΡΙ|ΓΡΥ|ΓΥΗ|ΓΥΡΩ|ΔΑ|ΔΕ|ΔΕΗ|ΔΕΙ|ΔΕΝ|ΔΕΣ|ΔΗ|ΔΗΘΕΝ|ΔΗΛΑΔΗ|ΔΗΩ|ΔΙ|ΔΙΑ|ΔΙΑΡΚΩΣ|ΔΙΟΛΟΥ|ΔΙΣ|'
- . 'ΔΙΧΩΣ|ΔΟΛ|ΔΟΝ|ΔΡΑ|ΔΡΥ|ΔΡΧ|ΔΥΕ|ΔΥΟ|ΔΩ|ΕΑΜ|ΕΑΝ|ΕΑΡ|ΕΘΗ|ΕΙ|ΕΙΔΕΜΗ|ΕΙΘΕ|ΕΙΜΑΙ|ΕΙΜΑΣΤΕ|ΕΙΝΑΙ|ΕΙΣ|ΕΙΣΑΙ|ΕΙΣΑΣΤΕ|ΕΙΣΤΕ|ΕΙΤΕ|ΕΙΧΑ|ΕΙΧΑΜΕ|'
- . 'ΕΙΧΑΝ|ΕΙΧΑΤΕ|ΕΙΧΕ|ΕΙΧΕΣ|ΕΚ|ΕΚΕΙ|ΕΛΑ|ΕΛΙ|ΕΜΠ|ΕΝ|ΕΝΤΕΛΩΣ|ΕΝΤΟΣ|ΕΝΤΩΜΕΤΑΞΥ|ΕΝΩ|ΕΞ|ΕΞΑΦΝΑ|ΕΞΙ|ΕΞΙΣΟΥ|ΕΞΩ|ΕΟΚ|ΕΠΑΝΩ|ΕΠΕΙΔΗ|ΕΠΕΙΤΑ|ΕΠΗ|'
- . 'ΕΠΙ|ΕΠΙΣΗΣ|ΕΠΟΜΕΝΩΣ|ΕΡΑ|ΕΣ|ΕΣΑΣ|ΕΣΕ|ΕΣΕΙΣ|ΕΣΕΝΑ|ΕΣΗ|ΕΣΤΩ|ΕΣΥ|ΕΣΩ|ΕΤΙ|ΕΤΣΙ|ΕΥ|ΕΥΑ|ΕΥΓΕ|ΕΥΘΥΣ|ΕΥΤΥΧΩΣ|ΕΦΕ|ΕΦΕΞΗΣ|ΕΦΤ|ΕΧΕ|ΕΧΕΙ|'
- . 'ΕΧΕΙΣ|ΕΧΕΤΕ|ΕΧΘΕΣ|ΕΧΟΜΕ|ΕΧΟΥΜΕ|ΕΧΟΥΝ|ΕΧΤΕΣ|ΕΧΩ|ΕΩΣ|ΖΕΑ|ΖΕΗ|ΖΕΙ|ΖΕΝ|ΖΗΝ|ΖΩ|Η|ΗΔΗ|ΗΔΥ|ΗΘΗ|ΗΛΟ|ΗΜΙ|ΗΠΑ|ΗΣΑΣΤΕ|ΗΣΟΥΝ|ΗΤΑ|ΗΤΑΝ|ΗΤΑΝΕ|'
- . 'ΗΤΟΙ|ΗΤΤΟΝ|ΗΩ|ΘΑ|ΘΥΕ|ΘΩΡ|Ι|ΙΑ|ΙΒΟ|ΙΔΗ|ΙΔΙΩΣ|ΙΕ|ΙΙ|ΙΙΙ|ΙΚΑ|ΙΛΟ|ΙΜΑ|ΙΝΑ|ΙΝΩ|ΙΞΕ|ΙΞΟ|ΙΟ|ΙΟΙ|ΙΣΑ|ΙΣΑΜΕ|ΙΣΕ|ΙΣΗ|ΙΣΙΑ|ΙΣΟ|ΙΣΩΣ|ΙΩΒ|ΙΩΝ|'
- . 'ΙΩΣ|ΙΑΝ|ΚΑΘ|ΚΑΘΕ|ΚΑΘΕΤΙ|ΚΑΘΟΛΟΥ|ΚΑΘΩΣ|ΚΑΙ|ΚΑΝ|ΚΑΠΟΤΕ|ΚΑΠΟΥ|ΚΑΠΩΣ|ΚΑΤ|ΚΑΤΑ|ΚΑΤΙ|ΚΑΤΙΤΙ|ΚΑΤΟΠΙΝ|ΚΑΤΩ|ΚΑΩ|ΚΒΟ|ΚΕΑ|ΚΕΙ|ΚΕΝ|ΚΙ|ΚΙΜ|'
- . 'ΚΙΟΛΑΣ|ΚΙΤ|ΚΙΧ|ΚΚΕ|ΚΛΙΣΕ|ΚΛΠ|ΚΟΚ|ΚΟΝΤΑ|ΚΟΧ|ΚΤΛ|ΚΥΡ|ΚΥΡΙΩΣ|ΚΩ|ΚΩΝ|ΛΑ|ΛΕΑ|ΛΕΝ|ΛΕΟ|ΛΙΑ|ΛΙΓΑΚΙ|ΛΙΓΟΥΛΑΚΙ|ΛΙΓΟ|ΛΙΓΩΤΕΡΟ|ΛΙΟ|ΛΙΡ|ΛΟΓΩ|'
- . 'ΛΟΙΠΑ|ΛΟΙΠΟΝ|ΛΟΣ|ΛΣ|ΛΥΩ|ΜΑ|ΜΑΖΙ|ΜΑΚΑΡΙ|ΜΑΛΙΣΤΑ|ΜΑΛΛΟΝ|ΜΑΝ|ΜΑΞ|ΜΑΣ|ΜΑΤ|ΜΕ|ΜΕΘΑΥΡΙΟ|ΜΕΙ|ΜΕΙΟΝ|ΜΕΛ|ΜΕΛΕΙ|ΜΕΛΛΕΤΑΙ|ΜΕΜΙΑΣ|ΜΕΝ|ΜΕΣ|'
- . 'ΜΕΣΑ|ΜΕΤ|ΜΕΤΑ|ΜΕΤΑΞΥ|ΜΕΧΡΙ|ΜΗ|ΜΗΔΕ|ΜΗΝ|ΜΗΠΩΣ|ΜΗΤΕ|ΜΙ|ΜΙΞ|ΜΙΣ|ΜΜΕ|ΜΝΑ|ΜΟΒ|ΜΟΛΙΣ|ΜΟΛΟΝΟΤΙ|ΜΟΝΑΧΑ|ΜΟΝΟΜΙΑΣ|ΜΙΑ|ΜΟΥ|ΜΠΑ|ΜΠΟΡΕΙ|'
- . 'ΜΠΟΡΟΥΝ|ΜΠΡΑΒΟ|ΜΠΡΟΣ|ΜΠΩ|ΜΥ|ΜΥΑ|ΜΥΝ|ΝΑ|ΝΑΕ|ΝΑΙ|ΝΑΟ|ΝΔ|ΝΕΐ|ΝΕΑ|ΝΕΕ|ΝΕΟ|ΝΙ|ΝΙΑ|ΝΙΚ|ΝΙΛ|ΝΙΝ|ΝΙΟ|ΝΤΑ|ΝΤΕ|ΝΤΙ|ΝΤΟ|ΝΥΝ|ΝΩΕ|ΝΩΡΙΣ|ΞΑΝΑ|'
- . 'ΞΑΦΝΙΚΑ|ΞΕΩ|ΞΙ|Ο|ΟΑ|ΟΑΠ|ΟΔΟ|ΟΕ|ΟΖΟ|ΟΗΕ|ΟΙ|ΟΙΑ|ΟΙΗ|ΟΚΑ|ΟΛΟΓΥΡΑ|ΟΛΟΝΕΝ|ΟΛΟΤΕΛΑ|ΟΛΩΣΔΙΟΛΟΥ|ΟΜΩΣ|ΟΝ|ΟΝΕ|ΟΝΟ|ΟΠΑ|ΟΠΕ|ΟΠΗ|ΟΠΟ|'
- . 'ΟΠΟΙΑΔΗΠΟΤΕ|ΟΠΟΙΑΝΔΗΠΟΤΕ|ΟΠΟΙΑΣΔΗΠΟΤΕ|ΟΠΟΙΔΗΠΟΤΕ|ΟΠΟΙΕΣΔΗΠΟΤΕ|ΟΠΟΙΟΔΗΠΟΤΕ|ΟΠΟΙΟΝΔΗΠΟΤΕ|ΟΠΟΙΟΣΔΗΠΟΤΕ|ΟΠΟΙΟΥΔΗΠΟΤΕ|ΟΠΟΙΟΥΣΔΗΠΟΤΕ|'
- . 'ΟΠΟΙΩΝΔΗΠΟΤΕ|ΟΠΟΤΕΔΗΠΟΤΕ|ΟΠΟΥ|ΟΠΟΥΔΗΠΟΤΕ|ΟΠΩΣ|ΟΡΑ|ΟΡΕ|ΟΡΗ|ΟΡΟ|ΟΡΦ|ΟΡΩ|ΟΣΑ|ΟΣΑΔΗΠΟΤΕ|ΟΣΕ|ΟΣΕΣΔΗΠΟΤΕ|ΟΣΗΔΗΠΟΤΕ|ΟΣΗΝΔΗΠΟΤΕ|'
- . 'ΟΣΗΣΔΗΠΟΤΕ|ΟΣΟΔΗΠΟΤΕ|ΟΣΟΙΔΗΠΟΤΕ|ΟΣΟΝΔΗΠΟΤΕ|ΟΣΟΣΔΗΠΟΤΕ|ΟΣΟΥΔΗΠΟΤΕ|ΟΣΟΥΣΔΗΠΟΤΕ|ΟΣΩΝΔΗΠΟΤΕ|ΟΤΑΝ|ΟΤΕ|ΟΤΙ|ΟΤΙΔΗΠΟΤΕ|ΟΥ|ΟΥΔΕ|ΟΥΚ|ΟΥΣ|'
- . 'ΟΥΤΕ|ΟΥΦ|ΟΧΙ|ΟΨΑ|ΟΨΕ|ΟΨΗ|ΟΨΙ|ΟΨΟ|ΠΑ|ΠΑΛΙ|ΠΑΝ|ΠΑΝΤΟΤΕ|ΠΑΝΤΟΥ|ΠΑΝΤΩΣ|ΠΑΠ|ΠΑΡ|ΠΑΡΑ|ΠΕΙ|ΠΕΡ|ΠΕΡΑ|ΠΕΡΙ|ΠΕΡΙΠΟΥ|ΠΕΡΣΙ|ΠΕΡΥΣΙ|ΠΕΣ|ΠΙ|'
- . 'ΠΙΑ|ΠΙΘΑΝΟΝ|ΠΙΚ|ΠΙΟ|ΠΙΣΩ|ΠΙΤ|ΠΙΩ|ΠΛΑΙ|ΠΛΕΟΝ|ΠΛΗΝ|ΠΛΩ|ΠΜ|ΠΟΑ|ΠΟΕ|ΠΟΛ|ΠΟΛΥ|ΠΟΠ|ΠΟΤΕ|ΠΟΥ|ΠΟΥΘΕ|ΠΟΥΘΕΝΑ|ΠΡΕΠΕΙ|ΠΡΙ|ΠΡΙΝ|ΠΡΟ|'
- . 'ΠΡΟΚΕΙΜΕΝΟΥ|ΠΡΟΚΕΙΤΑΙ|ΠΡΟΠΕΡΣΙ|ΠΡΟΣ|ΠΡΟΤΟΥ|ΠΡΟΧΘΕΣ|ΠΡΟΧΤΕΣ|ΠΡΩΤΥΤΕΡΑ|ΠΥΑ|ΠΥΞ|ΠΥΟ|ΠΥΡ|ΠΧ|ΠΩ|ΠΩΛ|ΠΩΣ|ΡΑ|ΡΑΙ|ΡΑΠ|ΡΑΣ|ΡΕ|ΡΕΑ|ΡΕΕ|ΡΕΙ|'
- . 'ΡΗΣ|ΡΘΩ|ΡΙΟ|ΡΟ|ΡΟΐ|ΡΟΕ|ΡΟΖ|ΡΟΗ|ΡΟΘ|ΡΟΙ|ΡΟΚ|ΡΟΛ|ΡΟΝ|ΡΟΣ|ΡΟΥ|ΣΑΙ|ΣΑΝ|ΣΑΟ|ΣΑΣ|ΣΕ|ΣΕΙΣ|ΣΕΚ|ΣΕΞ|ΣΕΡ|ΣΕΤ|ΣΕΦ|ΣΗΜΕΡΑ|ΣΙ|ΣΙΑ|ΣΙΓΑ|ΣΙΚ|'
- . 'ΣΙΧ|ΣΚΙ|ΣΟΙ|ΣΟΚ|ΣΟΛ|ΣΟΝ|ΣΟΣ|ΣΟΥ|ΣΡΙ|ΣΤΑ|ΣΤΗ|ΣΤΗΝ|ΣΤΗΣ|ΣΤΙΣ|ΣΤΟ|ΣΤΟΝ|ΣΤΟΥ|ΣΤΟΥΣ|ΣΤΩΝ|ΣΥ|ΣΥΓΧΡΟΝΩΣ|ΣΥΝ|ΣΥΝΑΜΑ|ΣΥΝΕΠΩΣ|ΣΥΝΗΘΩΣ|'
- . 'ΣΧΕΔΟΝ|ΣΩΣΤΑ|ΤΑ|ΤΑΔΕ|ΤΑΚ|ΤΑΝ|ΤΑΟ|ΤΑΥ|ΤΑΧΑ|ΤΑΧΑΤΕ|ΤΕ|ΤΕΙ|ΤΕΛ|ΤΕΛΙΚΑ|ΤΕΛΙΚΩΣ|ΤΕΣ|ΤΕΤ|ΤΖΟ|ΤΗ|ΤΗΛ|ΤΗΝ|ΤΗΣ|ΤΙ|ΤΙΚ|ΤΙΜ|ΤΙΠΟΤΑ|ΤΙΠΟΤΕ|'
- . 'ΤΙΣ|ΤΝΤ|ΤΟ|ΤΟΙ|ΤΟΚ|ΤΟΜ|ΤΟΝ|ΤΟΠ|ΤΟΣ|ΤΟΣ?Ν|ΤΟΣΑ|ΤΟΣΕΣ|ΤΟΣΗ|ΤΟΣΗΝ|ΤΟΣΗΣ|ΤΟΣΟ|ΤΟΣΟΙ|ΤΟΣΟΝ|ΤΟΣΟΣ|ΤΟΣΟΥ|ΤΟΣΟΥΣ|ΤΟΤΕ|ΤΟΥ|ΤΟΥΛΑΧΙΣΤΟ|'
- . 'ΤΟΥΛΑΧΙΣΤΟΝ|ΤΟΥΣ|ΤΣ|ΤΣΑ|ΤΣΕ|ΤΥΧΟΝ|ΤΩ|ΤΩΝ|ΤΩΡΑ|ΥΑΣ|ΥΒΑ|ΥΒΟ|ΥΙΕ|ΥΙΟ|ΥΛΑ|ΥΛΗ|ΥΝΙ|ΥΠ|ΥΠΕΡ|ΥΠΟ|ΥΠΟΨΗ|ΥΠΟΨΙΝ|ΥΣΤΕΡΑ|ΥΦΗ|ΥΨΗ|ΦΑ|ΦΑΐ|ΦΑΕ|'
- . 'ΦΑΝ|ΦΑΞ|ΦΑΣ|ΦΑΩ|ΦΕΖ|ΦΕΙ|ΦΕΤΟΣ|ΦΕΥ|ΦΙ|ΦΙΛ|ΦΙΣ|ΦΟΞ|ΦΠΑ|ΦΡΙ|ΧΑ|ΧΑΗ|ΧΑΛ|ΧΑΝ|ΧΑΦ|ΧΕ|ΧΕΙ|ΧΘΕΣ|ΧΙ|ΧΙΑ|ΧΙΛ|ΧΙΟ|ΧΛΜ|ΧΜ|ΧΟΗ|ΧΟΛ|ΧΡΩ|ΧΤΕΣ|'
- . 'ΧΩΡΙΣ|ΧΩΡΙΣΤΑ|ΨΕΣ|ΨΗΛΑ|ΨΙ|ΨΙΤ|Ω|ΩΑ|ΩΑΣ|ΩΔΕ|ΩΕΣ|ΩΘΩ|ΩΜΑ|ΩΜΕ|ΩΝ|ΩΟ|ΩΟΝ|ΩΟΥ|ΩΣ|ΩΣΑΝ|ΩΣΗ|ΩΣΟΤΟΥ|ΩΣΠΟΥ|ΩΣΤΕ|ΩΣΤΟΣΟ|ΩΤΑ|ΩΧ|ΩΩΝ)$/';
-
- if (preg_match($stop_words, $token))
- {
- return $this->toLowerCase($token, $wCase);
- }
-
- // Vowels
- $v = '(Α|Ε|Η|Ι|Ο|Υ|Ω)';
-
- // Vowels without Y
- $v2 = '(Α|Ε|Η|Ι|Ο|Ω)';
-
- $test1 = true;
-
- // Step S1. 14 stems
- $re = '/^(.+?)(ΙΖΑ|ΙΖΕΣ|ΙΖΕ|ΙΖΑΜΕ|ΙΖΑΤΕ|ΙΖΑΝ|ΙΖΑΝΕ|ΙΖΩ|ΙΖΕΙΣ|ΙΖΕΙ|ΙΖΟΥΜΕ|ΙΖΕΤΕ|ΙΖΟΥΝ|ΙΖΟΥΝΕ)$/';
- $exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΠΑ|ΞΑΝΑΠΑ|ΠΑ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ)$/';
- $exceptS2 = '/^(ΜΑΡΚ|ΚΟΡΝ|ΑΜΠΑΡ|ΑΡΡ|ΒΑΘΥΡΙ|ΒΑΡΚ|Β|ΒΟΛΒΟΡ|ΓΚΡ|ΓΛΥΚΟΡ|ΓΛΥΚΥΡ|ΙΜΠ|Λ|ΛΟΥ|ΜΑΡ|Μ|ΠΡ|ΜΠΡ|ΠΟΛΥΡ|Π|Ρ|ΠΙΠΕΡΟΡ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . 'I';
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . 'IΖ';
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S2. 7 stems
- $re = '/^(.+?)(ΩΘΗΚΑ|ΩΘΗΚΕΣ|ΩΘΗΚΕ|ΩΘΗΚΑΜΕ|ΩΘΗΚΑΤΕ|ΩΘΗΚΑΝ|ΩΘΗΚΑΝΕ)$/';
- $exceptS1 = '/^(ΑΛ|ΒΙ|ΕΝ|ΥΨ|ΛΙ|ΖΩ|Σ|Χ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . 'ΩΝ';
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S3. 7 stems
- $re = '/^(.+?)(ΙΣΑ|ΙΣΕΣ|ΙΣΕ|ΙΣΑΜΕ|ΙΣΑΤΕ|ΙΣΑΝ|ΙΣΑΝΕ)$/';
- $exceptS1 = '/^(ΑΝΑΜΠΑ|ΑΘΡΟ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
- $exceptS2 = '/^(ΑΝ|ΑΦ|ΓΕ|ΓΙΓΑΝΤΟΑΦ|ΓΚΕ|ΔΗΜΟΚΡΑΤ|ΚΟΜ|ΓΚ|Μ|Π|ΠΟΥΚΑΜ|ΟΛΟ|ΛΑΡ)$/';
-
- if ($token == "ΙΣΑ")
- {
- $token = "ΙΣ";
-
- return $token;
- }
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . 'Ι';
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . 'ΙΣ';
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S4. 7 stems
- $re = '/^(.+?)(ΙΣΩ|ΙΣΕΙΣ|ΙΣΕΙ|ΙΣΟΥΜΕ|ΙΣΕΤΕ|ΙΣΟΥΝ|ΙΣΟΥΝΕ)$/';
- $exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . 'Ι';
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S5. 11 stems
- $re = '/^(.+?)(ΙΣΤΟΣ|ΙΣΤΟΥ|ΙΣΤΟ|ΙΣΤΕ|ΙΣΤΟΙ|ΙΣΤΩΝ|ΙΣΤΟΥΣ|ΙΣΤΗ|ΙΣΤΗΣ|ΙΣΤΑ|ΙΣΤΕΣ)$/';
- $exceptS1 = '/^(Μ|Π|ΑΠ|ΑΡ|ΗΔ|ΚΤ|ΣΚ|ΣΧ|ΥΨ|ΦΑ|ΧΡ|ΧΤ|ΑΚΤ|ΑΟΡ|ΑΣΧ|ΑΤΑ|ΑΧΝ|ΑΧΤ|ΓΕΜ|ΓΥΡ|ΕΜΠ|ΕΥΠ|ΕΧΘ|ΗΦΑ|ΚΑΘ|ΚΑΚ|ΚΥΛ|ΛΥΓ|ΜΑΚ|ΜΕΓ|ΤΑΧ|ΦΙΛ|ΧΩΡ)$/';
- $exceptS2 = '/^(ΔΑΝΕ|ΣΥΝΑΘΡΟ|ΚΛΕ|ΣΕ|ΕΣΩΚΛΕ|ΑΣΕ|ΠΛΕ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . 'ΙΣΤ';
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . 'Ι';
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S6. 6 stems
- $re = '/^(.+?)(ΙΣΜΟ|ΙΣΜΟΙ|ΙΣΜΟΣ|ΙΣΜΟΥ|ΙΣΜΟΥΣ|ΙΣΜΩΝ)$/';
- $exceptS1 = '/^(ΑΓΝΩΣΤΙΚ|ΑΤΟΜΙΚ|ΓΝΩΣΤΙΚ|ΕΘΝΙΚ|ΕΚΛΕΚΤΙΚ|ΣΚΕΠΤΙΚ|ΤΟΠΙΚ)$/';
- $exceptS2 = '/^(ΣΕ|ΜΕΤΑΣΕ|ΜΙΚΡΟΣΕ|ΕΓΚΛΕ|ΑΠΟΚΛΕ)$/';
- $exceptS3 = '/^(ΔΑΝΕ|ΑΝΤΙΔΑΝΕ)$/';
- $exceptS4 = '/^(ΑΛΕΞΑΝΔΡΙΝ|ΒΥΖΑΝΤΙΝ|ΘΕΑΤΡΙΝ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = str_replace('ΙΚ', "", $token);
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . "ΙΣΜ";
- }
-
- if (preg_match($exceptS3, $token))
- {
- $token = $token . "Ι";
- }
-
- if (preg_match($exceptS4, $token))
- {
- $token = str_replace('ΙΝ', "", $token);
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S7. 4 stems
- $re = '/^(.+?)(ΑΡΑΚΙ|ΑΡΑΚΙΑ|ΟΥΔΑΚΙ|ΟΥΔΑΚΙΑ)$/';
- $exceptS1 = '/^(Σ|Χ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . "AΡΑΚ";
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S8. 8 stems
- $re = '/^(.+?)(ΑΚΙ|ΑΚΙΑ|ΙΤΣΑ|ΙΤΣΑΣ|ΙΤΣΕΣ|ΙΤΣΩΝ|ΑΡΑΚΙ|ΑΡΑΚΙΑ)$/';
- $exceptS1 = '/^(ΑΝΘΡ|ΒΑΜΒ|ΒΡ|ΚΑΙΜ|ΚΟΝ|ΚΟΡ|ΛΑΒΡ|ΛΟΥΛ|ΜΕΡ|ΜΟΥΣΤ|ΝΑΓΚΑΣ|ΠΛ|Ρ|ΡΥ|Σ|ΣΚ|ΣΟΚ|ΣΠΑΝ|ΤΖ|ΦΑΡΜ|Χ|'
- . 'ΚΑΠΑΚ|ΑΛΙΣΦ|ΑΜΒΡ|ΑΝΘΡ|Κ|ΦΥΛ|ΚΑΤΡΑΠ|ΚΛΙΜ|ΜΑΛ|ΣΛΟΒ|Φ|ΣΦ|ΤΣΕΧΟΣΛΟΒ)$/';
- $exceptS2 = '/^(Β|ΒΑΛ|ΓΙΑΝ|ΓΛ|Ζ|ΗΓΟΥΜΕΝ|ΚΑΡΔ|ΚΟΝ|ΜΑΚΡΥΝ|ΝΥΦ|ΠΑΤΕΡ|Π|ΣΚ|ΤΟΣ|ΤΡΙΠΟΛ)$/';
-
- // For words like ΠΛΟΥΣΙΟΚΟΡΙΤΣΑ, ΠΑΛΙΟΚΟΡΙΤΣΑ etc
- $exceptS3 = '/(ΚΟΡ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . "ΑΚ";
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . "ΙΤΣ";
- }
-
- if (preg_match($exceptS3, $token))
- {
- $token = $token . "ΙΤΣ";
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S9. 3 stems
- $re = '/^(.+?)(ΙΔΙΟ|ΙΔΙΑ|ΙΔΙΩΝ)$/';
- $exceptS1 = '/^(ΑΙΦΝ|ΙΡ|ΟΛΟ|ΨΑΛ)$/';
- $exceptS2 = '/(Ε|ΠΑΙΧΝ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . "ΙΔ";
- }
-
- if (preg_match($exceptS2, $token))
- {
- $token = $token . "ΙΔ";
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step S10. 4 stems
- $re = '/^(.+?)(ΙΣΚΟΣ|ΙΣΚΟΥ|ΙΣΚΟ|ΙΣΚΕ)$/';
- $exceptS1 = '/^(Δ|ΙΒ|ΜΗΝ|Ρ|ΦΡΑΓΚ|ΛΥΚ|ΟΒΕΛ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
-
- if (preg_match($exceptS1, $token))
- {
- $token = $token . "ΙΣΚ";
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- // Step 1
- // step1list is used in Step 1. 41 stems
- $step1list = Array();
- $step1list["ΦΑΓΙΑ"] = "ΦΑ";
- $step1list["ΦΑΓΙΟΥ"] = "ΦΑ";
- $step1list["ΦΑΓΙΩΝ"] = "ΦΑ";
- $step1list["ΣΚΑΓΙΑ"] = "ΣΚΑ";
- $step1list["ΣΚΑΓΙΟΥ"] = "ΣΚΑ";
- $step1list["ΣΚΑΓΙΩΝ"] = "ΣΚΑ";
- $step1list["ΟΛΟΓΙΟΥ"] = "ΟΛΟ";
- $step1list["ΟΛΟΓΙΑ"] = "ΟΛΟ";
- $step1list["ΟΛΟΓΙΩΝ"] = "ΟΛΟ";
- $step1list["ΣΟΓΙΟΥ"] = "ΣΟ";
- $step1list["ΣΟΓΙΑ"] = "ΣΟ";
- $step1list["ΣΟΓΙΩΝ"] = "ΣΟ";
- $step1list["ΤΑΤΟΓΙΑ"] = "ΤΑΤΟ";
- $step1list["ΤΑΤΟΓΙΟΥ"] = "ΤΑΤΟ";
- $step1list["ΤΑΤΟΓΙΩΝ"] = "ΤΑΤΟ";
- $step1list["ΚΡΕΑΣ"] = "ΚΡΕ";
- $step1list["ΚΡΕΑΤΟΣ"] = "ΚΡΕ";
- $step1list["ΚΡΕΑΤΑ"] = "ΚΡΕ";
- $step1list["ΚΡΕΑΤΩΝ"] = "ΚΡΕ";
- $step1list["ΠΕΡΑΣ"] = "ΠΕΡ";
- $step1list["ΠΕΡΑΤΟΣ"] = "ΠΕΡ";
-
- // Added by Spyros. Also at $re in step1
- $step1list["ΠΕΡΑΤΗ"] = "ΠΕΡ";
- $step1list["ΠΕΡΑΤΑ"] = "ΠΕΡ";
- $step1list["ΠΕΡΑΤΩΝ"] = "ΠΕΡ";
- $step1list["ΤΕΡΑΣ"] = "ΤΕΡ";
- $step1list["ΤΕΡΑΤΟΣ"] = "ΤΕΡ";
- $step1list["ΤΕΡΑΤΑ"] = "ΤΕΡ";
- $step1list["ΤΕΡΑΤΩΝ"] = "ΤΕΡ";
- $step1list["ΦΩΣ"] = "ΦΩ";
- $step1list["ΦΩΤΟΣ"] = "ΦΩ";
- $step1list["ΦΩΤΑ"] = "ΦΩ";
- $step1list["ΦΩΤΩΝ"] = "ΦΩ";
- $step1list["ΚΑΘΕΣΤΩΣ"] = "ΚΑΘΕΣΤ";
- $step1list["ΚΑΘΕΣΤΩΤΟΣ"] = "ΚΑΘΕΣΤ";
- $step1list["ΚΑΘΕΣΤΩΤΑ"] = "ΚΑΘΕΣΤ";
- $step1list["ΚΑΘΕΣΤΩΤΩΝ"] = "ΚΑΘΕΣΤ";
- $step1list["ΓΕΓΟΝΟΣ"] = "ΓΕΓΟΝ";
- $step1list["ΓΕΓΟΝΟΤΟΣ"] = "ΓΕΓΟΝ";
- $step1list["ΓΕΓΟΝΟΤΑ"] = "ΓΕΓΟΝ";
- $step1list["ΓΕΓΟΝΟΤΩΝ"] = "ΓΕΓΟΝ";
-
- $re = '/(.*)(ΦΑΓΙΑ|ΦΑΓΙΟΥ|ΦΑΓΙΩΝ|ΣΚΑΓΙΑ|ΣΚΑΓΙΟΥ|ΣΚΑΓΙΩΝ|ΟΛΟΓΙΟΥ|ΟΛΟΓΙΑ|ΟΛΟΓΙΩΝ|ΣΟΓΙΟΥ|ΣΟΓΙΑ|ΣΟΓΙΩΝ|ΤΑΤΟΓΙΑ|ΤΑΤΟΓΙΟΥ|ΤΑΤΟΓΙΩΝ|ΚΡΕΑΣ|ΚΡΕΑΤΟΣ|'
- . 'ΚΡΕΑΤΑ|ΚΡΕΑΤΩΝ|ΠΕΡΑΣ|ΠΕΡΑΤΟΣ|ΠΕΡΑΤΗ|ΠΕΡΑΤΑ|ΠΕΡΑΤΩΝ|ΤΕΡΑΣ|ΤΕΡΑΤΟΣ|ΤΕΡΑΤΑ|ΤΕΡΑΤΩΝ|ΦΩΣ|ΦΩΤΟΣ|ΦΩΤΑ|ΦΩΤΩΝ|ΚΑΘΕΣΤΩΣ|ΚΑΘΕΣΤΩΤΟΣ|'
- . 'ΚΑΘΕΣΤΩΤΑ|ΚΑΘΕΣΤΩΤΩΝ|ΓΕΓΟΝΟΣ|ΓΕΓΟΝΟΤΟΣ|ΓΕΓΟΝΟΤΑ|ΓΕΓΟΝΟΤΩΝ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $stem = $match[1];
- $suffix = $match[2];
- $token = $stem . (array_key_exists($suffix, $step1list) ? $step1list[$suffix] : '');
- $test1 = false;
- }
-
- // Step 2a. 2 stems
- $re = '/^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1];
- $re = '/(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ)$/';
-
- if (!preg_match($re, $token))
- {
- $token = $token . "ΑΔ";
- }
- }
-
- // Step 2b. 2 stems
- $re = '/^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $exept2 = '/(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/';
-
- if (preg_match($exept2, $token))
- {
- $token = $token . 'ΕΔ';
- }
- }
-
- // Step 2c
- $re = '/^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
-
- $exept3 = '/(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/';
-
- if (preg_match($exept3, $token))
- {
- $token = $token . 'ΟΥΔ';
- }
- }
-
- // Step 2d
- $re = '/^(.+?)(ΕΩΣ|ΕΩΝ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept4 = '/^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ)$/';
-
- if (preg_match($exept4, $token))
- {
- $token = $token . 'Ε';
- }
- }
-
- // Step 3
- $re = '/^(.+?)(ΙΑ|ΙΟΥ|ΙΩΝ)$/';
-
- if (preg_match($re, $token, $fp))
- {
- $stem = $fp[1];
- $token = $stem;
- $re = '/' . $v . '$/';
- $test1 = false;
-
- if (preg_match($re, $token))
- {
- $token = $stem . 'Ι';
- }
- }
-
- // Step 4
- $re = '/^(.+?)(ΙΚΑ|ΙΚΟ|ΙΚΟΥ|ΙΚΩΝ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $re = '/' . $v . '$/';
- $exept5 = '/^(ΑΛ|ΑΔ|ΕΝΔ|ΑΜΑΝ|ΑΜΜΟΧΑΛ|ΗΘ|ΑΝΗΘ|ΑΝΤΙΔ|ΦΥΣ|ΒΡΩΜ|ΓΕΡ|ΕΞΩΔ|ΚΑΛΠ|ΚΑΛΛΙΝ|ΚΑΤΑΔ|ΜΟΥΛ|ΜΠΑΝ|ΜΠΑΓΙΑΤ|ΜΠΟΛ|ΜΠΟΣ|ΝΙΤ|ΞΙΚ|ΣΥΝΟΜΗΛ|ΠΕΤΣ|'
- . 'ΠΙΤΣ|ΠΙΚΑΝΤ|ΠΛΙΑΤΣ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΥΝΑΔ|ΤΣΑΜ|ΥΠΟΔ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΧΑΣ)$/';
-
- if (preg_match($re, $token) || preg_match($exept5, $token))
- {
- $token = $token . 'ΙΚ';
- }
- }
-
- // Step 5a
- $re = '/^(.+?)(ΑΜΕ)$/';
- $re2 = '/^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/';
-
- if ($token == "ΑΓΑΜΕ")
- {
- $token = "ΑΓΑΜ";
- }
-
- if (preg_match($re2, $token))
- {
- preg_match($re2, $token, $match);
- $token = $match[1];
- $test1 = false;
- }
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept6 = '/^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/';
-
- if (preg_match($exept6, $token))
- {
- $token = $token . "ΑΜ";
- }
- }
-
- // Step 5b
- $re2 = '/^(.+?)(ΑΝΕ)$/';
- $re3 = '/^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/';
-
- if (preg_match($re3, $token))
- {
- preg_match($re3, $token, $match);
- $token = $match[1];
- $test1 = false;
- $re3 = '/^(ΤΡ|ΤΣ)$/';
-
- if (preg_match($re3, $token))
- {
- $token = $token . "ΑΓΑΝ";
- }
- }
-
- if (preg_match($re2, $token))
- {
- preg_match($re2, $token, $match);
- $token = $match[1];
- $test1 = false;
- $re2 = '/' . $v2 . '$/';
- $exept7 = '/^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜ|Ν|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|'
- . 'ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|'
- . 'ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|'
- . 'ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|'
- . 'ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/';
-
- if (preg_match($re2, $token) || preg_match($exept7, $token))
- {
- $token = $token . "ΑΝ";
- }
- }
-
- // Step 5c
- $re3 = '/^(.+?)(ΕΤΕ)$/';
- $re4 = '/^(.+?)(ΗΣΕΤΕ)$/';
-
- if (preg_match($re4, $token))
- {
- preg_match($re4, $token, $match);
- $token = $match[1];
- $test1 = false;
- }
-
- if (preg_match($re3, $token))
- {
- preg_match($re3, $token, $match);
- $token = $match[1];
- $test1 = false;
- $re3 = '/' . $v2 . '$/';
- $exept8 = '/(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/';
- $exept9 = '/^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/';
-
- if (preg_match($re3, $token) || preg_match($exept8, $token) || preg_match($exept9, $token))
- {
- $token = $token . "ΕΤ";
- }
- }
-
- // Step 5d
- $re = '/^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept10 = '/^(ΑΡΧ)$/';
- $exept11 = '/(ΚΡΕ)$/';
-
- if (preg_match($exept10, $token))
- {
- $token = $token . "ΟΝΤ";
- }
-
- if (preg_match($exept11, $token))
- {
- $token = $token . "ΩΝΤ";
- }
- }
-
- // Step 5e
- $re = '/^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept11 = '/^(ΟΝ)$/';
-
- if (preg_match($exept11, $token))
- {
- $token = $token . "ΟΜΑΣΤ";
- }
- }
-
- // Step 5f
- $re = '/^(.+?)(ΕΣΤΕ)$/';
- $re2 = '/^(.+?)(ΙΕΣΤΕ)$/';
-
- if (preg_match($re2, $token))
- {
- preg_match($re2, $token, $match);
- $token = $match[1];
- $test1 = false;
- $re2 = '/^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/';
-
- if (preg_match($re2, $token))
- {
- $token = $token . "ΙΕΣΤ";
- }
- }
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept12 = '/^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΑΡ|ΠΡΟ|ΝΙΣ)$/';
-
- if (preg_match($exept12, $token))
- {
- $token = $token . "ΕΣΤ";
- }
- }
-
- // Step 5g
- $re = '/^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/';
- $re2 = '/^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/';
-
- if (preg_match($re2, $token))
- {
- preg_match($re2, $token, $match);
- $token = $match[1];
- $test1 = false;
- }
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept13 = '/(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/';
- $exept14 = '/^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ|)$/';
-
- if (preg_match($exept13, $token) || preg_match($exept14, $token))
- {
- $token = $token . "ΗΚ";
- }
- }
-
- // Step 5h
- $re = '/^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept15 = '/^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ|ΔΕ|ΔΕΥΤΕΡΕΥ|ΚΑΘΑΡΕΥ|ΠΛΕ|ΤΣΑ)$/';
- $exept16 = '/(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/';
-
- if (preg_match($exept15, $token) || preg_match($exept16, $token))
- {
- $token = $token . "ΟΥΣ";
- }
- }
-
- // Step 5i
- $re = '/^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept17 = '/^(ΨΟΦ|ΝΑΥΛΟΧ)$/';
- $exept20 = '/(ΚΟΛΛ)$/';
- $exept18 = '/^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|'
- . 'ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/';
- $exept19 = '/(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/';
-
- if ((preg_match($exept18, $token) || preg_match($exept19, $token))
- && !(preg_match($exept17, $token) || preg_match($exept20, $token)))
- {
- $token = $token . "ΑΓ";
- }
- }
-
- // Step 5j
- $re = '/^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept21 = '/^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ)$/';
-
- if (preg_match($exept21, $token))
- {
- $token = $token . "ΗΣ";
- }
- }
-
- // Step 5k
- $re = '/^(.+?)(ΗΣΤΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept22 = '/^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/';
-
- if (preg_match($exept22, $token))
- {
- $token = $token . "ΗΣΤ";
- }
- }
-
- // Step 5l
- $re = '/^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept23 = '/^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/';
-
- if (preg_match($exept23, $token))
- {
- $token = $token . "ΟΥΝ";
- }
- }
-
- // Step 5m
- $re = '/^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- $test1 = false;
- $exept24 = '/^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/';
-
- if (preg_match($exept24, $token))
- {
- $token = $token . "ΟΥΜ";
- }
- }
-
- // Step 6
- $re = '/^(.+?)(ΜΑΤΑ|ΜΑΤΩΝ|ΜΑΤΟΣ)$/';
- $re2 = '/^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|'
- . 'ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|'
- . 'ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|'
- . 'ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ|ΥΣ|Ω|ΩΝ)$/';
-
- if (preg_match($re, $token, $match))
- {
- $token = $match[1] . "ΜΑ";
- }
-
- if (preg_match($re2, $token) && $test1)
- {
- preg_match($re2, $token, $match);
- $token = $match[1];
- }
-
- // Step 7 (ΠΑΡΑΘΕΤΙΚΑ)
- $re = '/^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/';
-
- if (preg_match($re, $token))
- {
- preg_match($re, $token, $match);
- $token = $match[1];
- }
-
- return $this->toLowerCase($token, $wCase);
- }
-
- /**
- * Converts the token to uppercase, suppressing accents and diaeresis. The array $wCase contains a special map of
- * the uppercase rule used to convert each character at each position.
- *
- * @param string $token Token to process
- * @param array &$wCase Map of uppercase rules
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function toUpperCase($token, &$wCase)
- {
- $wCase = array_fill(0, mb_strlen($token, 'UTF-8'), 0);
- $caseConvert = array(
- "α" => 'Α',
- "β" => 'Β',
- "γ" => 'Γ',
- "δ" => 'Δ',
- "ε" => 'Ε',
- "ζ" => 'Ζ',
- "η" => 'Η',
- "θ" => 'Θ',
- "ι" => 'Ι',
- "κ" => 'Κ',
- "λ" => 'Λ',
- "μ" => 'Μ',
- "ν" => 'Ν',
- "ξ" => 'Ξ',
- "ο" => 'Ο',
- "π" => 'Π',
- "ρ" => 'Ρ',
- "σ" => 'Σ',
- "τ" => 'Τ',
- "υ" => 'Υ',
- "φ" => 'Φ',
- "χ" => 'Χ',
- "ψ" => 'Ψ',
- "ω" => 'Ω',
- "ά" => 'Α',
- "έ" => 'Ε',
- "ή" => 'Η',
- "ί" => 'Ι',
- "ό" => 'Ο',
- "ύ" => 'Υ',
- "ώ" => 'Ω',
- "ς" => 'Σ',
- "ϊ" => 'Ι',
- "ϋ" => 'Ι',
- "ΐ" => 'Ι',
- "ΰ" => 'Υ',
- );
- $newToken = '';
-
- for ($i = 0; $i < mb_strlen($token); $i++)
- {
- $char = mb_substr($token, $i, 1);
- $isLower = array_key_exists($char, $caseConvert);
-
- if (!$isLower)
- {
- $newToken .= $char;
-
- continue;
- }
-
- $upperCase = $caseConvert[$char];
- $newToken .= $upperCase;
-
- $wCase[$i] = 1;
-
- if (in_array($char, ['ά', 'έ', 'ή', 'ί', 'ό', 'ύ', 'ώ', 'ς']))
- {
- $wCase[$i] = 2;
- }
-
- if (in_array($char, ['ϊ', 'ϋ']))
- {
- $wCase[$i] = 3;
- }
-
- if (in_array($char, ['ΐ', 'ΰ']))
- {
- $wCase[$i] = 4;
- }
- }
-
- return $newToken;
- }
-
- /**
- * Converts the suppressed uppercase token back to lowercase, using the $wCase map to add back the accents,
- * diaeresis and handle the special case of final sigma (different lowercase glyph than the regular sigma, only
- * used at the end of words).
- *
- * @param string $token Token to process
- * @param array $wCase Map of lowercase rules
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function toLowerCase($token, $wCase)
- {
- $newToken = '';
-
- for ($i = 0; $i < mb_strlen($token); $i++)
- {
- $char = mb_substr($token, $i, 1);
-
- // Is $wCase not set at this position? We assume no case conversion ever took place.
- if (!isset($wCase[$i]))
- {
- $newToken .= $char;
-
- continue;
- }
-
- // The character was not case-converted
- if ($wCase[$i] == 0)
- {
- $newToken .= $char;
-
- continue;
- }
-
- // Case 1: Unaccented letter
- if ($wCase[$i] == 1)
- {
- $newToken .= mb_strtolower($char);
-
- continue;
- }
-
- // Case 2: Vowel with accent (tonos); or the special case of final sigma
- if ($wCase[$i] == 2)
- {
- $charMap = [
- 'Α' => 'ά',
- 'Ε' => 'έ',
- 'Η' => 'ή',
- 'Ι' => 'ί',
- 'Ο' => 'ό',
- 'Υ' => 'ύ',
- 'Ω' => 'ώ',
- 'Σ' => 'ς'
- ];
-
- $newToken .= $charMap[$char];
-
- continue;
- }
-
- // Case 3: vowels with diaeresis (dialytika)
- if ($wCase[$i] == 3)
- {
- $charMap = [
- 'Ι' => 'ϊ',
- 'Υ' => 'ϋ'
- ];
-
- $newToken .= $charMap[$char];
-
- continue;
- }
-
- // Case 4: vowels with both diaeresis (dialytika) and accent (tonos)
- if ($wCase[$i] == 4)
- {
- $charMap = [
- 'Ι' => 'ΐ',
- 'Υ' => 'ΰ'
- ];
-
- $newToken .= $charMap[$char];
-
- continue;
- }
-
- // This should never happen!
- $newToken .= $char;
- }
-
- return $newToken;
- }
+ /**
+ * Language locale of the class
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ public $language = 'el';
+
+ /**
+ * Method to construct the language object.
+ *
+ * @since 4.0.0
+ */
+ public function __construct($locale = null)
+ {
+ // Override parent constructor since we don't need to load an external stemmer
+ }
+
+ /**
+ * Method to tokenise a text string. It takes into account the odd punctuation commonly used in Greek text, mapping
+ * it to ASCII punctuation.
+ *
+ * Reference: http://www.teicrete.gr/users/kutrulis/Glosika/Stixi.htm
+ *
+ * @param string $input The input to tokenise.
+ *
+ * @return array An array of term strings.
+ *
+ * @since 4.0.0
+ */
+ public function tokenise($input)
+ {
+ // Replace Greek calligraphic double quotes (various styles) to dumb double quotes
+ $input = str_replace(['“', '”', '„', '«' ,'»'], '"', $input);
+
+ // Replace Greek calligraphic single quotes (various styles) to dumb single quotes
+ $input = str_replace(['‘','’','‚'], "'", $input);
+
+ // Replace the middle dot (ano teleia) with a comma, adequate for the purpose of stemming
+ $input = str_replace('·', ',', $input);
+
+ // Dot and dash (τελεία και παύλα), used to denote the end of a context at the end of a paragraph.
+ $input = str_replace('.–', '.', $input);
+
+ // Ellipsis, two styles (separate dots or single glyph)
+ $input = str_replace(['...', '…'], '.', $input);
+
+ // Cross. Marks the death date of a person. Removed.
+ $input = str_replace('†', '', $input);
+
+ // Star. Reference, supposition word (in philology), birth date of a person.
+ $input = str_replace('*', '', $input);
+
+ // Paragraph. Indicates change of subject.
+ $input = str_replace('§', '.', $input);
+
+ // Plus/minus. Shows approximation. Not relevant for the stemmer, hence its conversion to a space.
+ $input = str_replace('±', ' ', $input);
+
+ return parent::tokenise($input);
+ }
+
+ /**
+ * Method to stem a token.
+ *
+ * @param string $token The token to stem.
+ *
+ * @return string The stemmed token.
+ *
+ * @since 4.0.0
+ */
+ public function stem($token)
+ {
+ $token = $this->toUpperCase($token, $wCase);
+
+ // Stop-word removal
+ $stop_words = '/^(ΕΚΟ|ΑΒΑ|ΑΓΑ|ΑΓΗ|ΑΓΩ|ΑΔΗ|ΑΔΩ|ΑΕ|ΑΕΙ|ΑΘΩ|ΑΙ|ΑΙΚ|ΑΚΗ|ΑΚΟΜΑ|ΑΚΟΜΗ|ΑΚΡΙΒΩΣ|ΑΛΑ|ΑΛΗΘΕΙΑ|ΑΛΗΘΙΝΑ|ΑΛΛΑΧΟΥ|ΑΛΛΙΩΣ|ΑΛΛΙΩΤΙΚΑ|'
+ . 'ΑΛΛΟΙΩΣ|ΑΛΛΟΙΩΤΙΚΑ|ΑΛΛΟΤΕ|ΑΛΤ|ΑΛΩ|ΑΜΑ|ΑΜΕ|ΑΜΕΣΑ|ΑΜΕΣΩΣ|ΑΜΩ|ΑΝ|ΑΝΑ|ΑΝΑΜΕΣΑ|ΑΝΑΜΕΤΑΞΥ|ΑΝΕΥ|ΑΝΤΙ|ΑΝΤΙΠΕΡΑ|ΑΝΤΙΣ|ΑΝΩ|ΑΝΩΤΕΡΩ|ΑΞΑΦΝΑ|'
+ . 'ΑΠ|ΑΠΕΝΑΝΤΙ|ΑΠΟ|ΑΠΟΨΕ|ΑΠΩ|ΑΡΑ|ΑΡΑΓΕ|ΑΡΕ|ΑΡΚ|ΑΡΚΕΤΑ|ΑΡΛ|ΑΡΜ|ΑΡΤ|ΑΡΥ|ΑΡΩ|ΑΣ|ΑΣΑ|ΑΣΟ|ΑΤΑ|ΑΤΕ|ΑΤΗ|ΑΤΙ|ΑΤΜ|ΑΤΟ|ΑΥΡΙΟ|ΑΦΗ|ΑΦΟΤΟΥ|ΑΦΟΥ|'
+ . 'ΑΧ|ΑΧΕ|ΑΧΟ|ΑΨΑ|ΑΨΕ|ΑΨΗ|ΑΨΥ|ΑΩΕ|ΑΩΟ|ΒΑΝ|ΒΑΤ|ΒΑΧ|ΒΕΑ|ΒΕΒΑΙΟΤΑΤΑ|ΒΗΞ|ΒΙΑ|ΒΙΕ|ΒΙΗ|ΒΙΟ|ΒΟΗ|ΒΟΩ|ΒΡΕ|ΓΑ|ΓΑΒ|ΓΑΡ|ΓΕΝ|ΓΕΣ||ΓΗ|ΓΗΝ|ΓΙ|ΓΙΑ|'
+ . 'ΓΙΕ|ΓΙΝ|ΓΙΟ|ΓΚΙ|ΓΙΑΤΙ|ΓΚΥ|ΓΟΗ|ΓΟΟ|ΓΡΗΓΟΡΑ|ΓΡΙ|ΓΡΥ|ΓΥΗ|ΓΥΡΩ|ΔΑ|ΔΕ|ΔΕΗ|ΔΕΙ|ΔΕΝ|ΔΕΣ|ΔΗ|ΔΗΘΕΝ|ΔΗΛΑΔΗ|ΔΗΩ|ΔΙ|ΔΙΑ|ΔΙΑΡΚΩΣ|ΔΙΟΛΟΥ|ΔΙΣ|'
+ . 'ΔΙΧΩΣ|ΔΟΛ|ΔΟΝ|ΔΡΑ|ΔΡΥ|ΔΡΧ|ΔΥΕ|ΔΥΟ|ΔΩ|ΕΑΜ|ΕΑΝ|ΕΑΡ|ΕΘΗ|ΕΙ|ΕΙΔΕΜΗ|ΕΙΘΕ|ΕΙΜΑΙ|ΕΙΜΑΣΤΕ|ΕΙΝΑΙ|ΕΙΣ|ΕΙΣΑΙ|ΕΙΣΑΣΤΕ|ΕΙΣΤΕ|ΕΙΤΕ|ΕΙΧΑ|ΕΙΧΑΜΕ|'
+ . 'ΕΙΧΑΝ|ΕΙΧΑΤΕ|ΕΙΧΕ|ΕΙΧΕΣ|ΕΚ|ΕΚΕΙ|ΕΛΑ|ΕΛΙ|ΕΜΠ|ΕΝ|ΕΝΤΕΛΩΣ|ΕΝΤΟΣ|ΕΝΤΩΜΕΤΑΞΥ|ΕΝΩ|ΕΞ|ΕΞΑΦΝΑ|ΕΞΙ|ΕΞΙΣΟΥ|ΕΞΩ|ΕΟΚ|ΕΠΑΝΩ|ΕΠΕΙΔΗ|ΕΠΕΙΤΑ|ΕΠΗ|'
+ . 'ΕΠΙ|ΕΠΙΣΗΣ|ΕΠΟΜΕΝΩΣ|ΕΡΑ|ΕΣ|ΕΣΑΣ|ΕΣΕ|ΕΣΕΙΣ|ΕΣΕΝΑ|ΕΣΗ|ΕΣΤΩ|ΕΣΥ|ΕΣΩ|ΕΤΙ|ΕΤΣΙ|ΕΥ|ΕΥΑ|ΕΥΓΕ|ΕΥΘΥΣ|ΕΥΤΥΧΩΣ|ΕΦΕ|ΕΦΕΞΗΣ|ΕΦΤ|ΕΧΕ|ΕΧΕΙ|'
+ . 'ΕΧΕΙΣ|ΕΧΕΤΕ|ΕΧΘΕΣ|ΕΧΟΜΕ|ΕΧΟΥΜΕ|ΕΧΟΥΝ|ΕΧΤΕΣ|ΕΧΩ|ΕΩΣ|ΖΕΑ|ΖΕΗ|ΖΕΙ|ΖΕΝ|ΖΗΝ|ΖΩ|Η|ΗΔΗ|ΗΔΥ|ΗΘΗ|ΗΛΟ|ΗΜΙ|ΗΠΑ|ΗΣΑΣΤΕ|ΗΣΟΥΝ|ΗΤΑ|ΗΤΑΝ|ΗΤΑΝΕ|'
+ . 'ΗΤΟΙ|ΗΤΤΟΝ|ΗΩ|ΘΑ|ΘΥΕ|ΘΩΡ|Ι|ΙΑ|ΙΒΟ|ΙΔΗ|ΙΔΙΩΣ|ΙΕ|ΙΙ|ΙΙΙ|ΙΚΑ|ΙΛΟ|ΙΜΑ|ΙΝΑ|ΙΝΩ|ΙΞΕ|ΙΞΟ|ΙΟ|ΙΟΙ|ΙΣΑ|ΙΣΑΜΕ|ΙΣΕ|ΙΣΗ|ΙΣΙΑ|ΙΣΟ|ΙΣΩΣ|ΙΩΒ|ΙΩΝ|'
+ . 'ΙΩΣ|ΙΑΝ|ΚΑΘ|ΚΑΘΕ|ΚΑΘΕΤΙ|ΚΑΘΟΛΟΥ|ΚΑΘΩΣ|ΚΑΙ|ΚΑΝ|ΚΑΠΟΤΕ|ΚΑΠΟΥ|ΚΑΠΩΣ|ΚΑΤ|ΚΑΤΑ|ΚΑΤΙ|ΚΑΤΙΤΙ|ΚΑΤΟΠΙΝ|ΚΑΤΩ|ΚΑΩ|ΚΒΟ|ΚΕΑ|ΚΕΙ|ΚΕΝ|ΚΙ|ΚΙΜ|'
+ . 'ΚΙΟΛΑΣ|ΚΙΤ|ΚΙΧ|ΚΚΕ|ΚΛΙΣΕ|ΚΛΠ|ΚΟΚ|ΚΟΝΤΑ|ΚΟΧ|ΚΤΛ|ΚΥΡ|ΚΥΡΙΩΣ|ΚΩ|ΚΩΝ|ΛΑ|ΛΕΑ|ΛΕΝ|ΛΕΟ|ΛΙΑ|ΛΙΓΑΚΙ|ΛΙΓΟΥΛΑΚΙ|ΛΙΓΟ|ΛΙΓΩΤΕΡΟ|ΛΙΟ|ΛΙΡ|ΛΟΓΩ|'
+ . 'ΛΟΙΠΑ|ΛΟΙΠΟΝ|ΛΟΣ|ΛΣ|ΛΥΩ|ΜΑ|ΜΑΖΙ|ΜΑΚΑΡΙ|ΜΑΛΙΣΤΑ|ΜΑΛΛΟΝ|ΜΑΝ|ΜΑΞ|ΜΑΣ|ΜΑΤ|ΜΕ|ΜΕΘΑΥΡΙΟ|ΜΕΙ|ΜΕΙΟΝ|ΜΕΛ|ΜΕΛΕΙ|ΜΕΛΛΕΤΑΙ|ΜΕΜΙΑΣ|ΜΕΝ|ΜΕΣ|'
+ . 'ΜΕΣΑ|ΜΕΤ|ΜΕΤΑ|ΜΕΤΑΞΥ|ΜΕΧΡΙ|ΜΗ|ΜΗΔΕ|ΜΗΝ|ΜΗΠΩΣ|ΜΗΤΕ|ΜΙ|ΜΙΞ|ΜΙΣ|ΜΜΕ|ΜΝΑ|ΜΟΒ|ΜΟΛΙΣ|ΜΟΛΟΝΟΤΙ|ΜΟΝΑΧΑ|ΜΟΝΟΜΙΑΣ|ΜΙΑ|ΜΟΥ|ΜΠΑ|ΜΠΟΡΕΙ|'
+ . 'ΜΠΟΡΟΥΝ|ΜΠΡΑΒΟ|ΜΠΡΟΣ|ΜΠΩ|ΜΥ|ΜΥΑ|ΜΥΝ|ΝΑ|ΝΑΕ|ΝΑΙ|ΝΑΟ|ΝΔ|ΝΕΐ|ΝΕΑ|ΝΕΕ|ΝΕΟ|ΝΙ|ΝΙΑ|ΝΙΚ|ΝΙΛ|ΝΙΝ|ΝΙΟ|ΝΤΑ|ΝΤΕ|ΝΤΙ|ΝΤΟ|ΝΥΝ|ΝΩΕ|ΝΩΡΙΣ|ΞΑΝΑ|'
+ . 'ΞΑΦΝΙΚΑ|ΞΕΩ|ΞΙ|Ο|ΟΑ|ΟΑΠ|ΟΔΟ|ΟΕ|ΟΖΟ|ΟΗΕ|ΟΙ|ΟΙΑ|ΟΙΗ|ΟΚΑ|ΟΛΟΓΥΡΑ|ΟΛΟΝΕΝ|ΟΛΟΤΕΛΑ|ΟΛΩΣΔΙΟΛΟΥ|ΟΜΩΣ|ΟΝ|ΟΝΕ|ΟΝΟ|ΟΠΑ|ΟΠΕ|ΟΠΗ|ΟΠΟ|'
+ . 'ΟΠΟΙΑΔΗΠΟΤΕ|ΟΠΟΙΑΝΔΗΠΟΤΕ|ΟΠΟΙΑΣΔΗΠΟΤΕ|ΟΠΟΙΔΗΠΟΤΕ|ΟΠΟΙΕΣΔΗΠΟΤΕ|ΟΠΟΙΟΔΗΠΟΤΕ|ΟΠΟΙΟΝΔΗΠΟΤΕ|ΟΠΟΙΟΣΔΗΠΟΤΕ|ΟΠΟΙΟΥΔΗΠΟΤΕ|ΟΠΟΙΟΥΣΔΗΠΟΤΕ|'
+ . 'ΟΠΟΙΩΝΔΗΠΟΤΕ|ΟΠΟΤΕΔΗΠΟΤΕ|ΟΠΟΥ|ΟΠΟΥΔΗΠΟΤΕ|ΟΠΩΣ|ΟΡΑ|ΟΡΕ|ΟΡΗ|ΟΡΟ|ΟΡΦ|ΟΡΩ|ΟΣΑ|ΟΣΑΔΗΠΟΤΕ|ΟΣΕ|ΟΣΕΣΔΗΠΟΤΕ|ΟΣΗΔΗΠΟΤΕ|ΟΣΗΝΔΗΠΟΤΕ|'
+ . 'ΟΣΗΣΔΗΠΟΤΕ|ΟΣΟΔΗΠΟΤΕ|ΟΣΟΙΔΗΠΟΤΕ|ΟΣΟΝΔΗΠΟΤΕ|ΟΣΟΣΔΗΠΟΤΕ|ΟΣΟΥΔΗΠΟΤΕ|ΟΣΟΥΣΔΗΠΟΤΕ|ΟΣΩΝΔΗΠΟΤΕ|ΟΤΑΝ|ΟΤΕ|ΟΤΙ|ΟΤΙΔΗΠΟΤΕ|ΟΥ|ΟΥΔΕ|ΟΥΚ|ΟΥΣ|'
+ . 'ΟΥΤΕ|ΟΥΦ|ΟΧΙ|ΟΨΑ|ΟΨΕ|ΟΨΗ|ΟΨΙ|ΟΨΟ|ΠΑ|ΠΑΛΙ|ΠΑΝ|ΠΑΝΤΟΤΕ|ΠΑΝΤΟΥ|ΠΑΝΤΩΣ|ΠΑΠ|ΠΑΡ|ΠΑΡΑ|ΠΕΙ|ΠΕΡ|ΠΕΡΑ|ΠΕΡΙ|ΠΕΡΙΠΟΥ|ΠΕΡΣΙ|ΠΕΡΥΣΙ|ΠΕΣ|ΠΙ|'
+ . 'ΠΙΑ|ΠΙΘΑΝΟΝ|ΠΙΚ|ΠΙΟ|ΠΙΣΩ|ΠΙΤ|ΠΙΩ|ΠΛΑΙ|ΠΛΕΟΝ|ΠΛΗΝ|ΠΛΩ|ΠΜ|ΠΟΑ|ΠΟΕ|ΠΟΛ|ΠΟΛΥ|ΠΟΠ|ΠΟΤΕ|ΠΟΥ|ΠΟΥΘΕ|ΠΟΥΘΕΝΑ|ΠΡΕΠΕΙ|ΠΡΙ|ΠΡΙΝ|ΠΡΟ|'
+ . 'ΠΡΟΚΕΙΜΕΝΟΥ|ΠΡΟΚΕΙΤΑΙ|ΠΡΟΠΕΡΣΙ|ΠΡΟΣ|ΠΡΟΤΟΥ|ΠΡΟΧΘΕΣ|ΠΡΟΧΤΕΣ|ΠΡΩΤΥΤΕΡΑ|ΠΥΑ|ΠΥΞ|ΠΥΟ|ΠΥΡ|ΠΧ|ΠΩ|ΠΩΛ|ΠΩΣ|ΡΑ|ΡΑΙ|ΡΑΠ|ΡΑΣ|ΡΕ|ΡΕΑ|ΡΕΕ|ΡΕΙ|'
+ . 'ΡΗΣ|ΡΘΩ|ΡΙΟ|ΡΟ|ΡΟΐ|ΡΟΕ|ΡΟΖ|ΡΟΗ|ΡΟΘ|ΡΟΙ|ΡΟΚ|ΡΟΛ|ΡΟΝ|ΡΟΣ|ΡΟΥ|ΣΑΙ|ΣΑΝ|ΣΑΟ|ΣΑΣ|ΣΕ|ΣΕΙΣ|ΣΕΚ|ΣΕΞ|ΣΕΡ|ΣΕΤ|ΣΕΦ|ΣΗΜΕΡΑ|ΣΙ|ΣΙΑ|ΣΙΓΑ|ΣΙΚ|'
+ . 'ΣΙΧ|ΣΚΙ|ΣΟΙ|ΣΟΚ|ΣΟΛ|ΣΟΝ|ΣΟΣ|ΣΟΥ|ΣΡΙ|ΣΤΑ|ΣΤΗ|ΣΤΗΝ|ΣΤΗΣ|ΣΤΙΣ|ΣΤΟ|ΣΤΟΝ|ΣΤΟΥ|ΣΤΟΥΣ|ΣΤΩΝ|ΣΥ|ΣΥΓΧΡΟΝΩΣ|ΣΥΝ|ΣΥΝΑΜΑ|ΣΥΝΕΠΩΣ|ΣΥΝΗΘΩΣ|'
+ . 'ΣΧΕΔΟΝ|ΣΩΣΤΑ|ΤΑ|ΤΑΔΕ|ΤΑΚ|ΤΑΝ|ΤΑΟ|ΤΑΥ|ΤΑΧΑ|ΤΑΧΑΤΕ|ΤΕ|ΤΕΙ|ΤΕΛ|ΤΕΛΙΚΑ|ΤΕΛΙΚΩΣ|ΤΕΣ|ΤΕΤ|ΤΖΟ|ΤΗ|ΤΗΛ|ΤΗΝ|ΤΗΣ|ΤΙ|ΤΙΚ|ΤΙΜ|ΤΙΠΟΤΑ|ΤΙΠΟΤΕ|'
+ . 'ΤΙΣ|ΤΝΤ|ΤΟ|ΤΟΙ|ΤΟΚ|ΤΟΜ|ΤΟΝ|ΤΟΠ|ΤΟΣ|ΤΟΣ?Ν|ΤΟΣΑ|ΤΟΣΕΣ|ΤΟΣΗ|ΤΟΣΗΝ|ΤΟΣΗΣ|ΤΟΣΟ|ΤΟΣΟΙ|ΤΟΣΟΝ|ΤΟΣΟΣ|ΤΟΣΟΥ|ΤΟΣΟΥΣ|ΤΟΤΕ|ΤΟΥ|ΤΟΥΛΑΧΙΣΤΟ|'
+ . 'ΤΟΥΛΑΧΙΣΤΟΝ|ΤΟΥΣ|ΤΣ|ΤΣΑ|ΤΣΕ|ΤΥΧΟΝ|ΤΩ|ΤΩΝ|ΤΩΡΑ|ΥΑΣ|ΥΒΑ|ΥΒΟ|ΥΙΕ|ΥΙΟ|ΥΛΑ|ΥΛΗ|ΥΝΙ|ΥΠ|ΥΠΕΡ|ΥΠΟ|ΥΠΟΨΗ|ΥΠΟΨΙΝ|ΥΣΤΕΡΑ|ΥΦΗ|ΥΨΗ|ΦΑ|ΦΑΐ|ΦΑΕ|'
+ . 'ΦΑΝ|ΦΑΞ|ΦΑΣ|ΦΑΩ|ΦΕΖ|ΦΕΙ|ΦΕΤΟΣ|ΦΕΥ|ΦΙ|ΦΙΛ|ΦΙΣ|ΦΟΞ|ΦΠΑ|ΦΡΙ|ΧΑ|ΧΑΗ|ΧΑΛ|ΧΑΝ|ΧΑΦ|ΧΕ|ΧΕΙ|ΧΘΕΣ|ΧΙ|ΧΙΑ|ΧΙΛ|ΧΙΟ|ΧΛΜ|ΧΜ|ΧΟΗ|ΧΟΛ|ΧΡΩ|ΧΤΕΣ|'
+ . 'ΧΩΡΙΣ|ΧΩΡΙΣΤΑ|ΨΕΣ|ΨΗΛΑ|ΨΙ|ΨΙΤ|Ω|ΩΑ|ΩΑΣ|ΩΔΕ|ΩΕΣ|ΩΘΩ|ΩΜΑ|ΩΜΕ|ΩΝ|ΩΟ|ΩΟΝ|ΩΟΥ|ΩΣ|ΩΣΑΝ|ΩΣΗ|ΩΣΟΤΟΥ|ΩΣΠΟΥ|ΩΣΤΕ|ΩΣΤΟΣΟ|ΩΤΑ|ΩΧ|ΩΩΝ)$/';
+
+ if (preg_match($stop_words, $token)) {
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Vowels
+ $v = '(Α|Ε|Η|Ι|Ο|Υ|Ω)';
+
+ // Vowels without Y
+ $v2 = '(Α|Ε|Η|Ι|Ο|Ω)';
+
+ $test1 = true;
+
+ // Step S1. 14 stems
+ $re = '/^(.+?)(ΙΖΑ|ΙΖΕΣ|ΙΖΕ|ΙΖΑΜΕ|ΙΖΑΤΕ|ΙΖΑΝ|ΙΖΑΝΕ|ΙΖΩ|ΙΖΕΙΣ|ΙΖΕΙ|ΙΖΟΥΜΕ|ΙΖΕΤΕ|ΙΖΟΥΝ|ΙΖΟΥΝΕ)$/';
+ $exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΠΑ|ΞΑΝΑΠΑ|ΠΑ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ)$/';
+ $exceptS2 = '/^(ΜΑΡΚ|ΚΟΡΝ|ΑΜΠΑΡ|ΑΡΡ|ΒΑΘΥΡΙ|ΒΑΡΚ|Β|ΒΟΛΒΟΡ|ΓΚΡ|ΓΛΥΚΟΡ|ΓΛΥΚΥΡ|ΙΜΠ|Λ|ΛΟΥ|ΜΑΡ|Μ|ΠΡ|ΜΠΡ|ΠΟΛΥΡ|Π|Ρ|ΠΙΠΕΡΟΡ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . 'I';
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . 'IΖ';
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S2. 7 stems
+ $re = '/^(.+?)(ΩΘΗΚΑ|ΩΘΗΚΕΣ|ΩΘΗΚΕ|ΩΘΗΚΑΜΕ|ΩΘΗΚΑΤΕ|ΩΘΗΚΑΝ|ΩΘΗΚΑΝΕ)$/';
+ $exceptS1 = '/^(ΑΛ|ΒΙ|ΕΝ|ΥΨ|ΛΙ|ΖΩ|Σ|Χ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . 'ΩΝ';
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S3. 7 stems
+ $re = '/^(.+?)(ΙΣΑ|ΙΣΕΣ|ΙΣΕ|ΙΣΑΜΕ|ΙΣΑΤΕ|ΙΣΑΝ|ΙΣΑΝΕ)$/';
+ $exceptS1 = '/^(ΑΝΑΜΠΑ|ΑΘΡΟ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
+ $exceptS2 = '/^(ΑΝ|ΑΦ|ΓΕ|ΓΙΓΑΝΤΟΑΦ|ΓΚΕ|ΔΗΜΟΚΡΑΤ|ΚΟΜ|ΓΚ|Μ|Π|ΠΟΥΚΑΜ|ΟΛΟ|ΛΑΡ)$/';
+
+ if ($token == "ΙΣΑ") {
+ $token = "ΙΣ";
+
+ return $token;
+ }
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . 'Ι';
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . 'ΙΣ';
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S4. 7 stems
+ $re = '/^(.+?)(ΙΣΩ|ΙΣΕΙΣ|ΙΣΕΙ|ΙΣΟΥΜΕ|ΙΣΕΤΕ|ΙΣΟΥΝ|ΙΣΟΥΝΕ)$/';
+ $exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . 'Ι';
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S5. 11 stems
+ $re = '/^(.+?)(ΙΣΤΟΣ|ΙΣΤΟΥ|ΙΣΤΟ|ΙΣΤΕ|ΙΣΤΟΙ|ΙΣΤΩΝ|ΙΣΤΟΥΣ|ΙΣΤΗ|ΙΣΤΗΣ|ΙΣΤΑ|ΙΣΤΕΣ)$/';
+ $exceptS1 = '/^(Μ|Π|ΑΠ|ΑΡ|ΗΔ|ΚΤ|ΣΚ|ΣΧ|ΥΨ|ΦΑ|ΧΡ|ΧΤ|ΑΚΤ|ΑΟΡ|ΑΣΧ|ΑΤΑ|ΑΧΝ|ΑΧΤ|ΓΕΜ|ΓΥΡ|ΕΜΠ|ΕΥΠ|ΕΧΘ|ΗΦΑ|ΚΑΘ|ΚΑΚ|ΚΥΛ|ΛΥΓ|ΜΑΚ|ΜΕΓ|ΤΑΧ|ΦΙΛ|ΧΩΡ)$/';
+ $exceptS2 = '/^(ΔΑΝΕ|ΣΥΝΑΘΡΟ|ΚΛΕ|ΣΕ|ΕΣΩΚΛΕ|ΑΣΕ|ΠΛΕ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . 'ΙΣΤ';
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . 'Ι';
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S6. 6 stems
+ $re = '/^(.+?)(ΙΣΜΟ|ΙΣΜΟΙ|ΙΣΜΟΣ|ΙΣΜΟΥ|ΙΣΜΟΥΣ|ΙΣΜΩΝ)$/';
+ $exceptS1 = '/^(ΑΓΝΩΣΤΙΚ|ΑΤΟΜΙΚ|ΓΝΩΣΤΙΚ|ΕΘΝΙΚ|ΕΚΛΕΚΤΙΚ|ΣΚΕΠΤΙΚ|ΤΟΠΙΚ)$/';
+ $exceptS2 = '/^(ΣΕ|ΜΕΤΑΣΕ|ΜΙΚΡΟΣΕ|ΕΓΚΛΕ|ΑΠΟΚΛΕ)$/';
+ $exceptS3 = '/^(ΔΑΝΕ|ΑΝΤΙΔΑΝΕ)$/';
+ $exceptS4 = '/^(ΑΛΕΞΑΝΔΡΙΝ|ΒΥΖΑΝΤΙΝ|ΘΕΑΤΡΙΝ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = str_replace('ΙΚ', "", $token);
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . "ΙΣΜ";
+ }
+
+ if (preg_match($exceptS3, $token)) {
+ $token = $token . "Ι";
+ }
+
+ if (preg_match($exceptS4, $token)) {
+ $token = str_replace('ΙΝ', "", $token);
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S7. 4 stems
+ $re = '/^(.+?)(ΑΡΑΚΙ|ΑΡΑΚΙΑ|ΟΥΔΑΚΙ|ΟΥΔΑΚΙΑ)$/';
+ $exceptS1 = '/^(Σ|Χ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . "AΡΑΚ";
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S8. 8 stems
+ $re = '/^(.+?)(ΑΚΙ|ΑΚΙΑ|ΙΤΣΑ|ΙΤΣΑΣ|ΙΤΣΕΣ|ΙΤΣΩΝ|ΑΡΑΚΙ|ΑΡΑΚΙΑ)$/';
+ $exceptS1 = '/^(ΑΝΘΡ|ΒΑΜΒ|ΒΡ|ΚΑΙΜ|ΚΟΝ|ΚΟΡ|ΛΑΒΡ|ΛΟΥΛ|ΜΕΡ|ΜΟΥΣΤ|ΝΑΓΚΑΣ|ΠΛ|Ρ|ΡΥ|Σ|ΣΚ|ΣΟΚ|ΣΠΑΝ|ΤΖ|ΦΑΡΜ|Χ|'
+ . 'ΚΑΠΑΚ|ΑΛΙΣΦ|ΑΜΒΡ|ΑΝΘΡ|Κ|ΦΥΛ|ΚΑΤΡΑΠ|ΚΛΙΜ|ΜΑΛ|ΣΛΟΒ|Φ|ΣΦ|ΤΣΕΧΟΣΛΟΒ)$/';
+ $exceptS2 = '/^(Β|ΒΑΛ|ΓΙΑΝ|ΓΛ|Ζ|ΗΓΟΥΜΕΝ|ΚΑΡΔ|ΚΟΝ|ΜΑΚΡΥΝ|ΝΥΦ|ΠΑΤΕΡ|Π|ΣΚ|ΤΟΣ|ΤΡΙΠΟΛ)$/';
+
+ // For words like ΠΛΟΥΣΙΟΚΟΡΙΤΣΑ, ΠΑΛΙΟΚΟΡΙΤΣΑ etc
+ $exceptS3 = '/(ΚΟΡ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . "ΑΚ";
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . "ΙΤΣ";
+ }
+
+ if (preg_match($exceptS3, $token)) {
+ $token = $token . "ΙΤΣ";
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S9. 3 stems
+ $re = '/^(.+?)(ΙΔΙΟ|ΙΔΙΑ|ΙΔΙΩΝ)$/';
+ $exceptS1 = '/^(ΑΙΦΝ|ΙΡ|ΟΛΟ|ΨΑΛ)$/';
+ $exceptS2 = '/(Ε|ΠΑΙΧΝ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . "ΙΔ";
+ }
+
+ if (preg_match($exceptS2, $token)) {
+ $token = $token . "ΙΔ";
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step S10. 4 stems
+ $re = '/^(.+?)(ΙΣΚΟΣ|ΙΣΚΟΥ|ΙΣΚΟ|ΙΣΚΕ)$/';
+ $exceptS1 = '/^(Δ|ΙΒ|ΜΗΝ|Ρ|ΦΡΑΓΚ|ΛΥΚ|ΟΒΕΛ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+
+ if (preg_match($exceptS1, $token)) {
+ $token = $token . "ΙΣΚ";
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ // Step 1
+ // step1list is used in Step 1. 41 stems
+ $step1list = array();
+ $step1list["ΦΑΓΙΑ"] = "ΦΑ";
+ $step1list["ΦΑΓΙΟΥ"] = "ΦΑ";
+ $step1list["ΦΑΓΙΩΝ"] = "ΦΑ";
+ $step1list["ΣΚΑΓΙΑ"] = "ΣΚΑ";
+ $step1list["ΣΚΑΓΙΟΥ"] = "ΣΚΑ";
+ $step1list["ΣΚΑΓΙΩΝ"] = "ΣΚΑ";
+ $step1list["ΟΛΟΓΙΟΥ"] = "ΟΛΟ";
+ $step1list["ΟΛΟΓΙΑ"] = "ΟΛΟ";
+ $step1list["ΟΛΟΓΙΩΝ"] = "ΟΛΟ";
+ $step1list["ΣΟΓΙΟΥ"] = "ΣΟ";
+ $step1list["ΣΟΓΙΑ"] = "ΣΟ";
+ $step1list["ΣΟΓΙΩΝ"] = "ΣΟ";
+ $step1list["ΤΑΤΟΓΙΑ"] = "ΤΑΤΟ";
+ $step1list["ΤΑΤΟΓΙΟΥ"] = "ΤΑΤΟ";
+ $step1list["ΤΑΤΟΓΙΩΝ"] = "ΤΑΤΟ";
+ $step1list["ΚΡΕΑΣ"] = "ΚΡΕ";
+ $step1list["ΚΡΕΑΤΟΣ"] = "ΚΡΕ";
+ $step1list["ΚΡΕΑΤΑ"] = "ΚΡΕ";
+ $step1list["ΚΡΕΑΤΩΝ"] = "ΚΡΕ";
+ $step1list["ΠΕΡΑΣ"] = "ΠΕΡ";
+ $step1list["ΠΕΡΑΤΟΣ"] = "ΠΕΡ";
+
+ // Added by Spyros. Also at $re in step1
+ $step1list["ΠΕΡΑΤΗ"] = "ΠΕΡ";
+ $step1list["ΠΕΡΑΤΑ"] = "ΠΕΡ";
+ $step1list["ΠΕΡΑΤΩΝ"] = "ΠΕΡ";
+ $step1list["ΤΕΡΑΣ"] = "ΤΕΡ";
+ $step1list["ΤΕΡΑΤΟΣ"] = "ΤΕΡ";
+ $step1list["ΤΕΡΑΤΑ"] = "ΤΕΡ";
+ $step1list["ΤΕΡΑΤΩΝ"] = "ΤΕΡ";
+ $step1list["ΦΩΣ"] = "ΦΩ";
+ $step1list["ΦΩΤΟΣ"] = "ΦΩ";
+ $step1list["ΦΩΤΑ"] = "ΦΩ";
+ $step1list["ΦΩΤΩΝ"] = "ΦΩ";
+ $step1list["ΚΑΘΕΣΤΩΣ"] = "ΚΑΘΕΣΤ";
+ $step1list["ΚΑΘΕΣΤΩΤΟΣ"] = "ΚΑΘΕΣΤ";
+ $step1list["ΚΑΘΕΣΤΩΤΑ"] = "ΚΑΘΕΣΤ";
+ $step1list["ΚΑΘΕΣΤΩΤΩΝ"] = "ΚΑΘΕΣΤ";
+ $step1list["ΓΕΓΟΝΟΣ"] = "ΓΕΓΟΝ";
+ $step1list["ΓΕΓΟΝΟΤΟΣ"] = "ΓΕΓΟΝ";
+ $step1list["ΓΕΓΟΝΟΤΑ"] = "ΓΕΓΟΝ";
+ $step1list["ΓΕΓΟΝΟΤΩΝ"] = "ΓΕΓΟΝ";
+
+ $re = '/(.*)(ΦΑΓΙΑ|ΦΑΓΙΟΥ|ΦΑΓΙΩΝ|ΣΚΑΓΙΑ|ΣΚΑΓΙΟΥ|ΣΚΑΓΙΩΝ|ΟΛΟΓΙΟΥ|ΟΛΟΓΙΑ|ΟΛΟΓΙΩΝ|ΣΟΓΙΟΥ|ΣΟΓΙΑ|ΣΟΓΙΩΝ|ΤΑΤΟΓΙΑ|ΤΑΤΟΓΙΟΥ|ΤΑΤΟΓΙΩΝ|ΚΡΕΑΣ|ΚΡΕΑΤΟΣ|'
+ . 'ΚΡΕΑΤΑ|ΚΡΕΑΤΩΝ|ΠΕΡΑΣ|ΠΕΡΑΤΟΣ|ΠΕΡΑΤΗ|ΠΕΡΑΤΑ|ΠΕΡΑΤΩΝ|ΤΕΡΑΣ|ΤΕΡΑΤΟΣ|ΤΕΡΑΤΑ|ΤΕΡΑΤΩΝ|ΦΩΣ|ΦΩΤΟΣ|ΦΩΤΑ|ΦΩΤΩΝ|ΚΑΘΕΣΤΩΣ|ΚΑΘΕΣΤΩΤΟΣ|'
+ . 'ΚΑΘΕΣΤΩΤΑ|ΚΑΘΕΣΤΩΤΩΝ|ΓΕΓΟΝΟΣ|ΓΕΓΟΝΟΤΟΣ|ΓΕΓΟΝΟΤΑ|ΓΕΓΟΝΟΤΩΝ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $stem = $match[1];
+ $suffix = $match[2];
+ $token = $stem . (array_key_exists($suffix, $step1list) ? $step1list[$suffix] : '');
+ $test1 = false;
+ }
+
+ // Step 2a. 2 stems
+ $re = '/^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1];
+ $re = '/(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ)$/';
+
+ if (!preg_match($re, $token)) {
+ $token = $token . "ΑΔ";
+ }
+ }
+
+ // Step 2b. 2 stems
+ $re = '/^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $exept2 = '/(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/';
+
+ if (preg_match($exept2, $token)) {
+ $token = $token . 'ΕΔ';
+ }
+ }
+
+ // Step 2c
+ $re = '/^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+
+ $exept3 = '/(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/';
+
+ if (preg_match($exept3, $token)) {
+ $token = $token . 'ΟΥΔ';
+ }
+ }
+
+ // Step 2d
+ $re = '/^(.+?)(ΕΩΣ|ΕΩΝ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept4 = '/^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ)$/';
+
+ if (preg_match($exept4, $token)) {
+ $token = $token . 'Ε';
+ }
+ }
+
+ // Step 3
+ $re = '/^(.+?)(ΙΑ|ΙΟΥ|ΙΩΝ)$/';
+
+ if (preg_match($re, $token, $fp)) {
+ $stem = $fp[1];
+ $token = $stem;
+ $re = '/' . $v . '$/';
+ $test1 = false;
+
+ if (preg_match($re, $token)) {
+ $token = $stem . 'Ι';
+ }
+ }
+
+ // Step 4
+ $re = '/^(.+?)(ΙΚΑ|ΙΚΟ|ΙΚΟΥ|ΙΚΩΝ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $re = '/' . $v . '$/';
+ $exept5 = '/^(ΑΛ|ΑΔ|ΕΝΔ|ΑΜΑΝ|ΑΜΜΟΧΑΛ|ΗΘ|ΑΝΗΘ|ΑΝΤΙΔ|ΦΥΣ|ΒΡΩΜ|ΓΕΡ|ΕΞΩΔ|ΚΑΛΠ|ΚΑΛΛΙΝ|ΚΑΤΑΔ|ΜΟΥΛ|ΜΠΑΝ|ΜΠΑΓΙΑΤ|ΜΠΟΛ|ΜΠΟΣ|ΝΙΤ|ΞΙΚ|ΣΥΝΟΜΗΛ|ΠΕΤΣ|'
+ . 'ΠΙΤΣ|ΠΙΚΑΝΤ|ΠΛΙΑΤΣ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΥΝΑΔ|ΤΣΑΜ|ΥΠΟΔ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΧΑΣ)$/';
+
+ if (preg_match($re, $token) || preg_match($exept5, $token)) {
+ $token = $token . 'ΙΚ';
+ }
+ }
+
+ // Step 5a
+ $re = '/^(.+?)(ΑΜΕ)$/';
+ $re2 = '/^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/';
+
+ if ($token == "ΑΓΑΜΕ") {
+ $token = "ΑΓΑΜ";
+ }
+
+ if (preg_match($re2, $token)) {
+ preg_match($re2, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ }
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept6 = '/^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/';
+
+ if (preg_match($exept6, $token)) {
+ $token = $token . "ΑΜ";
+ }
+ }
+
+ // Step 5b
+ $re2 = '/^(.+?)(ΑΝΕ)$/';
+ $re3 = '/^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/';
+
+ if (preg_match($re3, $token)) {
+ preg_match($re3, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $re3 = '/^(ΤΡ|ΤΣ)$/';
+
+ if (preg_match($re3, $token)) {
+ $token = $token . "ΑΓΑΝ";
+ }
+ }
+
+ if (preg_match($re2, $token)) {
+ preg_match($re2, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $re2 = '/' . $v2 . '$/';
+ $exept7 = '/^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜ|Ν|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|'
+ . 'ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|'
+ . 'ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|'
+ . 'ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|'
+ . 'ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/';
+
+ if (preg_match($re2, $token) || preg_match($exept7, $token)) {
+ $token = $token . "ΑΝ";
+ }
+ }
+
+ // Step 5c
+ $re3 = '/^(.+?)(ΕΤΕ)$/';
+ $re4 = '/^(.+?)(ΗΣΕΤΕ)$/';
+
+ if (preg_match($re4, $token)) {
+ preg_match($re4, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ }
+
+ if (preg_match($re3, $token)) {
+ preg_match($re3, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $re3 = '/' . $v2 . '$/';
+ $exept8 = '/(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/';
+ $exept9 = '/^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/';
+
+ if (preg_match($re3, $token) || preg_match($exept8, $token) || preg_match($exept9, $token)) {
+ $token = $token . "ΕΤ";
+ }
+ }
+
+ // Step 5d
+ $re = '/^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept10 = '/^(ΑΡΧ)$/';
+ $exept11 = '/(ΚΡΕ)$/';
+
+ if (preg_match($exept10, $token)) {
+ $token = $token . "ΟΝΤ";
+ }
+
+ if (preg_match($exept11, $token)) {
+ $token = $token . "ΩΝΤ";
+ }
+ }
+
+ // Step 5e
+ $re = '/^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept11 = '/^(ΟΝ)$/';
+
+ if (preg_match($exept11, $token)) {
+ $token = $token . "ΟΜΑΣΤ";
+ }
+ }
+
+ // Step 5f
+ $re = '/^(.+?)(ΕΣΤΕ)$/';
+ $re2 = '/^(.+?)(ΙΕΣΤΕ)$/';
+
+ if (preg_match($re2, $token)) {
+ preg_match($re2, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $re2 = '/^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/';
+
+ if (preg_match($re2, $token)) {
+ $token = $token . "ΙΕΣΤ";
+ }
+ }
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept12 = '/^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΑΡ|ΠΡΟ|ΝΙΣ)$/';
+
+ if (preg_match($exept12, $token)) {
+ $token = $token . "ΕΣΤ";
+ }
+ }
+
+ // Step 5g
+ $re = '/^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/';
+ $re2 = '/^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/';
+
+ if (preg_match($re2, $token)) {
+ preg_match($re2, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ }
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept13 = '/(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/';
+ $exept14 = '/^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ|)$/';
+
+ if (preg_match($exept13, $token) || preg_match($exept14, $token)) {
+ $token = $token . "ΗΚ";
+ }
+ }
+
+ // Step 5h
+ $re = '/^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept15 = '/^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ|ΔΕ|ΔΕΥΤΕΡΕΥ|ΚΑΘΑΡΕΥ|ΠΛΕ|ΤΣΑ)$/';
+ $exept16 = '/(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/';
+
+ if (preg_match($exept15, $token) || preg_match($exept16, $token)) {
+ $token = $token . "ΟΥΣ";
+ }
+ }
+
+ // Step 5i
+ $re = '/^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept17 = '/^(ΨΟΦ|ΝΑΥΛΟΧ)$/';
+ $exept20 = '/(ΚΟΛΛ)$/';
+ $exept18 = '/^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|'
+ . 'ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/';
+ $exept19 = '/(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/';
+
+ if (
+ (preg_match($exept18, $token) || preg_match($exept19, $token))
+ && !(preg_match($exept17, $token) || preg_match($exept20, $token))
+ ) {
+ $token = $token . "ΑΓ";
+ }
+ }
+
+ // Step 5j
+ $re = '/^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept21 = '/^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ)$/';
+
+ if (preg_match($exept21, $token)) {
+ $token = $token . "ΗΣ";
+ }
+ }
+
+ // Step 5k
+ $re = '/^(.+?)(ΗΣΤΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept22 = '/^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/';
+
+ if (preg_match($exept22, $token)) {
+ $token = $token . "ΗΣΤ";
+ }
+ }
+
+ // Step 5l
+ $re = '/^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept23 = '/^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/';
+
+ if (preg_match($exept23, $token)) {
+ $token = $token . "ΟΥΝ";
+ }
+ }
+
+ // Step 5m
+ $re = '/^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ $test1 = false;
+ $exept24 = '/^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/';
+
+ if (preg_match($exept24, $token)) {
+ $token = $token . "ΟΥΜ";
+ }
+ }
+
+ // Step 6
+ $re = '/^(.+?)(ΜΑΤΑ|ΜΑΤΩΝ|ΜΑΤΟΣ)$/';
+ $re2 = '/^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|'
+ . 'ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|'
+ . 'ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|'
+ . 'ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ|ΥΣ|Ω|ΩΝ)$/';
+
+ if (preg_match($re, $token, $match)) {
+ $token = $match[1] . "ΜΑ";
+ }
+
+ if (preg_match($re2, $token) && $test1) {
+ preg_match($re2, $token, $match);
+ $token = $match[1];
+ }
+
+ // Step 7 (ΠΑΡΑΘΕΤΙΚΑ)
+ $re = '/^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/';
+
+ if (preg_match($re, $token)) {
+ preg_match($re, $token, $match);
+ $token = $match[1];
+ }
+
+ return $this->toLowerCase($token, $wCase);
+ }
+
+ /**
+ * Converts the token to uppercase, suppressing accents and diaeresis. The array $wCase contains a special map of
+ * the uppercase rule used to convert each character at each position.
+ *
+ * @param string $token Token to process
+ * @param array &$wCase Map of uppercase rules
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function toUpperCase($token, &$wCase)
+ {
+ $wCase = array_fill(0, mb_strlen($token, 'UTF-8'), 0);
+ $caseConvert = array(
+ "α" => 'Α',
+ "β" => 'Β',
+ "γ" => 'Γ',
+ "δ" => 'Δ',
+ "ε" => 'Ε',
+ "ζ" => 'Ζ',
+ "η" => 'Η',
+ "θ" => 'Θ',
+ "ι" => 'Ι',
+ "κ" => 'Κ',
+ "λ" => 'Λ',
+ "μ" => 'Μ',
+ "ν" => 'Ν',
+ "ξ" => 'Ξ',
+ "ο" => 'Ο',
+ "π" => 'Π',
+ "ρ" => 'Ρ',
+ "σ" => 'Σ',
+ "τ" => 'Τ',
+ "υ" => 'Υ',
+ "φ" => 'Φ',
+ "χ" => 'Χ',
+ "ψ" => 'Ψ',
+ "ω" => 'Ω',
+ "ά" => 'Α',
+ "έ" => 'Ε',
+ "ή" => 'Η',
+ "ί" => 'Ι',
+ "ό" => 'Ο',
+ "ύ" => 'Υ',
+ "ώ" => 'Ω',
+ "ς" => 'Σ',
+ "ϊ" => 'Ι',
+ "ϋ" => 'Ι',
+ "ΐ" => 'Ι',
+ "ΰ" => 'Υ',
+ );
+ $newToken = '';
+
+ for ($i = 0; $i < mb_strlen($token); $i++) {
+ $char = mb_substr($token, $i, 1);
+ $isLower = array_key_exists($char, $caseConvert);
+
+ if (!$isLower) {
+ $newToken .= $char;
+
+ continue;
+ }
+
+ $upperCase = $caseConvert[$char];
+ $newToken .= $upperCase;
+
+ $wCase[$i] = 1;
+
+ if (in_array($char, ['ά', 'έ', 'ή', 'ί', 'ό', 'ύ', 'ώ', 'ς'])) {
+ $wCase[$i] = 2;
+ }
+
+ if (in_array($char, ['ϊ', 'ϋ'])) {
+ $wCase[$i] = 3;
+ }
+
+ if (in_array($char, ['ΐ', 'ΰ'])) {
+ $wCase[$i] = 4;
+ }
+ }
+
+ return $newToken;
+ }
+
+ /**
+ * Converts the suppressed uppercase token back to lowercase, using the $wCase map to add back the accents,
+ * diaeresis and handle the special case of final sigma (different lowercase glyph than the regular sigma, only
+ * used at the end of words).
+ *
+ * @param string $token Token to process
+ * @param array $wCase Map of lowercase rules
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function toLowerCase($token, $wCase)
+ {
+ $newToken = '';
+
+ for ($i = 0; $i < mb_strlen($token); $i++) {
+ $char = mb_substr($token, $i, 1);
+
+ // Is $wCase not set at this position? We assume no case conversion ever took place.
+ if (!isset($wCase[$i])) {
+ $newToken .= $char;
+
+ continue;
+ }
+
+ // The character was not case-converted
+ if ($wCase[$i] == 0) {
+ $newToken .= $char;
+
+ continue;
+ }
+
+ // Case 1: Unaccented letter
+ if ($wCase[$i] == 1) {
+ $newToken .= mb_strtolower($char);
+
+ continue;
+ }
+
+ // Case 2: Vowel with accent (tonos); or the special case of final sigma
+ if ($wCase[$i] == 2) {
+ $charMap = [
+ 'Α' => 'ά',
+ 'Ε' => 'έ',
+ 'Η' => 'ή',
+ 'Ι' => 'ί',
+ 'Ο' => 'ό',
+ 'Υ' => 'ύ',
+ 'Ω' => 'ώ',
+ 'Σ' => 'ς'
+ ];
+
+ $newToken .= $charMap[$char];
+
+ continue;
+ }
+
+ // Case 3: vowels with diaeresis (dialytika)
+ if ($wCase[$i] == 3) {
+ $charMap = [
+ 'Ι' => 'ϊ',
+ 'Υ' => 'ϋ'
+ ];
+
+ $newToken .= $charMap[$char];
+
+ continue;
+ }
+
+ // Case 4: vowels with both diaeresis (dialytika) and accent (tonos)
+ if ($wCase[$i] == 4) {
+ $charMap = [
+ 'Ι' => 'ΐ',
+ 'Υ' => 'ΰ'
+ ];
+
+ $newToken .= $charMap[$char];
+
+ continue;
+ }
+
+ // This should never happen!
+ $newToken .= $char;
+ }
+
+ return $newToken;
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/Language/Zh.php b/administrator/components/com_finder/src/Indexer/Language/Zh.php
index 7e5bcbfa00551..3e4539cbf1f1f 100644
--- a/administrator/components/com_finder/src/Indexer/Language/Zh.php
+++ b/administrator/components/com_finder/src/Indexer/Language/Zh.php
@@ -1,4 +1,5 @@
clean($format, 'cmd');
-
- // Only create one parser for each format.
- if (isset(self::$instances[$format]))
- {
- return self::$instances[$format];
- }
-
- // Setup the adapter for the parser.
- $class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Parser\\' . ucfirst($format);
-
- // Check if a parser exists for the format.
- if (class_exists($class))
- {
- self::$instances[$format] = new $class;
-
- return self::$instances[$format];
- }
-
- // Throw invalid format exception.
- throw new \Exception(Text::sprintf('COM_FINDER_INDEXER_INVALID_PARSER', $format));
- }
-
- /**
- * Method to parse input and extract the plain text. Because this method is
- * called from both inside and outside the indexer, it needs to be able to
- * batch out its parsing functionality to deal with the inefficiencies of
- * regular expressions. We will parse recursively in 2KB chunks.
- *
- * @param string $input The input to parse.
- *
- * @return string The plain text input.
- *
- * @since 2.5
- */
- public function parse($input)
- {
- // If the input is less than 2KB we can parse it in one go.
- if (strlen($input) <= 2048)
- {
- return $this->process($input);
- }
-
- // Input is longer than 2Kb so parse it in chunks of 2Kb or less.
- $start = 0;
- $end = strlen($input);
- $chunk = 2048;
- $return = null;
-
- while ($start < $end)
- {
- // Setup the string.
- $string = substr($input, $start, $chunk);
-
- // Find the last space character if we aren't at the end.
- $ls = (($start + $chunk) < $end ? strrpos($string, ' ') : false);
-
- // Truncate to the last space character.
- if ($ls !== false)
- {
- $string = substr($string, 0, $ls);
- }
-
- // Adjust the start position for the next iteration.
- $start += ($ls !== false ? ($ls + 1 - $chunk) + $chunk : $chunk);
-
- // Parse the chunk.
- $return .= $this->process($string);
- }
-
- return $return;
- }
-
- /**
- * Method to process input and extract the plain text.
- *
- * @param string $input The input to process.
- *
- * @return string The plain text input.
- *
- * @since 2.5
- */
- abstract protected function process($input);
+ /**
+ * Parser support instances container.
+ *
+ * @var Parser[]
+ * @since 4.0.0
+ */
+ protected static $instances = array();
+
+ /**
+ * Method to get a parser, creating it if necessary.
+ *
+ * @param string $format The type of parser to load.
+ *
+ * @return Parser A Parser instance.
+ *
+ * @since 2.5
+ * @throws \Exception on invalid parser.
+ */
+ public static function getInstance($format)
+ {
+ $format = InputFilter::getInstance()->clean($format, 'cmd');
+
+ // Only create one parser for each format.
+ if (isset(self::$instances[$format])) {
+ return self::$instances[$format];
+ }
+
+ // Setup the adapter for the parser.
+ $class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Parser\\' . ucfirst($format);
+
+ // Check if a parser exists for the format.
+ if (class_exists($class)) {
+ self::$instances[$format] = new $class();
+
+ return self::$instances[$format];
+ }
+
+ // Throw invalid format exception.
+ throw new \Exception(Text::sprintf('COM_FINDER_INDEXER_INVALID_PARSER', $format));
+ }
+
+ /**
+ * Method to parse input and extract the plain text. Because this method is
+ * called from both inside and outside the indexer, it needs to be able to
+ * batch out its parsing functionality to deal with the inefficiencies of
+ * regular expressions. We will parse recursively in 2KB chunks.
+ *
+ * @param string $input The input to parse.
+ *
+ * @return string The plain text input.
+ *
+ * @since 2.5
+ */
+ public function parse($input)
+ {
+ // If the input is less than 2KB we can parse it in one go.
+ if (strlen($input) <= 2048) {
+ return $this->process($input);
+ }
+
+ // Input is longer than 2Kb so parse it in chunks of 2Kb or less.
+ $start = 0;
+ $end = strlen($input);
+ $chunk = 2048;
+ $return = null;
+
+ while ($start < $end) {
+ // Setup the string.
+ $string = substr($input, $start, $chunk);
+
+ // Find the last space character if we aren't at the end.
+ $ls = (($start + $chunk) < $end ? strrpos($string, ' ') : false);
+
+ // Truncate to the last space character.
+ if ($ls !== false) {
+ $string = substr($string, 0, $ls);
+ }
+
+ // Adjust the start position for the next iteration.
+ $start += ($ls !== false ? ($ls + 1 - $chunk) + $chunk : $chunk);
+
+ // Parse the chunk.
+ $return .= $this->process($string);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to process input and extract the plain text.
+ *
+ * @param string $input The input to process.
+ *
+ * @return string The plain text input.
+ *
+ * @since 2.5
+ */
+ abstract protected function process($input);
}
diff --git a/administrator/components/com_finder/src/Indexer/Parser/Html.php b/administrator/components/com_finder/src/Indexer/Parser/Html.php
index 2c59281085828..e5c11c095371d 100644
--- a/administrator/components/com_finder/src/Indexer/Parser/Html.php
+++ b/administrator/components/com_finder/src/Indexer/Parser/Html.php
@@ -1,4 +1,5 @@
and tags. Do this first
- // because there might be removeBlocks($input, 'removeBlocks($input, '
diff --git a/administrator/components/com_menus/helpers/menus.php b/administrator/components/com_menus/helpers/menus.php
index 570b89ca30513..41cbaa13a4231 100644
--- a/administrator/components/com_menus/helpers/menus.php
+++ b/administrator/components/com_menus/helpers/menus.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Menus component helper.
diff --git a/administrator/components/com_menus/layouts/joomla/menu/edit_modules.php b/administrator/components/com_menus/layouts/joomla/menu/edit_modules.php
index 2c4ca108534ee..a8d3e336658c1 100644
--- a/administrator/components/com_menus/layouts/joomla/menu/edit_modules.php
+++ b/administrator/components/com_menus/layouts/joomla/menu/edit_modules.php
@@ -1,4 +1,5 @@
input;
$component = $input->getCmd('option', 'com_content');
-if ($component == 'com_categories')
-{
- $extension = $input->getCmd('extension', 'com_content');
- $parts = explode('.', $extension);
- $component = $parts[0];
+if ($component == 'com_categories') {
+ $extension = $input->getCmd('extension', 'com_content');
+ $parts = explode('.', $extension);
+ $component = $parts[0];
}
$saveHistory = ComponentHelper::getParams($component)->get('save_history', 0);
$fields = $displayData->get('fields') ?: array(
- array('parent', 'parent_id'),
- array('published', 'state', 'enabled'),
- array('category', 'catid'),
- 'featured',
- 'sticky',
- 'access',
- 'language',
- 'tags',
- 'note',
- 'version_note',
+ array('parent', 'parent_id'),
+ array('published', 'state', 'enabled'),
+ array('category', 'catid'),
+ 'featured',
+ 'sticky',
+ 'access',
+ 'language',
+ 'tags',
+ 'note',
+ 'version_note',
);
$hiddenFields = $displayData->get('hidden_fields') ?: array();
-if (!$saveHistory)
-{
- $hiddenFields[] = 'version_note';
+if (!$saveHistory) {
+ $hiddenFields[] = 'version_note';
}
$html = array();
$html[] = '';
-foreach ($fields as $field)
-{
- $field = is_array($field) ? $field : array($field);
+foreach ($fields as $field) {
+ $field = is_array($field) ? $field : array($field);
- foreach ($field as $f)
- {
- if ($form->getField($f))
- {
- if (in_array($f, $hiddenFields))
- {
- $form->setFieldAttribute($f, 'type', 'hidden');
- }
+ foreach ($field as $f) {
+ if ($form->getField($f)) {
+ if (in_array($f, $hiddenFields)) {
+ $form->setFieldAttribute($f, 'type', 'hidden');
+ }
- $html[] = '' . $form->renderField($f) . ' ';
- break;
- }
- }
+ $html[] = '' . $form->renderField($f) . ' ';
+ break;
+ }
+ }
}
$html[] = ' ';
diff --git a/administrator/components/com_menus/layouts/joomla/searchtools/default.php b/administrator/components/com_menus/layouts/joomla/searchtools/default.php
index 9f0758024b86b..0901324668cab 100644
--- a/administrator/components/com_menus/layouts/joomla/searchtools/default.php
+++ b/administrator/components/com_menus/layouts/joomla/searchtools/default.php
@@ -1,4 +1,5 @@
filterForm) && !empty($data['view']->filterForm))
-{
- // Checks if a selector (e.g. client_id) exists.
- if ($selectorField = $data['view']->filterForm->getField($selectorFieldName))
- {
- $showSelector = $selectorField->getAttribute('filtermode', '') === 'selector' ? true : $showSelector;
-
- // Checks if a selector should be shown in the current layout.
- if (isset($data['view']->layout))
- {
- $showSelector = $selectorField->getAttribute('layout', 'default') != $data['view']->layout ? false : $showSelector;
- }
-
- // Unset the selector field from active filters group.
- unset($data['view']->activeFilters[$selectorFieldName]);
- }
-
- if ($data['view'] instanceof \Joomla\Component\Menus\Administrator\View\Items\HtmlView) :
- unset($data['view']->activeFilters['client_id']);
- endif;
-
- // Checks if the filters button should exist.
- $filters = $data['view']->filterForm->getGroup('filter');
- $showFilterButton = isset($filters['filter_search']) && count($filters) === 1 ? false : true;
-
- // Checks if it should show the be hidden.
- $hideActiveFilters = empty($data['view']->activeFilters);
-
- // Check if the no results message should appear.
- if (isset($data['view']->total) && (int) $data['view']->total === 0)
- {
- $noResults = $data['view']->filterForm->getFieldAttribute('search', 'noresults', '', 'filter');
- if (!empty($noResults))
- {
- $noResultsText = Text::_($noResults);
- }
- }
+if (isset($data['view']->filterForm) && !empty($data['view']->filterForm)) {
+ // Checks if a selector (e.g. client_id) exists.
+ if ($selectorField = $data['view']->filterForm->getField($selectorFieldName)) {
+ $showSelector = $selectorField->getAttribute('filtermode', '') === 'selector' ? true : $showSelector;
+
+ // Checks if a selector should be shown in the current layout.
+ if (isset($data['view']->layout)) {
+ $showSelector = $selectorField->getAttribute('layout', 'default') != $data['view']->layout ? false : $showSelector;
+ }
+
+ // Unset the selector field from active filters group.
+ unset($data['view']->activeFilters[$selectorFieldName]);
+ }
+
+ if ($data['view'] instanceof \Joomla\Component\Menus\Administrator\View\Items\HtmlView) :
+ unset($data['view']->activeFilters['client_id']);
+ endif;
+
+ // Checks if the filters button should exist.
+ $filters = $data['view']->filterForm->getGroup('filter');
+ $showFilterButton = isset($filters['filter_search']) && count($filters) === 1 ? false : true;
+
+ // Checks if it should show the be hidden.
+ $hideActiveFilters = empty($data['view']->activeFilters);
+
+ // Check if the no results message should appear.
+ if (isset($data['view']->total) && (int) $data['view']->total === 0) {
+ $noResults = $data['view']->filterForm->getFieldAttribute('search', 'noresults', '', 'filter');
+ if (!empty($noResults)) {
+ $noResultsText = Text::_($noResults);
+ }
+ }
}
// Set some basic options.
$customOptions = array(
- 'filtersHidden' => isset($data['options']['filtersHidden']) && $data['options']['filtersHidden'] ? $data['options']['filtersHidden'] : $hideActiveFilters,
- 'filterButton' => isset($data['options']['filterButton']) && $data['options']['filterButton'] ? $data['options']['filterButton'] : $showFilterButton,
- 'defaultLimit' => $data['options']['defaultLimit'] ?? Factory::getApplication()->get('list_limit', 20),
- 'searchFieldSelector' => '#filter_search',
- 'selectorFieldName' => $selectorFieldName,
- 'showSelector' => $showSelector,
- 'orderFieldSelector' => '#list_fullordering',
- 'showNoResults' => !empty($noResultsText),
- 'noResultsText' => !empty($noResultsText) ? $noResultsText : '',
- 'formSelector' => !empty($data['options']['formSelector']) ? $data['options']['formSelector'] : '#adminForm',
+ 'filtersHidden' => isset($data['options']['filtersHidden']) && $data['options']['filtersHidden'] ? $data['options']['filtersHidden'] : $hideActiveFilters,
+ 'filterButton' => isset($data['options']['filterButton']) && $data['options']['filterButton'] ? $data['options']['filterButton'] : $showFilterButton,
+ 'defaultLimit' => $data['options']['defaultLimit'] ?? Factory::getApplication()->get('list_limit', 20),
+ 'searchFieldSelector' => '#filter_search',
+ 'selectorFieldName' => $selectorFieldName,
+ 'showSelector' => $showSelector,
+ 'orderFieldSelector' => '#list_fullordering',
+ 'showNoResults' => !empty($noResultsText),
+ 'noResultsText' => !empty($noResultsText) ? $noResultsText : '',
+ 'formSelector' => !empty($data['options']['formSelector']) ? $data['options']['formSelector'] : '#adminForm',
);
// Merge custom options in the options array.
@@ -89,39 +85,39 @@
HTMLHelper::_('searchtools.form', $data['options']['formSelector'], $data['options']);
?>
- sublayout('noitems', $data); ?>
+ sublayout('noitems', $data); ?>
diff --git a/administrator/components/com_menus/services/provider.php b/administrator/components/com_menus/services/provider.php
index 3f6056d7734c4..2251234727ec5 100644
--- a/administrator/components/com_menus/services/provider.php
+++ b/administrator/components/com_menus/services/provider.php
@@ -1,4 +1,5 @@
set(AssociationExtensionInterface::class, new AssociationsHelper);
-
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Menus'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Menus'));
-
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MenusComponent($container->get(ComponentDispatcherFactoryInterface::class));
-
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
-
- return $component;
- }
- );
- }
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(AssociationExtensionInterface::class, new AssociationsHelper());
+
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Menus'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Menus'));
+
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MenusComponent($container->get(ComponentDispatcherFactoryInterface::class));
+
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
+
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_menus/src/Controller/AjaxController.php b/administrator/components/com_menus/src/Controller/AjaxController.php
index 32787bc54a12c..127fcca779e09 100644
--- a/administrator/components/com_menus/src/Controller/AjaxController.php
+++ b/administrator/components/com_menus/src/Controller/AjaxController.php
@@ -1,4 +1,5 @@
input->getInt('assocId', 0);
+ /**
+ * Method to fetch associations of a menu item
+ *
+ * The method assumes that the following http parameters are passed in an Ajax Get request:
+ * token: the form token
+ * assocId: the id of the menu item whose associations are to be returned
+ * excludeLang: the association for this language is to be excluded
+ *
+ * @return null
+ *
+ * @since 3.9.0
+ */
+ public function fetchAssociations()
+ {
+ if (!Session::checkToken('get')) {
+ echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
+ } else {
+ $assocId = $this->input->getInt('assocId', 0);
- if ($assocId == 0)
- {
- echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
+ if ($assocId == 0) {
+ echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
- return;
- }
+ return;
+ }
- $excludeLang = $this->input->get('excludeLang', '', 'STRING');
+ $excludeLang = $this->input->get('excludeLang', '', 'STRING');
- $associations = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', (int) $assocId, 'id', '', '');
+ $associations = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', (int) $assocId, 'id', '', '');
- unset($associations[$excludeLang]);
+ unset($associations[$excludeLang]);
- // Add the title to each of the associated records
- Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_menus/tables');
- $menuTable = Table::getInstance('Menu', 'JTable', array());
+ // Add the title to each of the associated records
+ Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_menus/tables');
+ $menuTable = Table::getInstance('Menu', 'JTable', array());
- foreach ($associations as $lang => $association)
- {
- $menuTable->load($association->id);
- $associations[$lang]->title = $menuTable->title;
- }
+ foreach ($associations as $lang => $association) {
+ $menuTable->load($association->id);
+ $associations[$lang]->title = $menuTable->title;
+ }
- $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
+ $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
- if (count($associations) == 0)
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
- }
- elseif ($countContentLanguages > count($associations) + 2)
- {
- $tags = implode(', ', array_keys($associations));
- $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
- }
- else
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
- }
+ if (count($associations) == 0) {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
+ } elseif ($countContentLanguages > count($associations) + 2) {
+ $tags = implode(', ', array_keys($associations));
+ $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
+ } else {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
+ }
- echo new JsonResponse($associations, $message);
- }
- }
+ echo new JsonResponse($associations, $message);
+ }
+ }
}
diff --git a/administrator/components/com_menus/src/Controller/DisplayController.php b/administrator/components/com_menus/src/Controller/DisplayController.php
index 4f6047b026eb7..7723dc6bccc05 100644
--- a/administrator/components/com_menus/src/Controller/DisplayController.php
+++ b/administrator/components/com_menus/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->post->getCmd('menutype', '');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array|boolean $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
+ *
+ * @return static This object to support chaining.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ // Verify menu
+ $menuType = $this->input->post->getCmd('menutype', '');
- if ($menuType !== '')
- {
- $uri = Uri::getInstance();
+ if ($menuType !== '') {
+ $uri = Uri::getInstance();
- if ($uri->getVar('menutype') !== $menuType)
- {
- $uri->setVar('menutype', $menuType);
+ if ($uri->getVar('menutype') !== $menuType) {
+ $uri->setVar('menutype', $menuType);
- if ($forcedLanguage = $this->input->post->get('forcedLanguage'))
- {
- $uri->setVar('forcedLanguage', $forcedLanguage);
- }
+ if ($forcedLanguage = $this->input->post->get('forcedLanguage')) {
+ $uri->setVar('forcedLanguage', $forcedLanguage);
+ }
- $this->setRedirect(Route::_('index.php' . $uri->toString(['query']), false));
+ $this->setRedirect(Route::_('index.php' . $uri->toString(['query']), false));
- return parent::display();
- }
- }
+ return parent::display();
+ }
+ }
- // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
- if ($langMissing = $this->getModel('Menus', 'Administrator')->getMissingModuleLanguages())
- {
- $this->app->enqueueMessage(Text::sprintf('JMENU_MULTILANG_WARNING_MISSING_MODULES', implode(', ', $langMissing)), 'warning');
- }
+ // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
+ if ($langMissing = $this->getModel('Menus', 'Administrator')->getMissingModuleLanguages()) {
+ $this->app->enqueueMessage(Text::sprintf('JMENU_MULTILANG_WARNING_MISSING_MODULES', implode(', ', $langMissing)), 'warning');
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_menus/src/Controller/ItemController.php b/administrator/components/com_menus/src/Controller/ItemController.php
index cc1b0a5682d9f..f88c2d1abe35b 100644
--- a/administrator/components/com_menus/src/Controller/ItemController.php
+++ b/administrator/components/com_menus/src/Controller/ItemController.php
@@ -1,4 +1,5 @@
app->getIdentity();
-
- $menuType = $this->input->getCmd('menutype', $data['menutype'] ?? '');
-
- $menutypeID = 0;
-
- // Load menutype ID
- if ($menuType)
- {
- $menutypeID = (int) $this->getMenuTypeId($menuType);
- }
-
- return $user->authorise('core.create', 'com_menus.menu.' . $menutypeID);
- }
-
- /**
- * Method to check if you edit a record.
- *
- * Extended classes can override this if necessary.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key; default is id.
- *
- * @return boolean
- *
- * @since 3.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $user = $this->app->getIdentity();
-
- $menutypeID = 0;
-
- if (isset($data[$key]))
- {
- $model = $this->getModel();
- $item = $model->getItem($data[$key]);
-
- if (!empty($item->menutype))
- {
- // Protected menutype, do not allow edit
- if ($item->menutype == 'main')
- {
- return false;
- }
-
- $menutypeID = (int) $this->getMenuTypeId($item->menutype);
- }
- }
-
- return $user->authorise('core.edit', 'com_menus.menu.' . (int) $menutypeID);
- }
-
- /**
- * Loads the menutype ID by a given menutype string
- *
- * @param string $menutype The given menutype
- *
- * @return integer
- *
- * @since 3.6
- */
- protected function getMenuTypeId($menutype)
- {
- $model = $this->getModel();
- $table = $model->getTable('MenuType');
-
- $table->load(array('menutype' => $menutype));
-
- return (int) $table->id;
- }
-
- /**
- * Method to add a new menu item.
- *
- * @return mixed True if the record can be added, otherwise false.
- *
- * @since 1.6
- */
- public function add()
- {
- $result = parent::add();
-
- if ($result)
- {
- $context = 'com_menus.edit.item';
-
- $this->app->setUserState($context . '.type', null);
- $this->app->setUserState($context . '.link', null);
- }
-
- return $result;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 1.6
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
- $model = $this->getModel('Item', 'Administrator', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_menus&view=items' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
-
- /**
- * Method to cancel an edit.
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return boolean True if access level checks pass, false otherwise.
- *
- * @since 1.6
- */
- public function cancel($key = null)
- {
- $this->checkToken();
-
- $result = parent::cancel();
-
- if ($result)
- {
- // Clear the ancillary data from the session.
- $context = 'com_menus.edit.item';
- $this->app->setUserState($context . '.type', null);
- $this->app->setUserState($context . '.link', null);
-
- // Redirect to the list screen.
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend()
- . '&menutype=' . $this->app->getUserState('com_menus.items.menutype'), false
- )
- );
- }
-
- return $result;
- }
-
- /**
- * Method to edit an existing record.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key
- * (sometimes required to avoid router collisions).
- *
- * @return boolean True if access level check and checkout passes, false otherwise.
- *
- * @since 1.6
- */
- public function edit($key = null, $urlVar = null)
- {
- $result = parent::edit();
-
- if ($result)
- {
- // Push the new ancillary data into the session.
- $this->app->setUserState('com_menus.edit.item.type', null);
- $this->app->setUserState('com_menus.edit.item.link', null);
- }
-
- return $result;
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 3.0.1
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId, $urlVar);
-
- if ($recordId)
- {
- /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
- $model = $this->getModel();
- $item = $model->getItem($recordId);
- $clientId = $item->client_id;
- $append = '&client_id=' . $clientId . $append;
- }
- else
- {
- $clientId = $this->input->get('client_id', '0', 'int');
- $menuType = $this->input->get('menutype', 'mainmenu', 'cmd');
- $append = '&client_id=' . $clientId . ($menuType ? '&menutype=' . $menuType : '') . $append;
- }
-
- return $append;
- }
-
- /**
- * Method to save a record.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 1.6
- */
- public function save($key = null, $urlVar = null)
- {
- // Check for request forgeries.
- $this->checkToken();
-
- /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
- $model = $this->getModel('Item', 'Administrator', array());
- $table = $model->getTable();
- $data = $this->input->post->get('jform', array(), 'array');
- $task = $this->getTask();
- $context = 'com_menus.edit.item';
- $app = $this->app;
-
- // Set the menutype should we need it.
- if ($data['menutype'] !== '')
- {
- $this->input->set('menutype', $data['menutype']);
- }
-
- // Determine the name of the primary key for the data.
- if (empty($key))
- {
- $key = $table->getKeyName();
- }
-
- // To avoid data collisions the urlVar may be different from the primary key.
- if (empty($urlVar))
- {
- $urlVar = $key;
- }
-
- $recordId = $this->input->getInt($urlVar);
-
- // Populate the row id from the session.
- $data[$key] = $recordId;
-
- // The save2copy task needs to be handled slightly differently.
- if ($task == 'save2copy')
- {
- // Check-in the original row.
- if ($model->checkin($data['id']) === false)
- {
- // Check-in failed, go back to the item and display a notice.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'warning');
-
- return false;
- }
-
- // Reset the ID and then treat the request as for Apply.
- $data['id'] = 0;
- $data['associations'] = array();
- $task = 'apply';
- }
-
- // Access check.
- if (!$this->allowSave($data, $key))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . $this->getRedirectToListAppend(), false
- )
- );
-
- return false;
- }
-
- // Validate the posted data.
- // This post is made up of two forms, one for the item and one for params.
- $form = $model->getForm($data);
-
- if (!$form)
- {
- throw new \Exception($model->getError(), 500);
- }
-
- if ($data['type'] == 'url')
- {
- $data['link'] = str_replace(array('"', '>', '<'), '', $data['link']);
-
- if (strstr($data['link'], ':'))
- {
- $segments = explode(':', $data['link']);
- $protocol = strtolower($segments[0]);
- $scheme = array(
- 'http', 'https', 'ftp', 'ftps', 'gopher', 'mailto',
- 'news', 'prospero', 'telnet', 'rlogin', 'tn3270', 'wais',
- 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax',
- 'modem', 'git', 'sms',
- );
-
- if (!in_array($protocol, $scheme))
- {
- $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'warning');
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId), false)
- );
-
- return false;
- }
- }
- }
-
- $data = $model->validate($form, $data);
-
- // Preprocess request fields to ensure that we remove not set or empty request params
- $request = $form->getGroup('request', true);
-
- // Check for the special 'request' entry.
- if ($data['type'] == 'component' && !empty($request))
- {
- $removeArgs = array();
-
- if (!isset($data['request']) || !is_array($data['request']))
- {
- $data['request'] = array();
- }
-
- foreach ($request as $field)
- {
- $fieldName = $field->getAttribute('name');
-
- if (!isset($data['request'][$fieldName]) || $data['request'][$fieldName] == '')
- {
- $removeArgs[$fieldName] = '';
- }
- }
-
- // Parse the submitted link arguments.
- $args = array();
- parse_str(parse_url($data['link'], PHP_URL_QUERY), $args);
-
- // Merge in the user supplied request arguments.
- $args = array_merge($args, $data['request']);
-
- // Remove the unused request params
- if (!empty($args) && !empty($removeArgs))
- {
- $args = array_diff_key($args, $removeArgs);
- }
-
- $data['link'] = 'index.php?' . urldecode(http_build_query($args, '', '&'));
- }
-
- // Check for validation errors.
- if ($data === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Save the data in the session.
- $app->setUserState('com_menus.edit.item.data', $data);
-
- // Redirect back to the edit screen.
- $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
- $this->setRedirect(Route::_($editUrl, false));
-
- return false;
- }
-
- // Attempt to save the data.
- if (!$model->save($data))
- {
- // Save the data in the session.
- $app->setUserState('com_menus.edit.item.data', $data);
-
- // Redirect back to the edit screen.
- $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
- $this->setRedirect(Route::_($editUrl, false));
-
- return false;
- }
-
- // Save succeeded, check-in the row.
- if ($model->checkin($data['id']) === false)
- {
- // Check-in failed, go back to the row and display a notice.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'warning');
- $redirectUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
- $this->setRedirect(Route::_($redirectUrl, false));
-
- return false;
- }
-
- $this->setMessage(Text::_('COM_MENUS_SAVE_SUCCESS'));
-
- // Redirect the user and adjust session state based on the chosen task.
- switch ($task)
- {
- case 'apply':
- // Set the row data in the session.
- $recordId = $model->getState($this->context . '.id');
- $this->holdEditId($context, $recordId);
- $app->setUserState('com_menus.edit.item.data', null);
- $app->setUserState('com_menus.edit.item.type', null);
- $app->setUserState('com_menus.edit.item.link', null);
-
- // Redirect back to the edit screen.
- $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
- $this->setRedirect(Route::_($editUrl, false));
- break;
-
- case 'save2new':
- // Clear the row id and data in the session.
- $this->releaseEditId($context, $recordId);
- $app->setUserState('com_menus.edit.item.data', null);
- $app->setUserState('com_menus.edit.item.type', null);
- $app->setUserState('com_menus.edit.item.link', null);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend(), false));
- break;
-
- default:
- // Clear the row id and data in the session.
- $this->releaseEditId($context, $recordId);
- $app->setUserState('com_menus.edit.item.data', null);
- $app->setUserState('com_menus.edit.item.type', null);
- $app->setUserState('com_menus.edit.item.link', null);
-
- // Redirect to the list screen.
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend()
- . '&menutype=' . $app->getUserState('com_menus.items.menutype'), false
- )
- );
- break;
- }
-
- return true;
- }
-
- /**
- * Sets the type of the menu item currently being edited.
- *
- * @return void
- *
- * @since 1.6
- */
- public function setType()
- {
- $this->checkToken();
-
- $app = $this->app;
-
- // Get the posted values from the request.
- $data = $this->input->post->get('jform', array(), 'array');
-
- // Get the type.
- $type = $data['type'];
-
- $type = json_decode(base64_decode($type));
- $title = $type->title ?? null;
- $recordId = $type->id ?? 0;
-
- $specialTypes = array('alias', 'separator', 'url', 'heading', 'container');
-
- if (!in_array($title, $specialTypes))
- {
- $title = 'component';
- }
- else
- {
- // Set correct component id to ensure proper 404 messages with system links
- $data['component_id'] = 0;
- }
-
- $app->setUserState('com_menus.edit.item.type', $title);
-
- if ($title == 'component')
- {
- if (isset($type->request))
- {
- // Clean component name
- $type->request->option = InputFilter::getInstance()->clean($type->request->option, 'CMD');
-
- $component = ComponentHelper::getComponent($type->request->option);
- $data['component_id'] = $component->id;
-
- $app->setUserState('com_menus.edit.item.link', 'index.php?' . Uri::buildQuery((array) $type->request));
- }
- }
- // If the type is alias you just need the item id from the menu item referenced.
- elseif ($title == 'alias')
- {
- $app->setUserState('com_menus.edit.item.link', 'index.php?Itemid=');
- }
-
- unset($data['request']);
-
- $data['type'] = $title;
-
- if ($this->input->get('fieldtype') == 'type')
- {
- $data['link'] = $app->getUserState('com_menus.edit.item.link');
- }
-
- // Save the data in the session.
- $app->setUserState('com_menus.edit.item.data', $data);
-
- $this->type = $type;
- $this->setRedirect(
- Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId), false)
- );
- }
-
- /**
- * Gets the parent items of the menu location currently.
- *
- * @return void
- *
- * @since 3.2
- */
- public function getParentItem()
- {
- $app = $this->app;
-
- $results = array();
- $menutype = $this->input->get->get('menutype');
-
- if ($menutype)
- {
- /** @var \Joomla\Component\Menus\Administrator\Model\ItemsModel $model */
- $model = $this->getModel('Items', 'Administrator', array());
- $model->getState();
- $model->setState('filter.menutype', $menutype);
- $model->setState('list.select', 'a.id, a.title, a.level');
- $model->setState('list.start', '0');
- $model->setState('list.limit', '0');
-
- $results = $model->getItems();
-
- // Pad the option text with spaces using depth level as a multiplier.
- for ($i = 0, $n = count($results); $i < $n; $i++)
- {
- $results[$i]->title = str_repeat(' - ', $results[$i]->level) . $results[$i]->title;
- }
- }
-
- // Output a \JSON object
- echo json_encode($results);
-
- $app->close();
- }
+ /**
+ * Method to check if you can add a new record.
+ *
+ * Extended classes can override this if necessary.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 3.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $user = $this->app->getIdentity();
+
+ $menuType = $this->input->getCmd('menutype', $data['menutype'] ?? '');
+
+ $menutypeID = 0;
+
+ // Load menutype ID
+ if ($menuType) {
+ $menutypeID = (int) $this->getMenuTypeId($menuType);
+ }
+
+ return $user->authorise('core.create', 'com_menus.menu.' . $menutypeID);
+ }
+
+ /**
+ * Method to check if you edit a record.
+ *
+ * Extended classes can override this if necessary.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key; default is id.
+ *
+ * @return boolean
+ *
+ * @since 3.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $user = $this->app->getIdentity();
+
+ $menutypeID = 0;
+
+ if (isset($data[$key])) {
+ $model = $this->getModel();
+ $item = $model->getItem($data[$key]);
+
+ if (!empty($item->menutype)) {
+ // Protected menutype, do not allow edit
+ if ($item->menutype == 'main') {
+ return false;
+ }
+
+ $menutypeID = (int) $this->getMenuTypeId($item->menutype);
+ }
+ }
+
+ return $user->authorise('core.edit', 'com_menus.menu.' . (int) $menutypeID);
+ }
+
+ /**
+ * Loads the menutype ID by a given menutype string
+ *
+ * @param string $menutype The given menutype
+ *
+ * @return integer
+ *
+ * @since 3.6
+ */
+ protected function getMenuTypeId($menutype)
+ {
+ $model = $this->getModel();
+ $table = $model->getTable('MenuType');
+
+ $table->load(array('menutype' => $menutype));
+
+ return (int) $table->id;
+ }
+
+ /**
+ * Method to add a new menu item.
+ *
+ * @return mixed True if the record can be added, otherwise false.
+ *
+ * @since 1.6
+ */
+ public function add()
+ {
+ $result = parent::add();
+
+ if ($result) {
+ $context = 'com_menus.edit.item';
+
+ $this->app->setUserState($context . '.type', null);
+ $this->app->setUserState($context . '.link', null);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 1.6
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
+ $model = $this->getModel('Item', 'Administrator', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=items' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Method to cancel an edit.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return boolean True if access level checks pass, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function cancel($key = null)
+ {
+ $this->checkToken();
+
+ $result = parent::cancel();
+
+ if ($result) {
+ // Clear the ancillary data from the session.
+ $context = 'com_menus.edit.item';
+ $this->app->setUserState($context . '.type', null);
+ $this->app->setUserState($context . '.link', null);
+
+ // Redirect to the list screen.
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend()
+ . '&menutype=' . $this->app->getUserState('com_menus.items.menutype'),
+ false
+ )
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key
+ * (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if access level check and checkout passes, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function edit($key = null, $urlVar = null)
+ {
+ $result = parent::edit();
+
+ if ($result) {
+ // Push the new ancillary data into the session.
+ $this->app->setUserState('com_menus.edit.item.type', null);
+ $this->app->setUserState('com_menus.edit.item.link', null);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 3.0.1
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId, $urlVar);
+
+ if ($recordId) {
+ /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
+ $model = $this->getModel();
+ $item = $model->getItem($recordId);
+ $clientId = $item->client_id;
+ $append = '&client_id=' . $clientId . $append;
+ } else {
+ $clientId = $this->input->get('client_id', '0', 'int');
+ $menuType = $this->input->get('menutype', 'mainmenu', 'cmd');
+ $append = '&client_id=' . $clientId . ($menuType ? '&menutype=' . $menuType : '') . $append;
+ }
+
+ return $append;
+ }
+
+ /**
+ * Method to save a record.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
+ $model = $this->getModel('Item', 'Administrator', array());
+ $table = $model->getTable();
+ $data = $this->input->post->get('jform', array(), 'array');
+ $task = $this->getTask();
+ $context = 'com_menus.edit.item';
+ $app = $this->app;
+
+ // Set the menutype should we need it.
+ if ($data['menutype'] !== '') {
+ $this->input->set('menutype', $data['menutype']);
+ }
+
+ // Determine the name of the primary key for the data.
+ if (empty($key)) {
+ $key = $table->getKeyName();
+ }
+
+ // To avoid data collisions the urlVar may be different from the primary key.
+ if (empty($urlVar)) {
+ $urlVar = $key;
+ }
+
+ $recordId = $this->input->getInt($urlVar);
+
+ // Populate the row id from the session.
+ $data[$key] = $recordId;
+
+ // The save2copy task needs to be handled slightly differently.
+ if ($task == 'save2copy') {
+ // Check-in the original row.
+ if ($model->checkin($data['id']) === false) {
+ // Check-in failed, go back to the item and display a notice.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'warning');
+
+ return false;
+ }
+
+ // Reset the ID and then treat the request as for Apply.
+ $data['id'] = 0;
+ $data['associations'] = array();
+ $task = 'apply';
+ }
+
+ // Access check.
+ if (!$this->allowSave($data, $key)) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . $this->getRedirectToListAppend(),
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Validate the posted data.
+ // This post is made up of two forms, one for the item and one for params.
+ $form = $model->getForm($data);
+
+ if (!$form) {
+ throw new \Exception($model->getError(), 500);
+ }
+
+ if ($data['type'] == 'url') {
+ $data['link'] = str_replace(array('"', '>', '<'), '', $data['link']);
+
+ if (strstr($data['link'], ':')) {
+ $segments = explode(':', $data['link']);
+ $protocol = strtolower($segments[0]);
+ $scheme = array(
+ 'http', 'https', 'ftp', 'ftps', 'gopher', 'mailto',
+ 'news', 'prospero', 'telnet', 'rlogin', 'tn3270', 'wais',
+ 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax',
+ 'modem', 'git', 'sms',
+ );
+
+ if (!in_array($protocol, $scheme)) {
+ $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'warning');
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId), false)
+ );
+
+ return false;
+ }
+ }
+ }
+
+ $data = $model->validate($form, $data);
+
+ // Preprocess request fields to ensure that we remove not set or empty request params
+ $request = $form->getGroup('request', true);
+
+ // Check for the special 'request' entry.
+ if ($data['type'] == 'component' && !empty($request)) {
+ $removeArgs = array();
+
+ if (!isset($data['request']) || !is_array($data['request'])) {
+ $data['request'] = array();
+ }
+
+ foreach ($request as $field) {
+ $fieldName = $field->getAttribute('name');
+
+ if (!isset($data['request'][$fieldName]) || $data['request'][$fieldName] == '') {
+ $removeArgs[$fieldName] = '';
+ }
+ }
+
+ // Parse the submitted link arguments.
+ $args = array();
+ parse_str(parse_url($data['link'], PHP_URL_QUERY), $args);
+
+ // Merge in the user supplied request arguments.
+ $args = array_merge($args, $data['request']);
+
+ // Remove the unused request params
+ if (!empty($args) && !empty($removeArgs)) {
+ $args = array_diff_key($args, $removeArgs);
+ }
+
+ $data['link'] = 'index.php?' . urldecode(http_build_query($args, '', '&'));
+ }
+
+ // Check for validation errors.
+ if ($data === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Save the data in the session.
+ $app->setUserState('com_menus.edit.item.data', $data);
+
+ // Redirect back to the edit screen.
+ $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
+ $this->setRedirect(Route::_($editUrl, false));
+
+ return false;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($data)) {
+ // Save the data in the session.
+ $app->setUserState('com_menus.edit.item.data', $data);
+
+ // Redirect back to the edit screen.
+ $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+ $this->setRedirect(Route::_($editUrl, false));
+
+ return false;
+ }
+
+ // Save succeeded, check-in the row.
+ if ($model->checkin($data['id']) === false) {
+ // Check-in failed, go back to the row and display a notice.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'warning');
+ $redirectUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
+ $this->setRedirect(Route::_($redirectUrl, false));
+
+ return false;
+ }
+
+ $this->setMessage(Text::_('COM_MENUS_SAVE_SUCCESS'));
+
+ // Redirect the user and adjust session state based on the chosen task.
+ switch ($task) {
+ case 'apply':
+ // Set the row data in the session.
+ $recordId = $model->getState($this->context . '.id');
+ $this->holdEditId($context, $recordId);
+ $app->setUserState('com_menus.edit.item.data', null);
+ $app->setUserState('com_menus.edit.item.type', null);
+ $app->setUserState('com_menus.edit.item.link', null);
+
+ // Redirect back to the edit screen.
+ $editUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId);
+ $this->setRedirect(Route::_($editUrl, false));
+ break;
+
+ case 'save2new':
+ // Clear the row id and data in the session.
+ $this->releaseEditId($context, $recordId);
+ $app->setUserState('com_menus.edit.item.data', null);
+ $app->setUserState('com_menus.edit.item.type', null);
+ $app->setUserState('com_menus.edit.item.link', null);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend(), false));
+ break;
+
+ default:
+ // Clear the row id and data in the session.
+ $this->releaseEditId($context, $recordId);
+ $app->setUserState('com_menus.edit.item.data', null);
+ $app->setUserState('com_menus.edit.item.type', null);
+ $app->setUserState('com_menus.edit.item.link', null);
+
+ // Redirect to the list screen.
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend()
+ . '&menutype=' . $app->getUserState('com_menus.items.menutype'),
+ false
+ )
+ );
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets the type of the menu item currently being edited.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function setType()
+ {
+ $this->checkToken();
+
+ $app = $this->app;
+
+ // Get the posted values from the request.
+ $data = $this->input->post->get('jform', array(), 'array');
+
+ // Get the type.
+ $type = $data['type'];
+
+ $type = json_decode(base64_decode($type));
+ $title = $type->title ?? null;
+ $recordId = $type->id ?? 0;
+
+ $specialTypes = array('alias', 'separator', 'url', 'heading', 'container');
+
+ if (!in_array($title, $specialTypes)) {
+ $title = 'component';
+ } else {
+ // Set correct component id to ensure proper 404 messages with system links
+ $data['component_id'] = 0;
+ }
+
+ $app->setUserState('com_menus.edit.item.type', $title);
+
+ if ($title == 'component') {
+ if (isset($type->request)) {
+ // Clean component name
+ $type->request->option = InputFilter::getInstance()->clean($type->request->option, 'CMD');
+
+ $component = ComponentHelper::getComponent($type->request->option);
+ $data['component_id'] = $component->id;
+
+ $app->setUserState('com_menus.edit.item.link', 'index.php?' . Uri::buildQuery((array) $type->request));
+ }
+ } elseif ($title == 'alias') {
+ // If the type is alias you just need the item id from the menu item referenced.
+ $app->setUserState('com_menus.edit.item.link', 'index.php?Itemid=');
+ }
+
+ unset($data['request']);
+
+ $data['type'] = $title;
+
+ if ($this->input->get('fieldtype') == 'type') {
+ $data['link'] = $app->getUserState('com_menus.edit.item.link');
+ }
+
+ // Save the data in the session.
+ $app->setUserState('com_menus.edit.item.data', $data);
+
+ $this->type = $type;
+ $this->setRedirect(
+ Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId), false)
+ );
+ }
+
+ /**
+ * Gets the parent items of the menu location currently.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function getParentItem()
+ {
+ $app = $this->app;
+
+ $results = array();
+ $menutype = $this->input->get->get('menutype');
+
+ if ($menutype) {
+ /** @var \Joomla\Component\Menus\Administrator\Model\ItemsModel $model */
+ $model = $this->getModel('Items', 'Administrator', array());
+ $model->getState();
+ $model->setState('filter.menutype', $menutype);
+ $model->setState('list.select', 'a.id, a.title, a.level');
+ $model->setState('list.start', '0');
+ $model->setState('list.limit', '0');
+
+ $results = $model->getItems();
+
+ // Pad the option text with spaces using depth level as a multiplier.
+ for ($i = 0, $n = count($results); $i < $n; $i++) {
+ $results[$i]->title = str_repeat(' - ', $results[$i]->level) . $results[$i]->title;
+ }
+ }
+
+ // Output a \JSON object
+ echo json_encode($results);
+
+ $app->close();
+ }
}
diff --git a/administrator/components/com_menus/src/Controller/ItemsController.php b/administrator/components/com_menus/src/Controller/ItemsController.php
index 484ee17a0ba5c..7d9556809bf3d 100644
--- a/administrator/components/com_menus/src/Controller/ItemsController.php
+++ b/administrator/components/com_menus/src/Controller/ItemsController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Menus\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Menus\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -27,247 +27,219 @@
*/
class ItemsController extends AdminController
{
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 1.6
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- $this->registerTask('unsetDefault', 'setDefault');
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return object The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Item', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to get the number of published frontend menu items for quickicons
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('Items');
-
- $model->setState('filter.published', 1);
- $model->setState('filter.client_id', 0);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_MENUS_ITEMS_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_MENUS_ITEMS_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
-
- /**
- * Rebuild the nested set tree.
- *
- * @return boolean False on failure or error, true on success.
- *
- * @since 1.6
- */
- public function rebuild()
- {
- $this->checkToken();
-
- $this->setRedirect('index.php?option=com_menus&view=items&menutype=' . $this->input->getCmd('menutype'));
-
- /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
- $model = $this->getModel();
-
- if ($model->rebuild())
- {
- // Reorder succeeded.
- $this->setMessage(Text::_('COM_MENUS_ITEMS_REBUILD_SUCCESS'));
-
- return true;
- }
- else
- {
- // Rebuild failed.
- $this->setMessage(Text::sprintf('COM_MENUS_ITEMS_REBUILD_FAILED'), 'error');
-
- return false;
- }
- }
-
- /**
- * Method to set the home property for a list of items
- *
- * @return void
- *
- * @since 1.6
- */
- public function setDefault()
- {
- // Check for request forgeries
- $this->checkToken('request');
-
- $app = $this->app;
-
- // Get items to publish from the request.
- $cid = (array) $this->input->get('cid', array(), 'int');
- $data = array('setDefault' => 1, 'unsetDefault' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($data, $task, 0, 'int');
-
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
-
- if (empty($cid))
- {
- $this->setMessage(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Publish the items.
- if (!$model->setHome($cid, $value))
- {
- $this->setMessage($model->getError(), 'warning');
- }
- else
- {
- if ($value == 1)
- {
- $ntext = 'COM_MENUS_ITEMS_SET_HOME';
- }
- else
- {
- $ntext = 'COM_MENUS_ITEMS_UNSET_HOME';
- }
-
- $this->setMessage(Text::plural($ntext, count($cid)));
- }
- }
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . '&menutype=' . $app->getUserState('com_menus.items.menutype'), false
- )
- );
- }
-
- /**
- * Method to publish a list of items
- *
- * @return void
- *
- * @since 3.6.0
- */
- public function publish()
- {
- // Check for request forgeries
- $this->checkToken();
-
- // Get items to publish from the request.
- $cid = (array) $this->input->get('cid', array(), 'int');
- $data = array('publish' => 1, 'unpublish' => 0, 'trash' => -2, 'report' => -3);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($data, $task, 0, 'int');
-
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
-
- if (empty($cid))
- {
- try
- {
- Log::add(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- $this->setMessage(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), 'warning');
- }
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Publish the items.
- try
- {
- $model->publish($cid, $value);
- $errors = $model->getErrors();
- $messageType = 'message';
-
- if ($value == 1)
- {
- if ($errors)
- {
- $messageType = 'error';
- $ntext = $this->text_prefix . '_N_ITEMS_FAILED_PUBLISHING';
- }
- else
- {
- $ntext = $this->text_prefix . '_N_ITEMS_PUBLISHED';
- }
- }
- elseif ($value == 0)
- {
- $ntext = $this->text_prefix . '_N_ITEMS_UNPUBLISHED';
- }
- else
- {
- $ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
- }
-
- $this->setMessage(Text::plural($ntext, count($cid)), $messageType);
- }
- catch (\Exception $e)
- {
- $this->setMessage($e->getMessage(), 'error');
- }
- }
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list . '&menutype=' .
- $this->app->getUserState('com_menus.items.menutype'),
- false
- )
- );
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- return '&menutype=' . $this->app->getUserState('com_menus.items.menutype');
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('unsetDefault', 'setDefault');
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Item', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to get the number of published frontend menu items for quickicons
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Items');
+
+ $model->setState('filter.published', 1);
+ $model->setState('filter.client_id', 0);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_MENUS_ITEMS_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_MENUS_ITEMS_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
+
+ /**
+ * Rebuild the nested set tree.
+ *
+ * @return boolean False on failure or error, true on success.
+ *
+ * @since 1.6
+ */
+ public function rebuild()
+ {
+ $this->checkToken();
+
+ $this->setRedirect('index.php?option=com_menus&view=items&menutype=' . $this->input->getCmd('menutype'));
+
+ /** @var \Joomla\Component\Menus\Administrator\Model\ItemModel $model */
+ $model = $this->getModel();
+
+ if ($model->rebuild()) {
+ // Reorder succeeded.
+ $this->setMessage(Text::_('COM_MENUS_ITEMS_REBUILD_SUCCESS'));
+
+ return true;
+ } else {
+ // Rebuild failed.
+ $this->setMessage(Text::sprintf('COM_MENUS_ITEMS_REBUILD_FAILED'), 'error');
+
+ return false;
+ }
+ }
+
+ /**
+ * Method to set the home property for a list of items
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function setDefault()
+ {
+ // Check for request forgeries
+ $this->checkToken('request');
+
+ $app = $this->app;
+
+ // Get items to publish from the request.
+ $cid = (array) $this->input->get('cid', array(), 'int');
+ $data = array('setDefault' => 1, 'unsetDefault' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($data, $task, 0, 'int');
+
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
+
+ if (empty($cid)) {
+ $this->setMessage(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Publish the items.
+ if (!$model->setHome($cid, $value)) {
+ $this->setMessage($model->getError(), 'warning');
+ } else {
+ if ($value == 1) {
+ $ntext = 'COM_MENUS_ITEMS_SET_HOME';
+ } else {
+ $ntext = 'COM_MENUS_ITEMS_UNSET_HOME';
+ }
+
+ $this->setMessage(Text::plural($ntext, count($cid)));
+ }
+ }
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . '&menutype=' . $app->getUserState('com_menus.items.menutype'),
+ false
+ )
+ );
+ }
+
+ /**
+ * Method to publish a list of items
+ *
+ * @return void
+ *
+ * @since 3.6.0
+ */
+ public function publish()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get items to publish from the request.
+ $cid = (array) $this->input->get('cid', array(), 'int');
+ $data = array('publish' => 1, 'unpublish' => 0, 'trash' => -2, 'report' => -3);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($data, $task, 0, 'int');
+
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
+
+ if (empty($cid)) {
+ try {
+ Log::add(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ $this->setMessage(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), 'warning');
+ }
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Publish the items.
+ try {
+ $model->publish($cid, $value);
+ $errors = $model->getErrors();
+ $messageType = 'message';
+
+ if ($value == 1) {
+ if ($errors) {
+ $messageType = 'error';
+ $ntext = $this->text_prefix . '_N_ITEMS_FAILED_PUBLISHING';
+ } else {
+ $ntext = $this->text_prefix . '_N_ITEMS_PUBLISHED';
+ }
+ } elseif ($value == 0) {
+ $ntext = $this->text_prefix . '_N_ITEMS_UNPUBLISHED';
+ } else {
+ $ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
+ }
+
+ $this->setMessage(Text::plural($ntext, count($cid)), $messageType);
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list . '&menutype=' .
+ $this->app->getUserState('com_menus.items.menutype'),
+ false
+ )
+ );
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ return '&menutype=' . $this->app->getUserState('com_menus.items.menutype');
+ }
}
diff --git a/administrator/components/com_menus/src/Controller/MenuController.php b/administrator/components/com_menus/src/Controller/MenuController.php
index c2d2cd536c49b..9a087bbce09d6 100644
--- a/administrator/components/com_menus/src/Controller/MenuController.php
+++ b/administrator/components/com_menus/src/Controller/MenuController.php
@@ -1,4 +1,5 @@
setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
- }
-
- /**
- * Method to save a menu item.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 1.6
- */
- public function save($key = null, $urlVar = null)
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $app = $this->app;
- $data = $this->input->post->get('jform', array(), 'array');
- $context = 'com_menus.edit.menu';
- $task = $this->getTask();
- $recordId = $this->input->getInt('id');
-
- // Prevent using 'main' as menutype as this is reserved for backend menus
- if (strtolower($data['menutype']) == 'main')
- {
- $this->setMessage(Text::_('COM_MENUS_ERROR_MENUTYPE'), 'error');
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
-
- return false;
- }
-
- $data['menutype'] = InputFilter::getInstance()->clean($data['menutype'], 'TRIM');
-
- // Populate the row id from the session.
- $data['id'] = $recordId;
-
- // Get the model and attempt to validate the posted data.
- /** @var \Joomla\Component\Menus\Administrator\Model\MenuModel $model */
- $model = $this->getModel('Menu', '', ['ignore_request' => false]);
- $form = $model->getForm();
-
- if (!$form)
- {
- throw new \Exception($model->getError(), 500);
- }
-
- $validData = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($validData === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Save the data in the session.
- $app->setUserState($context . '.data', $data);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
-
- return false;
- }
-
- if (isset($validData['preset']))
- {
- $preset = trim($validData['preset']) ?: null;
-
- unset($validData['preset']);
- }
-
- // Attempt to save the data.
- if (!$model->save($validData))
- {
- // Save the data in the session.
- $app->setUserState($context . '.data', $validData);
-
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
-
- return false;
- }
-
- // Import the preset selected
- if (isset($preset) && $data['client_id'] == 1)
- {
- // Menu Type has not been saved yet. Make sure items get the real menutype.
- $menutype = ApplicationHelper::stringURLSafe($data['menutype']);
-
- try
- {
- MenusHelper::installPreset($preset, $menutype);
-
- $this->setMessage(Text::_('COM_MENUS_PRESET_IMPORT_SUCCESS'));
- }
- catch (\Exception $e)
- {
- // Save was successful but the preset could not be loaded. Let it through with just a warning
- $this->setMessage(Text::sprintf('COM_MENUS_PRESET_IMPORT_FAILED', $e->getMessage()));
- }
- }
- else
- {
- $this->setMessage(Text::_('COM_MENUS_MENU_SAVE_SUCCESS'));
- }
-
- // Redirect the user and adjust session state based on the chosen task.
- switch ($task)
- {
- case 'apply':
- // Set the record data in the session.
- $recordId = $model->getState($this->context . '.id');
- $this->holdEditId($context, $recordId);
- $app->setUserState($context . '.data', null);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
- break;
-
- case 'save2new':
- // Clear the record id and data from the session.
- $this->releaseEditId($context, $recordId);
- $app->setUserState($context . '.data', null);
-
- // Redirect back to the edit screen.
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit', false));
- break;
-
- default:
- // Clear the record id and data from the session.
- $this->releaseEditId($context, $recordId);
- $app->setUserState($context . '.data', null);
-
- // Redirect to the list screen.
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
- break;
- }
- }
-
- /**
- * Method to display a menu as preset xml.
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 3.8.0
- */
- public function exportXml()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $cid = (array) $this->input->get('cid', array(), 'int');
-
- // We know the first element is the one we need because we don't allow multi selection of rows
- $id = empty($cid) ? 0 : reset($cid);
-
- if ($id === 0)
- {
- $this->setMessage(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), 'warning');
-
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
-
- return false;
- }
-
- $model = $this->getModel('Menu');
- $item = $model->getItem($id);
-
- if (!$item->menutype)
- {
- $this->setMessage(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), 'warning');
-
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
-
- return false;
- }
-
- $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&menutype=' . $item->menutype . '&format=xml', false));
-
- return true;
- }
+ /**
+ * Dummy method to redirect back to standard controller
+ *
+ * @param boolean $cachable If true, the view output will be cached.
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return void
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
+ }
+
+ /**
+ * Method to save a menu item.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $app = $this->app;
+ $data = $this->input->post->get('jform', array(), 'array');
+ $context = 'com_menus.edit.menu';
+ $task = $this->getTask();
+ $recordId = $this->input->getInt('id');
+
+ // Prevent using 'main' as menutype as this is reserved for backend menus
+ if (strtolower($data['menutype']) == 'main') {
+ $this->setMessage(Text::_('COM_MENUS_ERROR_MENUTYPE'), 'error');
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
+
+ return false;
+ }
+
+ $data['menutype'] = InputFilter::getInstance()->clean($data['menutype'], 'TRIM');
+
+ // Populate the row id from the session.
+ $data['id'] = $recordId;
+
+ // Get the model and attempt to validate the posted data.
+ /** @var \Joomla\Component\Menus\Administrator\Model\MenuModel $model */
+ $model = $this->getModel('Menu', '', ['ignore_request' => false]);
+ $form = $model->getForm();
+
+ if (!$form) {
+ throw new \Exception($model->getError(), 500);
+ }
+
+ $validData = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($validData === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Save the data in the session.
+ $app->setUserState($context . '.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
+
+ return false;
+ }
+
+ if (isset($validData['preset'])) {
+ $preset = trim($validData['preset']) ?: null;
+
+ unset($validData['preset']);
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($validData)) {
+ // Save the data in the session.
+ $app->setUserState($context . '.data', $validData);
+
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
+
+ return false;
+ }
+
+ // Import the preset selected
+ if (isset($preset) && $data['client_id'] == 1) {
+ // Menu Type has not been saved yet. Make sure items get the real menutype.
+ $menutype = ApplicationHelper::stringURLSafe($data['menutype']);
+
+ try {
+ MenusHelper::installPreset($preset, $menutype);
+
+ $this->setMessage(Text::_('COM_MENUS_PRESET_IMPORT_SUCCESS'));
+ } catch (\Exception $e) {
+ // Save was successful but the preset could not be loaded. Let it through with just a warning
+ $this->setMessage(Text::sprintf('COM_MENUS_PRESET_IMPORT_FAILED', $e->getMessage()));
+ }
+ } else {
+ $this->setMessage(Text::_('COM_MENUS_MENU_SAVE_SUCCESS'));
+ }
+
+ // Redirect the user and adjust session state based on the chosen task.
+ switch ($task) {
+ case 'apply':
+ // Set the record data in the session.
+ $recordId = $model->getState($this->context . '.id');
+ $this->holdEditId($context, $recordId);
+ $app->setUserState($context . '.data', null);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit' . $this->getRedirectToItemAppend($recordId), false));
+ break;
+
+ case 'save2new':
+ // Clear the record id and data from the session.
+ $this->releaseEditId($context, $recordId);
+ $app->setUserState($context . '.data', null);
+
+ // Redirect back to the edit screen.
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&layout=edit', false));
+ break;
+
+ default:
+ // Clear the record id and data from the session.
+ $this->releaseEditId($context, $recordId);
+ $app->setUserState($context . '.data', null);
+
+ // Redirect to the list screen.
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
+ break;
+ }
+ }
+
+ /**
+ * Method to display a menu as preset xml.
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 3.8.0
+ */
+ public function exportXml()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $cid = (array) $this->input->get('cid', array(), 'int');
+
+ // We know the first element is the one we need because we don't allow multi selection of rows
+ $id = empty($cid) ? 0 : reset($cid);
+
+ if ($id === 0) {
+ $this->setMessage(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), 'warning');
+
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
+
+ return false;
+ }
+
+ $model = $this->getModel('Menu');
+ $item = $model->getItem($id);
+
+ if (!$item->menutype) {
+ $this->setMessage(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), 'warning');
+
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menus', false));
+
+ return false;
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_menus&view=menu&menutype=' . $item->menutype . '&format=xml', false));
+
+ return true;
+ }
}
diff --git a/administrator/components/com_menus/src/Controller/MenusController.php b/administrator/components/com_menus/src/Controller/MenusController.php
index 3bde6a0490a23..44e3bdfbf477b 100644
--- a/administrator/components/com_menus/src/Controller/MenusController.php
+++ b/administrator/components/com_menus/src/Controller/MenusController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Remove an item.
- *
- * @return void
- *
- * @since 1.6
- */
- public function delete()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $user = $this->app->getIdentity();
- $cids = (array) $this->input->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $cids = array_filter($cids);
-
- if (empty($cids))
- {
- $this->setMessage(Text::_('COM_MENUS_NO_MENUS_SELECTED'), 'warning');
- }
- else
- {
- // Access checks.
- foreach ($cids as $i => $id)
- {
- if (!$user->authorise('core.delete', 'com_menus.menu.' . (int) $id))
- {
- // Prune items that you can't change.
- unset($cids[$i]);
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'error');
- }
- }
-
- if (count($cids) > 0)
- {
- // Get the model.
- /** @var \Joomla\Component\Menus\Administrator\Model\MenuModel $model */
- $model = $this->getModel();
-
- // Remove the items.
- if (!$model->delete($cids))
- {
- $this->setMessage($model->getError(), 'error');
- }
- else
- {
- $this->setMessage(Text::plural('COM_MENUS_N_MENUS_DELETED', count($cids)));
- }
- }
- }
-
- $this->setRedirect('index.php?option=com_menus&view=menus');
- }
-
- /**
- * Temporary method. This should go into the 1.5 to 1.6 upgrade routines.
- *
- * @return void
- *
- * @since 1.6
- *
- * @deprecated 5.0 Will be removed without replacement as it was only used for the 1.5 to 1.6 upgrade
- */
- public function resync()
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $parts = null;
-
- try
- {
- $query->select(
- [
- $db->quoteName('element'),
- $db->quoteName('extension_id'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
- $db->setQuery($query);
-
- $components = $db->loadAssocList('element', 'extension_id');
- }
- catch (\RuntimeException $e)
- {
- $this->setMessage($e->getMessage(), 'warning');
-
- return;
- }
-
- // Load all the component menu links
- $query->select(
- [
- $db->quoteName('id'),
- $db->quoteName('link'),
- $db->quoteName('component_id'),
- ]
- )
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('component.item'));
- $db->setQuery($query);
-
- try
- {
- $items = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $this->setMessage($e->getMessage(), 'warning');
-
- return;
- }
-
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('component_id') . ' = :componentId')
- ->where($db->quoteName('id') . ' = :itemId')
- ->bind(':componentId', $componentId, ParameterType::INTEGER)
- ->bind(':itemId', $itemId, ParameterType::INTEGER);
-
- foreach ($items as $item)
- {
- // Parse the link.
- parse_str(parse_url($item->link, PHP_URL_QUERY), $parts);
- $itemId = $item->id;
-
- // Tease out the option.
- if (isset($parts['option']))
- {
- $option = $parts['option'];
-
- // Lookup the component ID
- if (isset($components[$option]))
- {
- $componentId = $components[$option];
- }
- else
- {
- // Mismatch. Needs human intervention.
- $componentId = -1;
- }
-
- // Check for mis-matched component ids in the menu link.
- if ($item->component_id != $componentId)
- {
- // Update the menu table.
- $log = "Link $item->id refers to $item->component_id, converting to $componentId ($item->link)";
- echo " $log";
-
- try
- {
- $db->setQuery($query)->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setMessage($e->getMessage(), 'warning');
-
- return;
- }
- }
- }
- }
- }
+ /**
+ * Display the view
+ *
+ * @param boolean $cachable If true, the view output will be cached.
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ }
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Menu', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Remove an item.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function delete()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $user = $this->app->getIdentity();
+ $cids = (array) $this->input->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $cids = array_filter($cids);
+
+ if (empty($cids)) {
+ $this->setMessage(Text::_('COM_MENUS_NO_MENUS_SELECTED'), 'warning');
+ } else {
+ // Access checks.
+ foreach ($cids as $i => $id) {
+ if (!$user->authorise('core.delete', 'com_menus.menu.' . (int) $id)) {
+ // Prune items that you can't change.
+ unset($cids[$i]);
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'error');
+ }
+ }
+
+ if (count($cids) > 0) {
+ // Get the model.
+ /** @var \Joomla\Component\Menus\Administrator\Model\MenuModel $model */
+ $model = $this->getModel();
+
+ // Remove the items.
+ if (!$model->delete($cids)) {
+ $this->setMessage($model->getError(), 'error');
+ } else {
+ $this->setMessage(Text::plural('COM_MENUS_N_MENUS_DELETED', count($cids)));
+ }
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_menus&view=menus');
+ }
+
+ /**
+ * Temporary method. This should go into the 1.5 to 1.6 upgrade routines.
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @deprecated 5.0 Will be removed without replacement as it was only used for the 1.5 to 1.6 upgrade
+ */
+ public function resync()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $parts = null;
+
+ try {
+ $query->select(
+ [
+ $db->quoteName('element'),
+ $db->quoteName('extension_id'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
+ $db->setQuery($query);
+
+ $components = $db->loadAssocList('element', 'extension_id');
+ } catch (\RuntimeException $e) {
+ $this->setMessage($e->getMessage(), 'warning');
+
+ return;
+ }
+
+ // Load all the component menu links
+ $query->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('link'),
+ $db->quoteName('component_id'),
+ ]
+ )
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('component.item'));
+ $db->setQuery($query);
+
+ try {
+ $items = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $this->setMessage($e->getMessage(), 'warning');
+
+ return;
+ }
+
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('component_id') . ' = :componentId')
+ ->where($db->quoteName('id') . ' = :itemId')
+ ->bind(':componentId', $componentId, ParameterType::INTEGER)
+ ->bind(':itemId', $itemId, ParameterType::INTEGER);
+
+ foreach ($items as $item) {
+ // Parse the link.
+ parse_str(parse_url($item->link, PHP_URL_QUERY), $parts);
+ $itemId = $item->id;
+
+ // Tease out the option.
+ if (isset($parts['option'])) {
+ $option = $parts['option'];
+
+ // Lookup the component ID
+ if (isset($components[$option])) {
+ $componentId = $components[$option];
+ } else {
+ // Mismatch. Needs human intervention.
+ $componentId = -1;
+ }
+
+ // Check for mis-matched component ids in the menu link.
+ if ($item->component_id != $componentId) {
+ // Update the menu table.
+ $log = "Link $item->id refers to $item->component_id, converting to $componentId ($item->link)";
+ echo " $log";
+
+ try {
+ $db->setQuery($query)->execute();
+ } catch (\RuntimeException $e) {
+ $this->setMessage($e->getMessage(), 'warning');
+
+ return;
+ }
+ }
+ }
+ }
+ }
}
diff --git a/administrator/components/com_menus/src/Extension/MenusComponent.php b/administrator/components/com_menus/src/Extension/MenusComponent.php
index cc611ed3f26e3..f634cf07a6c3a 100644
--- a/administrator/components/com_menus/src/Extension/MenusComponent.php
+++ b/administrator/components/com_menus/src/Extension/MenusComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('menus', new Menus);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('menus', new Menus());
+ }
}
diff --git a/administrator/components/com_menus/src/Field/MenuItemByTypeField.php b/administrator/components/com_menus/src/Field/MenuItemByTypeField.php
index 5df789ff8640e..f9f2b3f994cf7 100644
--- a/administrator/components/com_menus/src/Field/MenuItemByTypeField.php
+++ b/administrator/components/com_menus/src/Field/MenuItemByTypeField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.8.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'menuType':
- $this->menuType = (string) $value;
- break;
-
- case 'clientId':
- $this->clientId = (int) $value;
- break;
-
- case 'language':
- case 'published':
- case 'disable':
- $value = (string) $value;
- $this->$name = $value ? explode(',', $value) : array();
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a JForm object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see \Joomla\CMS\Form\FormField::setup()
- * @since 3.8.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result == true)
- {
- $menuType = (string) $this->element['menu_type'];
-
- if (!$menuType)
- {
- $app = Factory::getApplication();
- $currentMenuType = $app->getUserState('com_menus.items.menutype', '');
- $menuType = $app->input->getString('menutype', $currentMenuType);
- }
-
- $this->menuType = $menuType;
- $this->clientId = (int) $this->element['client_id'];
- $this->published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array();
- $this->disable = $this->element['disable'] ? explode(',', (string) $this->element['disable']) : array();
- $this->language = $this->element['language'] ? explode(',', (string) $this->element['language']) : array();
- }
-
- return $result;
- }
-
- /**
- * Method to get the field option groups.
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 3.8.0
- */
- protected function getGroups()
- {
- $groups = array();
-
- $menuType = $this->menuType;
-
- // Get the menu items.
- $items = MenusHelper::getMenuLinks($menuType, 0, 0, $this->published, $this->language, $this->clientId);
-
- // Build group for a specific menu type.
- if ($menuType)
- {
- // If the menutype is empty, group the items by menutype.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__menu_types'))
- ->where($db->quoteName('menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
- $db->setQuery($query);
-
- try
- {
- $menuTitle = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $menuTitle = $menuType;
- }
-
- // Initialize the group.
- $groups[$menuTitle] = array();
-
- // Build the options array.
- foreach ($items as $key => $link)
- {
- // Unset if item is menu_item_root
- if ($link->text === 'Menu_Item_Root')
- {
- unset($items[$key]);
- continue;
- }
-
- $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
-
- // Displays language code if not set to All
- if ($link->language !== '*')
- {
- $lang = ' (' . $link->language . ')';
- }
- else
- {
- $lang = '';
- }
-
- $groups[$menuTitle][] = HTMLHelper::_('select.option',
- $link->value, $levelPrefix . $link->text . $lang,
- 'value',
- 'text',
- in_array($link->type, $this->disable)
- );
- }
- }
- // Build groups for all menu types.
- else
- {
- // Build the groups arrays.
- foreach ($items as $menu)
- {
- // Initialize the group.
- $groups[$menu->title] = array();
-
- // Build the options array.
- foreach ($menu->links as $link)
- {
- $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
-
- // Displays language code if not set to All
- if ($link->language !== '*')
- {
- $lang = ' (' . $link->language . ')';
- }
- else
- {
- $lang = '';
- }
-
- $groups[$menu->title][] = HTMLHelper::_('select.option',
- $link->value,
- $levelPrefix . $link->text . $lang,
- 'value',
- 'text',
- in_array($link->type, $this->disable)
- );
- }
- }
- }
-
- // Merge any additional groups in the XML definition.
- $groups = array_merge(parent::getGroups(), $groups);
-
- return $groups;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.8.0
+ */
+ public $type = 'MenuItemByType';
+
+ /**
+ * The menu type.
+ *
+ * @var string
+ * @since 3.8.0
+ */
+ protected $menuType;
+
+ /**
+ * The client id.
+ *
+ * @var string
+ * @since 3.8.0
+ */
+ protected $clientId;
+
+ /**
+ * The language.
+ *
+ * @var array
+ * @since 3.8.0
+ */
+ protected $language;
+
+ /**
+ * The published status.
+ *
+ * @var array
+ * @since 3.8.0
+ */
+ protected $published;
+
+ /**
+ * The disabled status.
+ *
+ * @var array
+ * @since 3.8.0
+ */
+ protected $disable;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.8.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'menuType':
+ case 'clientId':
+ case 'language':
+ case 'published':
+ case 'disable':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'menuType':
+ $this->menuType = (string) $value;
+ break;
+
+ case 'clientId':
+ $this->clientId = (int) $value;
+ break;
+
+ case 'language':
+ case 'published':
+ case 'disable':
+ $value = (string) $value;
+ $this->$name = $value ? explode(',', $value) : array();
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see \Joomla\CMS\Form\FormField::setup()
+ * @since 3.8.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result == true) {
+ $menuType = (string) $this->element['menu_type'];
+
+ if (!$menuType) {
+ $app = Factory::getApplication();
+ $currentMenuType = $app->getUserState('com_menus.items.menutype', '');
+ $menuType = $app->input->getString('menutype', $currentMenuType);
+ }
+
+ $this->menuType = $menuType;
+ $this->clientId = (int) $this->element['client_id'];
+ $this->published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array();
+ $this->disable = $this->element['disable'] ? explode(',', (string) $this->element['disable']) : array();
+ $this->language = $this->element['language'] ? explode(',', (string) $this->element['language']) : array();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 3.8.0
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+
+ $menuType = $this->menuType;
+
+ // Get the menu items.
+ $items = MenusHelper::getMenuLinks($menuType, 0, 0, $this->published, $this->language, $this->clientId);
+
+ // Build group for a specific menu type.
+ if ($menuType) {
+ // If the menutype is empty, group the items by menutype.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__menu_types'))
+ ->where($db->quoteName('menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+ $db->setQuery($query);
+
+ try {
+ $menuTitle = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $menuTitle = $menuType;
+ }
+
+ // Initialize the group.
+ $groups[$menuTitle] = array();
+
+ // Build the options array.
+ foreach ($items as $key => $link) {
+ // Unset if item is menu_item_root
+ if ($link->text === 'Menu_Item_Root') {
+ unset($items[$key]);
+ continue;
+ }
+
+ $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
+
+ // Displays language code if not set to All
+ if ($link->language !== '*') {
+ $lang = ' (' . $link->language . ')';
+ } else {
+ $lang = '';
+ }
+
+ $groups[$menuTitle][] = HTMLHelper::_(
+ 'select.option',
+ $link->value,
+ $levelPrefix . $link->text . $lang,
+ 'value',
+ 'text',
+ in_array($link->type, $this->disable)
+ );
+ }
+ } else {
+ // Build groups for all menu types.
+ // Build the groups arrays.
+ foreach ($items as $menu) {
+ // Initialize the group.
+ $groups[$menu->title] = array();
+
+ // Build the options array.
+ foreach ($menu->links as $link) {
+ $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
+
+ // Displays language code if not set to All
+ if ($link->language !== '*') {
+ $lang = ' (' . $link->language . ')';
+ } else {
+ $lang = '';
+ }
+
+ $groups[$menu->title][] = HTMLHelper::_(
+ 'select.option',
+ $link->value,
+ $levelPrefix . $link->text . $lang,
+ 'value',
+ 'text',
+ in_array($link->type, $this->disable)
+ );
+ }
+ }
+ }
+
+ // Merge any additional groups in the XML definition.
+ $groups = array_merge(parent::getGroups(), $groups);
+
+ return $groups;
+ }
}
diff --git a/administrator/components/com_menus/src/Field/MenuOrderingField.php b/administrator/components/com_menus/src/Field/MenuOrderingField.php
index b0c89afd2bd5c..a2106cbbc45a5 100644
--- a/administrator/components/com_menus/src/Field/MenuOrderingField.php
+++ b/administrator/components/com_menus/src/Field/MenuOrderingField.php
@@ -1,4 +1,5 @@
form->getValue('parent_id', 0);
-
- if (!$parent_id)
- {
- return false;
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.id', 'value'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.client_id', 'clientId'),
- ]
- )
- ->from($db->quoteName('#__menu', 'a'))
-
- ->where($db->quoteName('a.published') . ' >= 0')
- ->where($db->quoteName('a.parent_id') . ' = :parentId')
- ->bind(':parentId', $parent_id, ParameterType::INTEGER);
-
- if ($menuType = $this->form->getValue('menutype'))
- {
- $query->where($db->quoteName('a.menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
- }
- else
- {
- $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote(''));
- }
-
- $query->order($db->quoteName('a.lft') . ' ASC');
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- // Allow translation of custom admin menus
- foreach ($options as &$option)
- {
- if ($option->clientId != 0)
- {
- $option->text = Text::_($option->text);
- }
- }
-
- $options = array_merge(
- array(array('value' => '-1', 'text' => Text::_('COM_MENUS_ITEM_FIELD_ORDERING_VALUE_FIRST'))),
- $options,
- array(array('value' => '-2', 'text' => Text::_('COM_MENUS_ITEM_FIELD_ORDERING_VALUE_LAST')))
- );
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7
- */
- protected function getInput()
- {
- if ($this->form->getValue('id', 0) == 0)
- {
- return '' . Text::_('COM_MENUS_ITEM_FIELD_ORDERING_TEXT') . ' ';
- }
- else
- {
- return parent::getInput();
- }
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7
+ */
+ protected $type = 'MenuOrdering';
+
+ /**
+ * Method to get the list of siblings in a menu.
+ * The method requires that parent be set.
+ *
+ * @return array|boolean The field option objects or false if the parent field has not been set
+ *
+ * @since 1.7
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ // Get the parent
+ $parent_id = (int) $this->form->getValue('parent_id', 0);
+
+ if (!$parent_id) {
+ return false;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.client_id', 'clientId'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'a'))
+
+ ->where($db->quoteName('a.published') . ' >= 0')
+ ->where($db->quoteName('a.parent_id') . ' = :parentId')
+ ->bind(':parentId', $parent_id, ParameterType::INTEGER);
+
+ if ($menuType = $this->form->getValue('menutype')) {
+ $query->where($db->quoteName('a.menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+ } else {
+ $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote(''));
+ }
+
+ $query->order($db->quoteName('a.lft') . ' ASC');
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ // Allow translation of custom admin menus
+ foreach ($options as &$option) {
+ if ($option->clientId != 0) {
+ $option->text = Text::_($option->text);
+ }
+ }
+
+ $options = array_merge(
+ array(array('value' => '-1', 'text' => Text::_('COM_MENUS_ITEM_FIELD_ORDERING_VALUE_FIRST'))),
+ $options,
+ array(array('value' => '-2', 'text' => Text::_('COM_MENUS_ITEM_FIELD_ORDERING_VALUE_LAST')))
+ );
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7
+ */
+ protected function getInput()
+ {
+ if ($this->form->getValue('id', 0) == 0) {
+ return '' . Text::_('COM_MENUS_ITEM_FIELD_ORDERING_TEXT') . ' ';
+ } else {
+ return parent::getInput();
+ }
+ }
}
diff --git a/administrator/components/com_menus/src/Field/MenuParentField.php b/administrator/components/com_menus/src/Field/MenuParentField.php
index 1eaa109792acd..24e2002c2087e 100644
--- a/administrator/components/com_menus/src/Field/MenuParentField.php
+++ b/administrator/components/com_menus/src/Field/MenuParentField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- 'DISTINCT ' . $db->quoteName('a.id', 'value'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.level'),
- $db->quoteName('a.lft'),
- ]
- )
- ->from($db->quoteName('#__menu', 'a'));
-
- // Filter by menu type.
- if ($menuType = $this->form->getValue('menutype'))
- {
- $query->where($db->quoteName('a.menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
- }
- else
- {
- // Skip special menu types
- $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote(''));
- $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main'));
- }
-
- // Filter by client id.
- $clientId = $this->getAttribute('clientid');
-
- if (!is_null($clientId))
- {
- $clientId = (int) $clientId;
- $query->where($db->quoteName('a.client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- // Prevent parenting to children of this item.
- if ($id = (int) $this->form->getValue('id'))
- {
- $query->join('LEFT', $db->quoteName('#__menu', 'p'), $db->quoteName('p.id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER)
- ->where(
- 'NOT(' . $db->quoteName('a.lft') . ' >= ' . $db->quoteName('p.lft')
- . ' AND ' . $db->quoteName('a.rgt') . ' <= ' . $db->quoteName('p.rgt') . ')'
- );
- }
-
- $query->where($db->quoteName('a.published') . ' != -2')
- ->order($db->quoteName('a.lft') . ' ASC');
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- // Pad the option text with spaces using depth level as a multiplier.
- for ($i = 0, $n = count($options); $i < $n; $i++)
- {
- if ($clientId != 0)
- {
- // Allow translation of custom admin menus
- $options[$i]->text = str_repeat('- ', $options[$i]->level) . Text::_($options[$i]->text);
- }
- else
- {
- $options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->text;
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'MenuParent';
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.6
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'DISTINCT ' . $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.lft'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'a'));
+
+ // Filter by menu type.
+ if ($menuType = $this->form->getValue('menutype')) {
+ $query->where($db->quoteName('a.menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+ } else {
+ // Skip special menu types
+ $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote(''));
+ $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main'));
+ }
+
+ // Filter by client id.
+ $clientId = $this->getAttribute('clientid');
+
+ if (!is_null($clientId)) {
+ $clientId = (int) $clientId;
+ $query->where($db->quoteName('a.client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ // Prevent parenting to children of this item.
+ if ($id = (int) $this->form->getValue('id')) {
+ $query->join('LEFT', $db->quoteName('#__menu', 'p'), $db->quoteName('p.id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->where(
+ 'NOT(' . $db->quoteName('a.lft') . ' >= ' . $db->quoteName('p.lft')
+ . ' AND ' . $db->quoteName('a.rgt') . ' <= ' . $db->quoteName('p.rgt') . ')'
+ );
+ }
+
+ $query->where($db->quoteName('a.published') . ' != -2')
+ ->order($db->quoteName('a.lft') . ' ASC');
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ // Pad the option text with spaces using depth level as a multiplier.
+ for ($i = 0, $n = count($options); $i < $n; $i++) {
+ if ($clientId != 0) {
+ // Allow translation of custom admin menus
+ $options[$i]->text = str_repeat('- ', $options[$i]->level) . Text::_($options[$i]->text);
+ } else {
+ $options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->text;
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_menus/src/Field/MenuPresetField.php b/administrator/components/com_menus/src/Field/MenuPresetField.php
index 288da3c3185d9..049eb3278feae 100644
--- a/administrator/components/com_menus/src/Field/MenuPresetField.php
+++ b/administrator/components/com_menus/src/Field/MenuPresetField.php
@@ -1,4 +1,5 @@
name, Text::_($preset->title));
- }
+ foreach ($presets as $preset) {
+ $options[] = HTMLHelper::_('select.option', $preset->name, Text::_($preset->title));
+ }
- return array_merge(parent::getOptions(), $options);
- }
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_menus/src/Field/MenutypeField.php b/administrator/components/com_menus/src/Field/MenutypeField.php
index c784466371fe4..1edebcb4032dc 100644
--- a/administrator/components/com_menus/src/Field/MenutypeField.php
+++ b/administrator/components/com_menus/src/Field/MenutypeField.php
@@ -1,4 +1,5 @@
form->getValue('id');
- $size = (string) ($v = $this->element['size']) ? ' size="' . $v . '"' : '';
- $class = (string) ($v = $this->element['class']) ? ' class="form-control ' . $v . '"' : ' class="form-control"';
- $required = (string) $this->element['required'] ? ' required="required"' : '';
- $clientId = (int) $this->element['clientid'] ?: 0;
-
- // Get a reverse lookup of the base link URL to Title
- switch ($this->value)
- {
- case 'url':
- $value = Text::_('COM_MENUS_TYPE_EXTERNAL_URL');
- break;
-
- case 'alias':
- $value = Text::_('COM_MENUS_TYPE_ALIAS');
- break;
-
- case 'separator':
- $value = Text::_('COM_MENUS_TYPE_SEPARATOR');
- break;
-
- case 'heading':
- $value = Text::_('COM_MENUS_TYPE_HEADING');
- break;
-
- case 'container':
- $value = Text::_('COM_MENUS_TYPE_CONTAINER');
- break;
-
- default:
- $link = $this->form->getValue('link');
- $value = '';
-
- if ($link !== null)
- {
- $model = Factory::getApplication()->bootComponent('com_menus')
- ->getMVCFactory()->createModel('Menutypes', 'Administrator', array('ignore_request' => true));
- $model->setState('client_id', $clientId);
-
- $rlu = $model->getReverseLookup();
-
- // Clean the link back to the option, view and layout
- $value = Text::_(ArrayHelper::getValue($rlu, MenusHelper::getLinkKey($link)));
- }
- break;
- }
-
- $link = Route::_('index.php?option=com_menus&view=menutypes&tmpl=component&client_id=' . $clientId . '&recordId=' . $recordId);
- $html[] = ' ';
- $html[] = ''
- . ' '
- . Text::_('JSELECT') . ' ';
- $html[] = HTMLHelper::_(
- 'bootstrap.renderModal',
- 'menuTypeModal',
- array(
- 'url' => $link,
- 'title' => Text::_('COM_MENUS_ITEM_FIELD_TYPE_LABEL'),
- 'width' => '800px',
- 'height' => '300px',
- 'modalWidth' => 80,
- 'bodyHeight' => 70,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- )
- );
-
- // This hidden field has an ID so it can be used for showon attributes
- $html[] = ' ';
-
- return implode("\n", $html);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'menutype';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $html = array();
+ $recordId = (int) $this->form->getValue('id');
+ $size = (string) ($v = $this->element['size']) ? ' size="' . $v . '"' : '';
+ $class = (string) ($v = $this->element['class']) ? ' class="form-control ' . $v . '"' : ' class="form-control"';
+ $required = (string) $this->element['required'] ? ' required="required"' : '';
+ $clientId = (int) $this->element['clientid'] ?: 0;
+
+ // Get a reverse lookup of the base link URL to Title
+ switch ($this->value) {
+ case 'url':
+ $value = Text::_('COM_MENUS_TYPE_EXTERNAL_URL');
+ break;
+
+ case 'alias':
+ $value = Text::_('COM_MENUS_TYPE_ALIAS');
+ break;
+
+ case 'separator':
+ $value = Text::_('COM_MENUS_TYPE_SEPARATOR');
+ break;
+
+ case 'heading':
+ $value = Text::_('COM_MENUS_TYPE_HEADING');
+ break;
+
+ case 'container':
+ $value = Text::_('COM_MENUS_TYPE_CONTAINER');
+ break;
+
+ default:
+ $link = $this->form->getValue('link');
+ $value = '';
+
+ if ($link !== null) {
+ $model = Factory::getApplication()->bootComponent('com_menus')
+ ->getMVCFactory()->createModel('Menutypes', 'Administrator', array('ignore_request' => true));
+ $model->setState('client_id', $clientId);
+
+ $rlu = $model->getReverseLookup();
+
+ // Clean the link back to the option, view and layout
+ $value = Text::_(ArrayHelper::getValue($rlu, MenusHelper::getLinkKey($link)));
+ }
+ break;
+ }
+
+ $link = Route::_('index.php?option=com_menus&view=menutypes&tmpl=component&client_id=' . $clientId . '&recordId=' . $recordId);
+ $html[] = ' ';
+ $html[] = ''
+ . ' '
+ . Text::_('JSELECT') . ' ';
+ $html[] = HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'menuTypeModal',
+ array(
+ 'url' => $link,
+ 'title' => Text::_('COM_MENUS_ITEM_FIELD_TYPE_LABEL'),
+ 'width' => '800px',
+ 'height' => '300px',
+ 'modalWidth' => 80,
+ 'bodyHeight' => 70,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ )
+ );
+
+ // This hidden field has an ID so it can be used for showon attributes
+ $html[] = ' ';
+
+ return implode("\n", $html);
+ }
}
diff --git a/administrator/components/com_menus/src/Field/Modal/MenuField.php b/administrator/components/com_menus/src/Field/Modal/MenuField.php
index 092c1cc3b3529..48c8440101865 100644
--- a/administrator/components/com_menus/src/Field/Modal/MenuField.php
+++ b/administrator/components/com_menus/src/Field/Modal/MenuField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.7.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'allowSelect':
- case 'allowClear':
- case 'allowNew':
- case 'allowEdit':
- case 'allowPropagate':
- $value = (string) $value;
- $this->$name = !($value === 'false' || $value === 'off' || $value === '0');
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a JForm object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.7.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->allowSelect = ((string) $this->element['select']) !== 'false';
- $this->allowClear = ((string) $this->element['clear']) !== 'false';
- $this->allowPropagate = ((string) $this->element['propagate']) === 'true';
-
- // Creating/editing menu items is not supported in frontend.
- $isAdministrator = Factory::getApplication()->isClient('administrator');
- $this->allowNew = $isAdministrator ? ((string) $this->element['new']) === 'true' : false;
- $this->allowEdit = $isAdministrator ? ((string) $this->element['edit']) === 'true' : false;
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.7.0
- */
- protected function getInput()
- {
- $clientId = (int) $this->element['clientid'];
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- // Load language
- Factory::getLanguage()->load('com_menus', JPATH_ADMINISTRATOR);
-
- // The active article id field.
- $value = (int) $this->value ?: '';
-
- // Create the modal id.
- $modalId = 'Item_' . $this->id;
-
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
-
- // Add the modal field script to the document head.
- $wa->useScript('field.modal-fields');
-
- // Script to proxy the select modal function to the modal-fields.js file.
- if ($this->allowSelect)
- {
- static $scriptSelect = null;
-
- if (is_null($scriptSelect))
- {
- $scriptSelect = array();
- }
-
- if (!isset($scriptSelect[$this->id]))
- {
- $wa->addInlineScript("
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'Modal_Menu';
+
+ /**
+ * Determinate, if the select button is shown
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ protected $allowSelect = true;
+
+ /**
+ * Determinate, if the clear button is shown
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ protected $allowClear = true;
+
+ /**
+ * Determinate, if the create button is shown
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ protected $allowNew = false;
+
+ /**
+ * Determinate, if the edit button is shown
+ *
+ * @var boolean
+ * @since 3.7.0
+ */
+ protected $allowEdit = false;
+
+ /**
+ * Determinate, if the propagate button is shown
+ *
+ * @var boolean
+ * @since 3.9.0
+ */
+ protected $allowPropagate = false;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.7.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'allowSelect':
+ case 'allowClear':
+ case 'allowNew':
+ case 'allowEdit':
+ case 'allowPropagate':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'allowSelect':
+ case 'allowClear':
+ case 'allowNew':
+ case 'allowEdit':
+ case 'allowPropagate':
+ $value = (string) $value;
+ $this->$name = !($value === 'false' || $value === 'off' || $value === '0');
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a JForm object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.7.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->allowSelect = ((string) $this->element['select']) !== 'false';
+ $this->allowClear = ((string) $this->element['clear']) !== 'false';
+ $this->allowPropagate = ((string) $this->element['propagate']) === 'true';
+
+ // Creating/editing menu items is not supported in frontend.
+ $isAdministrator = Factory::getApplication()->isClient('administrator');
+ $this->allowNew = $isAdministrator ? ((string) $this->element['new']) === 'true' : false;
+ $this->allowEdit = $isAdministrator ? ((string) $this->element['edit']) === 'true' : false;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getInput()
+ {
+ $clientId = (int) $this->element['clientid'];
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ // Load language
+ Factory::getLanguage()->load('com_menus', JPATH_ADMINISTRATOR);
+
+ // The active article id field.
+ $value = (int) $this->value ?: '';
+
+ // Create the modal id.
+ $modalId = 'Item_' . $this->id;
+
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+
+ // Add the modal field script to the document head.
+ $wa->useScript('field.modal-fields');
+
+ // Script to proxy the select modal function to the modal-fields.js file.
+ if ($this->allowSelect) {
+ static $scriptSelect = null;
+
+ if (is_null($scriptSelect)) {
+ $scriptSelect = array();
+ }
+
+ if (!isset($scriptSelect[$this->id])) {
+ $wa->addInlineScript(
+ "
window.jSelectMenu_" . $this->id . " = function (id, title, object) {
window.processModalSelect('Item', '" . $this->id . "', id, title, '', object);
}",
- [],
- ['type' => 'module']
- );
-
- Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
-
- $scriptSelect[$this->id] = true;
- }
- }
-
- // Setup variables for display.
- $linkSuffix = '&layout=modal&client_id=' . $clientId . '&tmpl=component&' . Session::getFormToken() . '=1';
- $linkItems = 'index.php?option=com_menus&view=items' . $linkSuffix;
- $linkItem = 'index.php?option=com_menus&view=item' . $linkSuffix;
- $modalTitle = Text::_('COM_MENUS_SELECT_A_MENUITEM');
-
- if (isset($this->element['language']))
- {
- $linkItems .= '&forcedLanguage=' . $this->element['language'];
- $linkItem .= '&forcedLanguage=' . $this->element['language'];
- $modalTitle .= ' — ' . $this->element['label'];
- }
-
- $urlSelect = $linkItems . '&function=jSelectMenu_' . $this->id;
- $urlEdit = $linkItem . '&task=item.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
- $urlNew = $linkItem . '&task=item.add';
-
- if ($value)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $value, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $title = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- // Placeholder if option is present or not
- if (empty($title))
- {
- if ($this->element->option && (string) $this->element->option['value'] == '')
- {
- $title_holder = Text::_($this->element->option);
- }
- else
- {
- $title_holder = Text::_('COM_MENUS_SELECT_A_MENUITEM');
- }
- }
-
- $title = empty($title) ? $title_holder : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
-
- // The current menu item display field.
- $html = '';
-
- if ($this->allowSelect || $this->allowNew || $this->allowEdit || $this->allowClear)
- {
- $html .= '';
- }
-
- $html .= ' ';
-
- // Select menu item button
- if ($this->allowSelect)
- {
- $html .= ''
- . ' ' . Text::_('JSELECT')
- . ' ';
- }
-
- // New menu item button
- if ($this->allowNew)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_CREATE')
- . ' ';
- }
-
- // Edit menu item button
- if ($this->allowEdit)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_EDIT')
- . ' ';
- }
-
- // Clear menu item button
- if ($this->allowClear)
- {
- $html .= ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
- }
-
- // Propagate menu item button
- if ($this->allowPropagate && count($languages) > 2)
- {
- // Strip off language tag at the end
- $tagLength = (int) strlen($this->element['language']);
- $callbackFunctionStem = substr("jSelectMenu_" . $this->id, 0, -$tagLength);
-
- $html .= ''
- . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
- . ' ';
- }
-
- if ($this->allowSelect || $this->allowNew || $this->allowEdit || $this->allowClear)
- {
- $html .= ' ';
- }
-
- // Select menu item modal
- if ($this->allowSelect)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalSelect' . $modalId,
- array(
- 'title' => $modalTitle,
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
- }
-
- // New menu item modal
- if ($this->allowNew)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalNew' . $modalId,
- array(
- 'title' => Text::_('COM_MENUS_NEW_MENUITEM'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlNew,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Edit menu item modal
- if ($this->allowEdit)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalEdit' . $modalId,
- array(
- 'title' => Text::_('COM_MENUS_EDIT_MENUITEM'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlEdit,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Note: class='required' for client side validation.
- $class = $this->required ? ' class="required modal-value"' : '';
-
- // Placeholder if option is present or not when clearing field
- if ($this->element->option && (string) $this->element->option['value'] == '')
- {
- $title_holder = Text::_($this->element->option);
- }
- else
- {
- $title_holder = Text::_('COM_MENUS_SELECT_A_MENUITEM');
- }
-
- $html .= ' ';
-
- return $html;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 3.7.0
- */
- protected function getLabel()
- {
- return str_replace($this->id, $this->id . '_name', parent::getLabel());
- }
+ [],
+ ['type' => 'module']
+ );
+
+ Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
+
+ $scriptSelect[$this->id] = true;
+ }
+ }
+
+ // Setup variables for display.
+ $linkSuffix = '&layout=modal&client_id=' . $clientId . '&tmpl=component&' . Session::getFormToken() . '=1';
+ $linkItems = 'index.php?option=com_menus&view=items' . $linkSuffix;
+ $linkItem = 'index.php?option=com_menus&view=item' . $linkSuffix;
+ $modalTitle = Text::_('COM_MENUS_SELECT_A_MENUITEM');
+
+ if (isset($this->element['language'])) {
+ $linkItems .= '&forcedLanguage=' . $this->element['language'];
+ $linkItem .= '&forcedLanguage=' . $this->element['language'];
+ $modalTitle .= ' — ' . $this->element['label'];
+ }
+
+ $urlSelect = $linkItems . '&function=jSelectMenu_' . $this->id;
+ $urlEdit = $linkItem . '&task=item.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
+ $urlNew = $linkItem . '&task=item.add';
+
+ if ($value) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $value, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $title = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ // Placeholder if option is present or not
+ if (empty($title)) {
+ if ($this->element->option && (string) $this->element->option['value'] == '') {
+ $title_holder = Text::_($this->element->option);
+ } else {
+ $title_holder = Text::_('COM_MENUS_SELECT_A_MENUITEM');
+ }
+ }
+
+ $title = empty($title) ? $title_holder : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
+
+ // The current menu item display field.
+ $html = '';
+
+ if ($this->allowSelect || $this->allowNew || $this->allowEdit || $this->allowClear) {
+ $html .= '';
+ }
+
+ $html .= ' ';
+
+ // Select menu item button
+ if ($this->allowSelect) {
+ $html .= ''
+ . ' ' . Text::_('JSELECT')
+ . ' ';
+ }
+
+ // New menu item button
+ if ($this->allowNew) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_CREATE')
+ . ' ';
+ }
+
+ // Edit menu item button
+ if ($this->allowEdit) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_EDIT')
+ . ' ';
+ }
+
+ // Clear menu item button
+ if ($this->allowClear) {
+ $html .= ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+ }
+
+ // Propagate menu item button
+ if ($this->allowPropagate && count($languages) > 2) {
+ // Strip off language tag at the end
+ $tagLength = (int) strlen($this->element['language']);
+ $callbackFunctionStem = substr("jSelectMenu_" . $this->id, 0, -$tagLength);
+
+ $html .= ''
+ . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
+ . ' ';
+ }
+
+ if ($this->allowSelect || $this->allowNew || $this->allowEdit || $this->allowClear) {
+ $html .= ' ';
+ }
+
+ // Select menu item modal
+ if ($this->allowSelect) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalSelect' . $modalId,
+ array(
+ 'title' => $modalTitle,
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+ }
+
+ // New menu item modal
+ if ($this->allowNew) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalNew' . $modalId,
+ array(
+ 'title' => Text::_('COM_MENUS_NEW_MENUITEM'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlNew,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Edit menu item modal
+ if ($this->allowEdit) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalEdit' . $modalId,
+ array(
+ 'title' => Text::_('COM_MENUS_EDIT_MENUITEM'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlEdit,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Note: class='required' for client side validation.
+ $class = $this->required ? ' class="required modal-value"' : '';
+
+ // Placeholder if option is present or not when clearing field
+ if ($this->element->option && (string) $this->element->option['value'] == '') {
+ $title_holder = Text::_($this->element->option);
+ } else {
+ $title_holder = Text::_('COM_MENUS_SELECT_A_MENUITEM');
+ }
+
+ $html .= ' ';
+
+ return $html;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getLabel()
+ {
+ return str_replace($this->id, $this->id . '_name', parent::getLabel());
+ }
}
diff --git a/administrator/components/com_menus/src/Helper/AssociationsHelper.php b/administrator/components/com_menus/src/Helper/AssociationsHelper.php
index 14f0c37dddcee..d22b6cd8b8301 100644
--- a/administrator/components/com_menus/src/Helper/AssociationsHelper.php
+++ b/administrator/components/com_menus/src/Helper/AssociationsHelper.php
@@ -1,4 +1,5 @@
getType($typeName);
-
- $context = $this->extension . '.item';
-
- // Get the associations.
- $associations = Associations::getAssociations(
- $this->extension,
- $type['tables']['a'],
- $context,
- $id,
- 'id',
- 'alias',
- ''
- );
-
- return $associations;
- }
-
- /**
- * Get item information
- *
- * @param string $typeName The item type
- * @param int $id The id of item for which we need the associated items
- *
- * @return Table|null
- *
- * @since 3.7.0
- */
- public function getItem($typeName, $id)
- {
- if (empty($id))
- {
- return null;
- }
-
- $table = null;
-
- switch ($typeName)
- {
- case 'item':
- $table = Table::getInstance('menu');
- break;
- }
-
- if (is_null($table))
- {
- return null;
- }
-
- $table->load($id);
-
- return $table;
- }
-
- /**
- * Get information about the type
- *
- * @param string $typeName The item type
- *
- * @return array Array of item types
- *
- * @since 3.7.0
- */
- public function getType($typeName = '')
- {
- $fields = $this->getFieldsTemplate();
- $tables = array();
- $joins = array();
- $support = $this->getSupportTemplate();
- $title = '';
-
- if (in_array($typeName, $this->itemTypes))
- {
- switch ($typeName)
- {
- case 'item':
- $fields['ordering'] = 'a.lft';
- $fields['level'] = 'a.level';
- $fields['catid'] = '';
- $fields['state'] = 'a.published';
- $fields['created_user_id'] = '';
- $fields['menutype'] = 'a.menutype';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['level'] = true;
-
- $tables = array(
- 'a' => '#__menu'
- );
-
- $title = 'menu';
- break;
- }
- }
-
- return array(
- 'fields' => $fields,
- 'support' => $support,
- 'tables' => $tables,
- 'joins' => $joins,
- 'title' => $title
- );
- }
+ /**
+ * The extension name
+ *
+ * @var array $extension
+ *
+ * @since 3.7.0
+ */
+ protected $extension = 'com_menus';
+
+ /**
+ * Array of item types
+ *
+ * @var array $itemTypes
+ *
+ * @since 3.7.0
+ */
+ protected $itemTypes = array('item');
+
+ /**
+ * Has the extension association support
+ *
+ * @var boolean $associationsSupport
+ *
+ * @since 3.7.0
+ */
+ protected $associationsSupport = true;
+
+ /**
+ * Method to get the associations for a given item.
+ *
+ * @param integer $id Id of the item
+ * @param string $view Name of the view
+ *
+ * @return array Array of associations for the item
+ *
+ * @since 4.0.0
+ */
+ public function getAssociationsForItem($id = 0, $view = null)
+ {
+ return [];
+ }
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public function getAssociations($typeName, $id)
+ {
+ $type = $this->getType($typeName);
+
+ $context = $this->extension . '.item';
+
+ // Get the associations.
+ $associations = Associations::getAssociations(
+ $this->extension,
+ $type['tables']['a'],
+ $context,
+ $id,
+ 'id',
+ 'alias',
+ ''
+ );
+
+ return $associations;
+ }
+
+ /**
+ * Get item information
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return Table|null
+ *
+ * @since 3.7.0
+ */
+ public function getItem($typeName, $id)
+ {
+ if (empty($id)) {
+ return null;
+ }
+
+ $table = null;
+
+ switch ($typeName) {
+ case 'item':
+ $table = Table::getInstance('menu');
+ break;
+ }
+
+ if (is_null($table)) {
+ return null;
+ }
+
+ $table->load($id);
+
+ return $table;
+ }
+
+ /**
+ * Get information about the type
+ *
+ * @param string $typeName The item type
+ *
+ * @return array Array of item types
+ *
+ * @since 3.7.0
+ */
+ public function getType($typeName = '')
+ {
+ $fields = $this->getFieldsTemplate();
+ $tables = array();
+ $joins = array();
+ $support = $this->getSupportTemplate();
+ $title = '';
+
+ if (in_array($typeName, $this->itemTypes)) {
+ switch ($typeName) {
+ case 'item':
+ $fields['ordering'] = 'a.lft';
+ $fields['level'] = 'a.level';
+ $fields['catid'] = '';
+ $fields['state'] = 'a.published';
+ $fields['created_user_id'] = '';
+ $fields['menutype'] = 'a.menutype';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['level'] = true;
+
+ $tables = array(
+ 'a' => '#__menu'
+ );
+
+ $title = 'menu';
+ break;
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'support' => $support,
+ 'tables' => $tables,
+ 'joins' => $joins,
+ 'title' => $title
+ );
+ }
}
diff --git a/administrator/components/com_menus/src/Helper/MenusHelper.php b/administrator/components/com_menus/src/Helper/MenusHelper.php
index 7d515381fd32d..bd582150b312b 100644
--- a/administrator/components/com_menus/src/Helper/MenusHelper.php
+++ b/administrator/components/com_menus/src/Helper/MenusHelper.php
@@ -1,4 +1,5 @@
$value)
- {
- if ((!in_array($name, self::$_filter)) && (!($name == 'task' && !array_key_exists('view', $request))))
- {
- // Remove the variables we want to ignore.
- unset($request[$name]);
- }
- }
-
- ksort($request);
-
- return 'index.php?' . http_build_query($request, '', '&');
- }
-
- /**
- * Get the menu list for create a menu module
- *
- * @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all
- *
- * @return array The menu array list
- *
- * @since 1.6
- */
- public static function getMenuTypes($clientId = 0)
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('a.menutype'))
- ->from($db->quoteName('#__menu_types', 'a'));
-
- if (isset($clientId))
- {
- $clientId = (int) $clientId;
- $query->where($db->quoteName('a.client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- $db->setQuery($query);
-
- return $db->loadColumn();
- }
-
- /**
- * Get a list of menu links for one or all menus.
- *
- * @param string $menuType An option menu to filter the list on, otherwise all menu with given client id links
- * are returned as a grouped array.
- * @param integer $parentId An optional parent ID to pivot results around.
- * @param integer $mode An optional mode. If parent ID is set and mode=2, the parent and children are excluded from the list.
- * @param array $published An optional array of states
- * @param array $languages Optional array of specify which languages we want to filter
- * @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all (used only if menutype not given)
- *
- * @return array|boolean
- *
- * @since 1.6
- */
- public static function getMenuLinks($menuType = null, $parentId = 0, $mode = 0, $published = array(), $languages = array(), $clientId = 0)
- {
- $hasClientId = $clientId !== null;
- $clientId = (int) $clientId;
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(
- [
- 'DISTINCT ' . $db->quoteName('a.id', 'value'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.alias'),
- $db->quoteName('a.level'),
- $db->quoteName('a.menutype'),
- $db->quoteName('a.client_id'),
- $db->quoteName('a.type'),
- $db->quoteName('a.published'),
- $db->quoteName('a.template_style_id'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.language'),
- $db->quoteName('a.lft'),
- $db->quoteName('e.name', 'componentname'),
- $db->quoteName('e.element'),
- ]
- )
- ->from($db->quoteName('#__menu', 'a'))
- ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
-
- if (Multilanguage::isEnabled())
- {
- $query->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- $db->quoteName('l.sef', 'language_sef'),
- ]
- )
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
- }
-
- // Filter by the type if given, this is more specific than client id
- if ($menuType)
- {
- $query->where('(' . $db->quoteName('a.menutype') . ' = :menuType OR ' . $db->quoteName('a.parent_id') . ' = 0)')
- ->bind(':menuType', $menuType);
- }
- elseif ($hasClientId)
- {
- $query->where($db->quoteName('a.client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- // Prevent the parent and children from showing if requested.
- if ($parentId && $mode == 2)
- {
- $query->join('LEFT', $db->quoteName('#__menu', 'p'), $db->quoteName('p.id') . ' = :parentId')
- ->where(
- '(' . $db->quoteName('a.lft') . ' <= ' . $db->quoteName('p.lft')
- . ' OR ' . $db->quoteName('a.rgt') . ' >= ' . $db->quoteName('p.rgt') . ')'
- )
- ->bind(':parentId', $parentId, ParameterType::INTEGER);
- }
-
- if (!empty($languages))
- {
- $query->whereIn($db->quoteName('a.language'), (array) $languages, ParameterType::STRING);
- }
-
- if (!empty($published))
- {
- $query->whereIn($db->quoteName('a.published'), (array) $published);
- }
-
- $query->where($db->quoteName('a.published') . ' != -2');
- $query->order($db->quoteName('a.lft') . ' ASC');
-
- try
- {
- // Get the options.
- $db->setQuery($query);
- $links = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- if (empty($menuType))
- {
- // If the menutype is empty, group the items by menutype.
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName('#__menu_types'))
- ->where($db->quoteName('menutype') . ' <> ' . $db->quote(''))
- ->order(
- [
- $db->quoteName('title'),
- $db->quoteName('menutype'),
- ]
- );
-
- if ($hasClientId)
- {
- $query->where($db->quoteName('client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- try
- {
- $db->setQuery($query);
- $menuTypes = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- // Create a reverse lookup and aggregate the links.
- $rlu = array();
-
- foreach ($menuTypes as &$type)
- {
- $rlu[$type->menutype] = & $type;
- $type->links = array();
- }
-
- // Loop through the list of menu links.
- foreach ($links as &$link)
- {
- if (isset($rlu[$link->menutype]))
- {
- $rlu[$link->menutype]->links[] = & $link;
-
- // Cleanup garbage.
- unset($link->menutype);
- }
- }
-
- return $menuTypes;
- }
- else
- {
- return $links;
- }
- }
-
- /**
- * Get the associations
- *
- * @param integer $pk Menu item id
- *
- * @return array
- *
- * @since 3.0
- */
- public static function getAssociations($pk)
- {
- $langAssociations = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', $pk, 'id', '', '');
- $associations = array();
-
- foreach ($langAssociations as $langAssociation)
- {
- $associations[$langAssociation->language] = $langAssociation->id;
- }
-
- return $associations;
- }
-
- /**
- * Load the menu items from database for the given menutype
- *
- * @param string $menutype The selected menu type
- * @param boolean $enabledOnly Whether to load only enabled/published menu items.
- * @param int[] $exclude The menu items to exclude from the list
- *
- * @return AdministratorMenuItem A root node with the menu items as children
- *
- * @since 4.0.0
- */
- public static function getMenuItems($menutype, $enabledOnly = false, $exclude = array())
- {
- $root = new AdministratorMenuItem;
- $db = Factory::getContainer()->get(DatabaseInterface::class);
- $query = $db->getQuery(true);
-
- // Prepare the query.
- $query->select($db->quoteName('m') . '.*')
- ->from($db->quoteName('#__menu', 'm'))
- ->where(
- [
- $db->quoteName('m.menutype') . ' = :menutype',
- $db->quoteName('m.client_id') . ' = 1',
- $db->quoteName('m.id') . ' > 1',
- ]
- )
- ->bind(':menutype', $menutype);
-
- if ($enabledOnly)
- {
- $query->where($db->quoteName('m.published') . ' = 1');
- }
-
- // Filter on the enabled states.
- $query->select($db->quoteName('e.element'))
- ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
- ->extendWhere(
- 'AND',
- [
- $db->quoteName('e.enabled') . ' = 1',
- $db->quoteName('e.enabled') . ' IS NULL',
- ],
- 'OR'
- );
-
- if (count($exclude))
- {
- $exId = array_map('intval', array_filter($exclude, 'is_numeric'));
- $exEl = array_filter($exclude, 'is_string');
-
- if ($exId)
- {
- $query->whereNotIn($db->quoteName('m.id'), $exId)
- ->whereNotIn($db->quoteName('m.parent_id'), $exId);
- }
-
- if ($exEl)
- {
- $query->whereNotIn($db->quoteName('e.element'), $exEl, ParameterType::STRING);
- }
- }
-
- // Order by lft.
- $query->order($db->quoteName('m.lft'));
-
- try
- {
- $menuItems = [];
- $iterator = $db->setQuery($query)->getIterator();
-
- foreach ($iterator as $item)
- {
- $menuItems[$item->id] = new AdministratorMenuItem((array) $item);
- }
-
- unset($iterator);
-
- foreach ($menuItems as $menuitem)
- {
- // Resolve the alias item to get the original item
- if ($menuitem->type == 'alias')
- {
- static::resolveAlias($menuitem);
- }
-
- if ($menuitem->link = in_array($menuitem->type, array('separator', 'heading', 'container')) ? '#' : trim($menuitem->link))
- {
- $menuitem->submenu = array();
- $menuitem->class = $menuitem->img ?? '';
- $menuitem->scope = $menuitem->scope ?? null;
- $menuitem->target = $menuitem->browserNav ? '_blank' : '';
- }
-
- $menuitem->ajaxbadge = $menuitem->getParams()->get('ajax-badge');
- $menuitem->dashboard = $menuitem->getParams()->get('dashboard');
-
- if ($menuitem->parent_id > 1)
- {
- if (isset($menuItems[$menuitem->parent_id]))
- {
- $menuItems[$menuitem->parent_id]->addChild($menuitem);
- }
- }
- else
- {
- $root->addChild($menuitem);
- }
- }
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
- }
-
- return $root;
- }
-
- /**
- * Method to install a preset menu into database and link them to the given menutype
- *
- * @param string $preset The preset name
- * @param string $menutype The target menutype
- *
- * @return void
- *
- * @throws \Exception
- *
- * @since 4.0.0
- */
- public static function installPreset($preset, $menutype)
- {
- $root = static::loadPreset($preset, false);
-
- if (count($root->getChildren()) == 0)
- {
- throw new \Exception(Text::_('COM_MENUS_PRESET_LOAD_FAILED'));
- }
-
- static::installPresetItems($root, $menutype);
- }
-
- /**
- * Method to install a preset menu item into database and link it to the given menutype
- *
- * @param AdministratorMenuItem $node The parent node of the items to process
- * @param string $menutype The target menutype
- *
- * @return void
- *
- * @throws \Exception
- *
- * @since 4.0.0
- */
- protected static function installPresetItems($node, $menutype)
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $items = $node->getChildren();
-
- static $components = array();
-
- if (!$components)
- {
- $query->select(
- [
- $db->quoteName('extension_id'),
- $db->quoteName('element'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
- $components = $db->setQuery($query)->loadObjectList();
- $components = array_column((array) $components, 'element', 'extension_id');
- }
-
- Factory::getApplication()->triggerEvent('onPreprocessMenuItems', array('com_menus.administrator.import', &$items, null, true));
-
- foreach ($items as $item)
- {
- /** @var \Joomla\CMS\Table\Menu $table */
- $table = Table::getInstance('Menu');
-
- $item->alias = $menutype . '-' . $item->title;
-
- // Temporarily set unicodeslugs if a menu item has an unicode alias
- $unicode = Factory::getApplication()->set('unicodeslugs', 1);
- $item->alias = ApplicationHelper::stringURLSafe($item->alias);
- Factory::getApplication()->set('unicodeslugs', $unicode);
-
- if ($item->type == 'separator')
- {
- // Do not reuse a separator
- $item->title = $item->title ?: '-';
- $item->alias = microtime(true);
- }
- elseif ($item->type == 'heading' || $item->type == 'container')
- {
- // Try to match an existing record to have minimum collision for a heading
- $keys = array(
- 'menutype' => $menutype,
- 'type' => $item->type,
- 'title' => $item->title,
- 'parent_id' => (int) $item->getParent()->id,
- 'client_id' => 1,
- );
- $table->load($keys);
- }
- elseif ($item->type == 'url' || $item->type == 'component')
- {
- if (substr($item->link, 0, 8) === 'special:')
- {
- $special = substr($item->link, 8);
-
- if ($special === 'language-forum')
- {
- $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
- }
- elseif ($special === 'custom-forum')
- {
- $item->link = '';
- }
- }
-
- // Try to match an existing record to have minimum collision for a link
- $keys = array(
- 'menutype' => $menutype,
- 'type' => $item->type,
- 'link' => $item->link,
- 'parent_id' => (int) $item->getParent()->id,
- 'client_id' => 1,
- );
- $table->load($keys);
- }
-
- // Translate "hideitems" param value from "element" into "menu-item-id"
- if ($item->type == 'container' && count($hideitems = (array) $item->getParams()->get('hideitems')))
- {
- foreach ($hideitems as &$hel)
- {
- if (!is_numeric($hel))
- {
- $hel = array_search($hel, $components);
- }
- }
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->whereIn($db->quoteName('component_id'), $hideitems);
- $hideitems = $db->setQuery($query)->loadColumn();
-
- $item->getParams()->set('hideitems', $hideitems);
- }
-
- $record = array(
- 'menutype' => $menutype,
- 'title' => $item->title,
- 'alias' => $item->alias,
- 'type' => $item->type,
- 'link' => $item->link,
- 'browserNav' => $item->browserNav,
- 'img' => $item->class,
- 'access' => $item->access,
- 'component_id' => array_search($item->element, $components) ?: 0,
- 'parent_id' => (int) $item->getParent()->id,
- 'client_id' => 1,
- 'published' => 1,
- 'language' => '*',
- 'home' => 0,
- 'params' => (string) $item->getParams(),
- );
-
- if (!$table->bind($record))
- {
- throw new \Exception($table->getError());
- }
-
- $table->setLocation($item->getParent()->id, 'last-child');
-
- if (!$table->check())
- {
- throw new \Exception($table->getError());
- }
-
- if (!$table->store())
- {
- throw new \Exception($table->getError());
- }
-
- $item->id = $table->get('id');
-
- if ($item->hasChildren())
- {
- static::installPresetItems($item, $menutype);
- }
- }
- }
-
- /**
- * Add a custom preset externally via plugin or any other means.
- * WARNING: Presets with same name will replace previously added preset *except* Joomla's default preset (joomla)
- *
- * @param string $name The unique identifier for the preset.
- * @param string $title The display label for the preset.
- * @param string $path The path to the preset file.
- * @param bool $replace Whether to replace the preset with the same name if any (except 'joomla').
- *
- * @return void
- *
- * @since 4.0.0
- */
- public static function addPreset($name, $title, $path, $replace = true)
- {
- if (static::$presets === null)
- {
- static::getPresets();
- }
-
- if ($name == 'joomla')
- {
- $replace = false;
- }
-
- if (($replace || !array_key_exists($name, static::$presets)) && is_file($path))
- {
- $preset = new \stdClass;
-
- $preset->name = $name;
- $preset->title = $title;
- $preset->path = $path;
-
- static::$presets[$name] = $preset;
- }
- }
-
- /**
- * Get a list of available presets.
- *
- * @return \stdClass[]
- *
- * @since 4.0.0
- */
- public static function getPresets()
- {
- if (static::$presets === null)
- {
- // Important: 'null' will cause infinite recursion.
- static::$presets = array();
-
- $components = ComponentHelper::getComponents();
- $lang = Factory::getApplication()->getLanguage();
-
- foreach ($components as $component)
- {
- if (!$component->enabled)
- {
- continue;
- }
-
- $folder = JPATH_ADMINISTRATOR . '/components/' . $component->option . '/presets/';
-
- if (!Folder::exists($folder))
- {
- continue;
- }
-
- $lang->load($component->option . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($component->option . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->option);
-
- $presets = Folder::files($folder, '.xml');
-
- foreach ($presets as $preset)
- {
- $name = File::stripExt($preset);
- $title = strtoupper($component->option . '_MENUS_PRESET_' . $name);
- static::addPreset($name, $title, $folder . $preset);
- }
- }
-
- // Load from template folder automatically
- $app = Factory::getApplication();
- $tpl = JPATH_THEMES . '/' . $app->getTemplate() . '/html/com_menus/presets';
-
- if (is_dir($tpl))
- {
- $files = Folder::files($tpl, '\.xml$');
-
- foreach ($files as $file)
- {
- $name = substr($file, 0, -4);
- $title = str_replace('-', ' ', $name);
-
- static::addPreset(strtolower($name), ucwords($title), $tpl . '/' . $file);
- }
- }
- }
-
- return static::$presets;
- }
-
- /**
- * Load the menu items from a preset file into a hierarchical list of objects
- *
- * @param string $name The preset name
- * @param bool $fallback Fallback to default (joomla) preset if the specified one could not be loaded?
- * @param AdministratorMenuItem $parent Root node of the menu
- *
- * @return AdministratorMenuItem
- *
- * @since 4.0.0
- */
- public static function loadPreset($name, $fallback = true, $parent = null)
- {
- $presets = static::getPresets();
-
- if (!$parent)
- {
- $parent = new AdministratorMenuItem;
- }
-
- if (isset($presets[$name]) && ($xml = simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement)
- {
- static::loadXml($xml, $parent);
- }
- elseif ($fallback && isset($presets['default']))
- {
- if (($xml = simplexml_load_file($presets['default']->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement)
- {
- static::loadXml($xml, $parent);
- }
- }
-
- return $parent;
- }
-
- /**
- * Method to resolve the menu item alias type menu item
- *
- * @param AdministratorMenuItem &$item The alias object
- *
- * @return void
- *
- * @since 4.0.0
- */
- public static function resolveAlias(&$item)
- {
- $obj = $item;
-
- while ($obj->type == 'alias')
- {
- $aliasTo = (int) $obj->getParams()->get('aliasoptions');
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.link'),
- $db->quoteName('a.type'),
- $db->quoteName('e.element'),
- ]
- )
- ->from($db->quoteName('#__menu', 'a'))
- ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'))
- ->where($db->quoteName('a.id') . ' = :aliasTo')
- ->bind(':aliasTo', $aliasTo, ParameterType::INTEGER);
-
- try
- {
- $obj = new AdministratorMenuItem($db->setQuery($query)->loadAssoc());
-
- if (!$obj)
- {
- $item->link = '';
-
- return;
- }
- }
- catch (\Exception $e)
- {
- $item->link = '';
-
- return;
- }
- }
-
- $item->id = $obj->id;
- $item->link = $obj->link;
- $item->type = $obj->type;
- $item->element = $obj->element;
- }
-
- /**
- * Parse the flat list of menu items and prepare the hierarchy of them using parent-child relationship.
- *
- * @param AdministratorMenuItem $item Menu item to preprocess
- *
- * @return void
- *
- * @since 4.0.0
- */
- public static function preprocess($item)
- {
- // Resolve the alias item to get the original item
- if ($item->type == 'alias')
- {
- static::resolveAlias($item);
- }
-
- if ($item->link = in_array($item->type, array('separator', 'heading', 'container')) ? '#' : trim($item->link))
- {
- $item->class = $item->img ?? '';
- $item->scope = $item->scope ?? null;
- $item->target = $item->browserNav ? '_blank' : '';
- }
- }
-
- /**
- * Load a menu tree from an XML file
- *
- * @param \SimpleXMLElement[] $elements The xml menuitem nodes
- * @param AdministratorMenuItem $parent The menu hierarchy list to be populated
- * @param string[] $replace The substring replacements for iterator type items
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected static function loadXml($elements, $parent, $replace = array())
- {
- foreach ($elements as $element)
- {
- if ($element->getName() != 'menuitem')
- {
- continue;
- }
-
- $select = (string) $element['sql_select'];
- $from = (string) $element['sql_from'];
-
- /**
- * Following is a repeatable group based on simple database query. This requires sql_* attributes (sql_select and sql_from are required)
- * The values can be used like - "{sql:columnName}" in any attribute of repeated elements.
- * The repeated elements are place inside this xml node but they will be populated in the same level in the rendered menu
- */
- if ($select && $from)
- {
- $hidden = $element['hidden'] == 'true';
- $where = (string) $element['sql_where'];
- $order = (string) $element['sql_order'];
- $group = (string) $element['sql_group'];
- $lJoin = (string) $element['sql_leftjoin'];
- $iJoin = (string) $element['sql_innerjoin'];
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $query->select($select)->from($from);
-
- if ($where)
- {
- $query->where($where);
- }
-
- if ($order)
- {
- $query->order($order);
- }
-
- if ($group)
- {
- $query->group($group);
- }
-
- if ($lJoin)
- {
- $query->join('LEFT', $lJoin);
- }
-
- if ($iJoin)
- {
- $query->join('INNER', $iJoin);
- }
-
- $results = $db->setQuery($query)->loadObjectList();
-
- // Skip the entire group if no items to iterate over.
- if ($results)
- {
- // Show the repeatable group heading node only if not set as hidden.
- if (!$hidden)
- {
- $child = static::parseXmlNode($element, $replace);
- $parent->addChild($child);
- }
-
- // Iterate over the matching records, items goes in the same level (not $item->submenu) as this node.
- if ('self' == (string) $element['sql_target'])
- {
- foreach ($results as $result)
- {
- static::loadXml($element->menuitem, $child, $result);
- }
- }
- else
- {
- foreach ($results as $result)
- {
- static::loadXml($element->menuitem, $parent, $result);
- }
- }
- }
- }
- else
- {
- $item = static::parseXmlNode($element, $replace);
-
- // Process the child nodes
- static::loadXml($element->menuitem, $item, $replace);
-
- $parent->addChild($item);
- }
- }
- }
-
- /**
- * Create a menu item node from an xml element
- *
- * @param \SimpleXMLElement $node A menuitem element from preset xml
- * @param string[] $replace The values to substitute in the title, link and element texts
- *
- * @return \stdClass
- *
- * @since 4.0.0
- */
- protected static function parseXmlNode($node, $replace = array())
- {
- $item = new AdministratorMenuItem;
-
- $item->id = null;
- $item->type = (string) $node['type'];
- $item->title = (string) $node['title'];
- $item->alias = (string) $node['alias'];
- $item->link = (string) $node['link'];
- $item->target = (string) $node['target'];
- $item->element = (string) $node['element'];
- $item->class = (string) $node['class'];
- $item->icon = (string) $node['icon'];
- $item->access = (int) $node['access'];
- $item->scope = (string) $node['scope'] ?: 'default';
- $item->ajaxbadge = (string) $node['ajax-badge'];
- $item->dashboard = (string) $node['dashboard'];
-
- $params = new Registry(trim($node->params));
- $params->set('menu-permission', (string) $node['permission']);
-
- if ($item->type == 'separator' && trim($item->title, '- '))
- {
- $params->set('text_separator', 1);
- }
-
- if ($item->type == 'heading' || $item->type == 'container')
- {
- $item->link = '#';
- }
-
- if ((string) $node['quicktask'])
- {
- $params->set('menu-quicktask', (string) $node['quicktask']);
- $params->set('menu-quicktask-title', (string) $node['quicktask-title']);
- $params->set('menu-quicktask-icon', (string) $node['quicktask-icon']);
- $params->set('menu-quicktask-permission', (string) $node['quicktask-permission']);
- }
-
- // Translate attributes for iterator values
- foreach ($replace as $var => $val)
- {
- $item->title = str_replace("{sql:$var}", $val, $item->title);
- $item->element = str_replace("{sql:$var}", $val, $item->element);
- $item->link = str_replace("{sql:$var}", $val, $item->link);
- $item->class = str_replace("{sql:$var}", $val, $item->class);
- $item->icon = str_replace("{sql:$var}", $val, $item->icon);
- $params->set('menu-quicktask', str_replace("{sql:$var}", $val, $params->get('menu-quicktask')));
- }
-
- $item->setParams($params);
-
- return $item;
- }
+ /**
+ * Defines the valid request variables for the reverse lookup.
+ *
+ * @var array
+ */
+ protected static $_filter = array('option', 'view', 'layout');
+
+ /**
+ * List of preset include paths
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected static $presets = null;
+
+ /**
+ * Gets a standard form of a link for lookups.
+ *
+ * @param mixed $request A link string or array of request variables.
+ *
+ * @return mixed A link in standard option-view-layout form, or false if the supplied response is invalid.
+ *
+ * @since 1.6
+ */
+ public static function getLinkKey($request)
+ {
+ if (empty($request)) {
+ return false;
+ }
+
+ // Check if the link is in the form of index.php?...
+ if (is_string($request)) {
+ $args = array();
+
+ if (strpos($request, 'index.php') === 0) {
+ parse_str(parse_url(htmlspecialchars_decode($request), PHP_URL_QUERY), $args);
+ } else {
+ parse_str($request, $args);
+ }
+
+ $request = $args;
+ }
+
+ // Only take the option, view and layout parts.
+ foreach ($request as $name => $value) {
+ if ((!in_array($name, self::$_filter)) && (!($name == 'task' && !array_key_exists('view', $request)))) {
+ // Remove the variables we want to ignore.
+ unset($request[$name]);
+ }
+ }
+
+ ksort($request);
+
+ return 'index.php?' . http_build_query($request, '', '&');
+ }
+
+ /**
+ * Get the menu list for create a menu module
+ *
+ * @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all
+ *
+ * @return array The menu array list
+ *
+ * @since 1.6
+ */
+ public static function getMenuTypes($clientId = 0)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('a.menutype'))
+ ->from($db->quoteName('#__menu_types', 'a'));
+
+ if (isset($clientId)) {
+ $clientId = (int) $clientId;
+ $query->where($db->quoteName('a.client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ $db->setQuery($query);
+
+ return $db->loadColumn();
+ }
+
+ /**
+ * Get a list of menu links for one or all menus.
+ *
+ * @param string $menuType An option menu to filter the list on, otherwise all menu with given client id links
+ * are returned as a grouped array.
+ * @param integer $parentId An optional parent ID to pivot results around.
+ * @param integer $mode An optional mode. If parent ID is set and mode=2, the parent and children are excluded from the list.
+ * @param array $published An optional array of states
+ * @param array $languages Optional array of specify which languages we want to filter
+ * @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all (used only if menutype not given)
+ *
+ * @return array|boolean
+ *
+ * @since 1.6
+ */
+ public static function getMenuLinks($menuType = null, $parentId = 0, $mode = 0, $published = array(), $languages = array(), $clientId = 0)
+ {
+ $hasClientId = $clientId !== null;
+ $clientId = (int) $clientId;
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'DISTINCT ' . $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.alias'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.menutype'),
+ $db->quoteName('a.client_id'),
+ $db->quoteName('a.type'),
+ $db->quoteName('a.published'),
+ $db->quoteName('a.template_style_id'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.language'),
+ $db->quoteName('a.lft'),
+ $db->quoteName('e.name', 'componentname'),
+ $db->quoteName('e.element'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'a'))
+ ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
+
+ if (Multilanguage::isEnabled()) {
+ $query->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ $db->quoteName('l.sef', 'language_sef'),
+ ]
+ )
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
+ }
+
+ // Filter by the type if given, this is more specific than client id
+ if ($menuType) {
+ $query->where('(' . $db->quoteName('a.menutype') . ' = :menuType OR ' . $db->quoteName('a.parent_id') . ' = 0)')
+ ->bind(':menuType', $menuType);
+ } elseif ($hasClientId) {
+ $query->where($db->quoteName('a.client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ // Prevent the parent and children from showing if requested.
+ if ($parentId && $mode == 2) {
+ $query->join('LEFT', $db->quoteName('#__menu', 'p'), $db->quoteName('p.id') . ' = :parentId')
+ ->where(
+ '(' . $db->quoteName('a.lft') . ' <= ' . $db->quoteName('p.lft')
+ . ' OR ' . $db->quoteName('a.rgt') . ' >= ' . $db->quoteName('p.rgt') . ')'
+ )
+ ->bind(':parentId', $parentId, ParameterType::INTEGER);
+ }
+
+ if (!empty($languages)) {
+ $query->whereIn($db->quoteName('a.language'), (array) $languages, ParameterType::STRING);
+ }
+
+ if (!empty($published)) {
+ $query->whereIn($db->quoteName('a.published'), (array) $published);
+ }
+
+ $query->where($db->quoteName('a.published') . ' != -2');
+ $query->order($db->quoteName('a.lft') . ' ASC');
+
+ try {
+ // Get the options.
+ $db->setQuery($query);
+ $links = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ if (empty($menuType)) {
+ // If the menutype is empty, group the items by menutype.
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__menu_types'))
+ ->where($db->quoteName('menutype') . ' <> ' . $db->quote(''))
+ ->order(
+ [
+ $db->quoteName('title'),
+ $db->quoteName('menutype'),
+ ]
+ );
+
+ if ($hasClientId) {
+ $query->where($db->quoteName('client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ try {
+ $db->setQuery($query);
+ $menuTypes = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ // Create a reverse lookup and aggregate the links.
+ $rlu = array();
+
+ foreach ($menuTypes as &$type) {
+ $rlu[$type->menutype] = & $type;
+ $type->links = array();
+ }
+
+ // Loop through the list of menu links.
+ foreach ($links as &$link) {
+ if (isset($rlu[$link->menutype])) {
+ $rlu[$link->menutype]->links[] = & $link;
+
+ // Cleanup garbage.
+ unset($link->menutype);
+ }
+ }
+
+ return $menuTypes;
+ } else {
+ return $links;
+ }
+ }
+
+ /**
+ * Get the associations
+ *
+ * @param integer $pk Menu item id
+ *
+ * @return array
+ *
+ * @since 3.0
+ */
+ public static function getAssociations($pk)
+ {
+ $langAssociations = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', $pk, 'id', '', '');
+ $associations = array();
+
+ foreach ($langAssociations as $langAssociation) {
+ $associations[$langAssociation->language] = $langAssociation->id;
+ }
+
+ return $associations;
+ }
+
+ /**
+ * Load the menu items from database for the given menutype
+ *
+ * @param string $menutype The selected menu type
+ * @param boolean $enabledOnly Whether to load only enabled/published menu items.
+ * @param int[] $exclude The menu items to exclude from the list
+ *
+ * @return AdministratorMenuItem A root node with the menu items as children
+ *
+ * @since 4.0.0
+ */
+ public static function getMenuItems($menutype, $enabledOnly = false, $exclude = array())
+ {
+ $root = new AdministratorMenuItem();
+ $db = Factory::getContainer()->get(DatabaseInterface::class);
+ $query = $db->getQuery(true);
+
+ // Prepare the query.
+ $query->select($db->quoteName('m') . '.*')
+ ->from($db->quoteName('#__menu', 'm'))
+ ->where(
+ [
+ $db->quoteName('m.menutype') . ' = :menutype',
+ $db->quoteName('m.client_id') . ' = 1',
+ $db->quoteName('m.id') . ' > 1',
+ ]
+ )
+ ->bind(':menutype', $menutype);
+
+ if ($enabledOnly) {
+ $query->where($db->quoteName('m.published') . ' = 1');
+ }
+
+ // Filter on the enabled states.
+ $query->select($db->quoteName('e.element'))
+ ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
+ ->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('e.enabled') . ' = 1',
+ $db->quoteName('e.enabled') . ' IS NULL',
+ ],
+ 'OR'
+ );
+
+ if (count($exclude)) {
+ $exId = array_map('intval', array_filter($exclude, 'is_numeric'));
+ $exEl = array_filter($exclude, 'is_string');
+
+ if ($exId) {
+ $query->whereNotIn($db->quoteName('m.id'), $exId)
+ ->whereNotIn($db->quoteName('m.parent_id'), $exId);
+ }
+
+ if ($exEl) {
+ $query->whereNotIn($db->quoteName('e.element'), $exEl, ParameterType::STRING);
+ }
+ }
+
+ // Order by lft.
+ $query->order($db->quoteName('m.lft'));
+
+ try {
+ $menuItems = [];
+ $iterator = $db->setQuery($query)->getIterator();
+
+ foreach ($iterator as $item) {
+ $menuItems[$item->id] = new AdministratorMenuItem((array) $item);
+ }
+
+ unset($iterator);
+
+ foreach ($menuItems as $menuitem) {
+ // Resolve the alias item to get the original item
+ if ($menuitem->type == 'alias') {
+ static::resolveAlias($menuitem);
+ }
+
+ if ($menuitem->link = in_array($menuitem->type, array('separator', 'heading', 'container')) ? '#' : trim($menuitem->link)) {
+ $menuitem->submenu = array();
+ $menuitem->class = $menuitem->img ?? '';
+ $menuitem->scope = $menuitem->scope ?? null;
+ $menuitem->target = $menuitem->browserNav ? '_blank' : '';
+ }
+
+ $menuitem->ajaxbadge = $menuitem->getParams()->get('ajax-badge');
+ $menuitem->dashboard = $menuitem->getParams()->get('dashboard');
+
+ if ($menuitem->parent_id > 1) {
+ if (isset($menuItems[$menuitem->parent_id])) {
+ $menuItems[$menuitem->parent_id]->addChild($menuitem);
+ }
+ } else {
+ $root->addChild($menuitem);
+ }
+ }
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
+ }
+
+ return $root;
+ }
+
+ /**
+ * Method to install a preset menu into database and link them to the given menutype
+ *
+ * @param string $preset The preset name
+ * @param string $menutype The target menutype
+ *
+ * @return void
+ *
+ * @throws \Exception
+ *
+ * @since 4.0.0
+ */
+ public static function installPreset($preset, $menutype)
+ {
+ $root = static::loadPreset($preset, false);
+
+ if (count($root->getChildren()) == 0) {
+ throw new \Exception(Text::_('COM_MENUS_PRESET_LOAD_FAILED'));
+ }
+
+ static::installPresetItems($root, $menutype);
+ }
+
+ /**
+ * Method to install a preset menu item into database and link it to the given menutype
+ *
+ * @param AdministratorMenuItem $node The parent node of the items to process
+ * @param string $menutype The target menutype
+ *
+ * @return void
+ *
+ * @throws \Exception
+ *
+ * @since 4.0.0
+ */
+ protected static function installPresetItems($node, $menutype)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $items = $node->getChildren();
+
+ static $components = array();
+
+ if (!$components) {
+ $query->select(
+ [
+ $db->quoteName('extension_id'),
+ $db->quoteName('element'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('component'));
+ $components = $db->setQuery($query)->loadObjectList();
+ $components = array_column((array) $components, 'element', 'extension_id');
+ }
+
+ Factory::getApplication()->triggerEvent('onPreprocessMenuItems', array('com_menus.administrator.import', &$items, null, true));
+
+ foreach ($items as $item) {
+ /** @var \Joomla\CMS\Table\Menu $table */
+ $table = Table::getInstance('Menu');
+
+ $item->alias = $menutype . '-' . $item->title;
+
+ // Temporarily set unicodeslugs if a menu item has an unicode alias
+ $unicode = Factory::getApplication()->set('unicodeslugs', 1);
+ $item->alias = ApplicationHelper::stringURLSafe($item->alias);
+ Factory::getApplication()->set('unicodeslugs', $unicode);
+
+ if ($item->type == 'separator') {
+ // Do not reuse a separator
+ $item->title = $item->title ?: '-';
+ $item->alias = microtime(true);
+ } elseif ($item->type == 'heading' || $item->type == 'container') {
+ // Try to match an existing record to have minimum collision for a heading
+ $keys = array(
+ 'menutype' => $menutype,
+ 'type' => $item->type,
+ 'title' => $item->title,
+ 'parent_id' => (int) $item->getParent()->id,
+ 'client_id' => 1,
+ );
+ $table->load($keys);
+ } elseif ($item->type == 'url' || $item->type == 'component') {
+ if (substr($item->link, 0, 8) === 'special:') {
+ $special = substr($item->link, 8);
+
+ if ($special === 'language-forum') {
+ $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
+ } elseif ($special === 'custom-forum') {
+ $item->link = '';
+ }
+ }
+
+ // Try to match an existing record to have minimum collision for a link
+ $keys = array(
+ 'menutype' => $menutype,
+ 'type' => $item->type,
+ 'link' => $item->link,
+ 'parent_id' => (int) $item->getParent()->id,
+ 'client_id' => 1,
+ );
+ $table->load($keys);
+ }
+
+ // Translate "hideitems" param value from "element" into "menu-item-id"
+ if ($item->type == 'container' && count($hideitems = (array) $item->getParams()->get('hideitems'))) {
+ foreach ($hideitems as &$hel) {
+ if (!is_numeric($hel)) {
+ $hel = array_search($hel, $components);
+ }
+ }
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->whereIn($db->quoteName('component_id'), $hideitems);
+ $hideitems = $db->setQuery($query)->loadColumn();
+
+ $item->getParams()->set('hideitems', $hideitems);
+ }
+
+ $record = array(
+ 'menutype' => $menutype,
+ 'title' => $item->title,
+ 'alias' => $item->alias,
+ 'type' => $item->type,
+ 'link' => $item->link,
+ 'browserNav' => $item->browserNav,
+ 'img' => $item->class,
+ 'access' => $item->access,
+ 'component_id' => array_search($item->element, $components) ?: 0,
+ 'parent_id' => (int) $item->getParent()->id,
+ 'client_id' => 1,
+ 'published' => 1,
+ 'language' => '*',
+ 'home' => 0,
+ 'params' => (string) $item->getParams(),
+ );
+
+ if (!$table->bind($record)) {
+ throw new \Exception($table->getError());
+ }
+
+ $table->setLocation($item->getParent()->id, 'last-child');
+
+ if (!$table->check()) {
+ throw new \Exception($table->getError());
+ }
+
+ if (!$table->store()) {
+ throw new \Exception($table->getError());
+ }
+
+ $item->id = $table->get('id');
+
+ if ($item->hasChildren()) {
+ static::installPresetItems($item, $menutype);
+ }
+ }
+ }
+
+ /**
+ * Add a custom preset externally via plugin or any other means.
+ * WARNING: Presets with same name will replace previously added preset *except* Joomla's default preset (joomla)
+ *
+ * @param string $name The unique identifier for the preset.
+ * @param string $title The display label for the preset.
+ * @param string $path The path to the preset file.
+ * @param bool $replace Whether to replace the preset with the same name if any (except 'joomla').
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public static function addPreset($name, $title, $path, $replace = true)
+ {
+ if (static::$presets === null) {
+ static::getPresets();
+ }
+
+ if ($name == 'joomla') {
+ $replace = false;
+ }
+
+ if (($replace || !array_key_exists($name, static::$presets)) && is_file($path)) {
+ $preset = new \stdClass();
+
+ $preset->name = $name;
+ $preset->title = $title;
+ $preset->path = $path;
+
+ static::$presets[$name] = $preset;
+ }
+ }
+
+ /**
+ * Get a list of available presets.
+ *
+ * @return \stdClass[]
+ *
+ * @since 4.0.0
+ */
+ public static function getPresets()
+ {
+ if (static::$presets === null) {
+ // Important: 'null' will cause infinite recursion.
+ static::$presets = array();
+
+ $components = ComponentHelper::getComponents();
+ $lang = Factory::getApplication()->getLanguage();
+
+ foreach ($components as $component) {
+ if (!$component->enabled) {
+ continue;
+ }
+
+ $folder = JPATH_ADMINISTRATOR . '/components/' . $component->option . '/presets/';
+
+ if (!Folder::exists($folder)) {
+ continue;
+ }
+
+ $lang->load($component->option . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($component->option . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->option);
+
+ $presets = Folder::files($folder, '.xml');
+
+ foreach ($presets as $preset) {
+ $name = File::stripExt($preset);
+ $title = strtoupper($component->option . '_MENUS_PRESET_' . $name);
+ static::addPreset($name, $title, $folder . $preset);
+ }
+ }
+
+ // Load from template folder automatically
+ $app = Factory::getApplication();
+ $tpl = JPATH_THEMES . '/' . $app->getTemplate() . '/html/com_menus/presets';
+
+ if (is_dir($tpl)) {
+ $files = Folder::files($tpl, '\.xml$');
+
+ foreach ($files as $file) {
+ $name = substr($file, 0, -4);
+ $title = str_replace('-', ' ', $name);
+
+ static::addPreset(strtolower($name), ucwords($title), $tpl . '/' . $file);
+ }
+ }
+ }
+
+ return static::$presets;
+ }
+
+ /**
+ * Load the menu items from a preset file into a hierarchical list of objects
+ *
+ * @param string $name The preset name
+ * @param bool $fallback Fallback to default (joomla) preset if the specified one could not be loaded?
+ * @param AdministratorMenuItem $parent Root node of the menu
+ *
+ * @return AdministratorMenuItem
+ *
+ * @since 4.0.0
+ */
+ public static function loadPreset($name, $fallback = true, $parent = null)
+ {
+ $presets = static::getPresets();
+
+ if (!$parent) {
+ $parent = new AdministratorMenuItem();
+ }
+
+ if (isset($presets[$name]) && ($xml = simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) {
+ static::loadXml($xml, $parent);
+ } elseif ($fallback && isset($presets['default'])) {
+ if (($xml = simplexml_load_file($presets['default']->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) {
+ static::loadXml($xml, $parent);
+ }
+ }
+
+ return $parent;
+ }
+
+ /**
+ * Method to resolve the menu item alias type menu item
+ *
+ * @param AdministratorMenuItem &$item The alias object
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public static function resolveAlias(&$item)
+ {
+ $obj = $item;
+
+ while ($obj->type == 'alias') {
+ $aliasTo = (int) $obj->getParams()->get('aliasoptions');
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.link'),
+ $db->quoteName('a.type'),
+ $db->quoteName('e.element'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'a'))
+ ->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'))
+ ->where($db->quoteName('a.id') . ' = :aliasTo')
+ ->bind(':aliasTo', $aliasTo, ParameterType::INTEGER);
+
+ try {
+ $obj = new AdministratorMenuItem($db->setQuery($query)->loadAssoc());
+
+ if (!$obj) {
+ $item->link = '';
+
+ return;
+ }
+ } catch (\Exception $e) {
+ $item->link = '';
+
+ return;
+ }
+ }
+
+ $item->id = $obj->id;
+ $item->link = $obj->link;
+ $item->type = $obj->type;
+ $item->element = $obj->element;
+ }
+
+ /**
+ * Parse the flat list of menu items and prepare the hierarchy of them using parent-child relationship.
+ *
+ * @param AdministratorMenuItem $item Menu item to preprocess
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public static function preprocess($item)
+ {
+ // Resolve the alias item to get the original item
+ if ($item->type == 'alias') {
+ static::resolveAlias($item);
+ }
+
+ if ($item->link = in_array($item->type, array('separator', 'heading', 'container')) ? '#' : trim($item->link)) {
+ $item->class = $item->img ?? '';
+ $item->scope = $item->scope ?? null;
+ $item->target = $item->browserNav ? '_blank' : '';
+ }
+ }
+
+ /**
+ * Load a menu tree from an XML file
+ *
+ * @param \SimpleXMLElement[] $elements The xml menuitem nodes
+ * @param AdministratorMenuItem $parent The menu hierarchy list to be populated
+ * @param string[] $replace The substring replacements for iterator type items
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected static function loadXml($elements, $parent, $replace = array())
+ {
+ foreach ($elements as $element) {
+ if ($element->getName() != 'menuitem') {
+ continue;
+ }
+
+ $select = (string) $element['sql_select'];
+ $from = (string) $element['sql_from'];
+
+ /**
+ * Following is a repeatable group based on simple database query. This requires sql_* attributes (sql_select and sql_from are required)
+ * The values can be used like - "{sql:columnName}" in any attribute of repeated elements.
+ * The repeated elements are place inside this xml node but they will be populated in the same level in the rendered menu
+ */
+ if ($select && $from) {
+ $hidden = $element['hidden'] == 'true';
+ $where = (string) $element['sql_where'];
+ $order = (string) $element['sql_order'];
+ $group = (string) $element['sql_group'];
+ $lJoin = (string) $element['sql_leftjoin'];
+ $iJoin = (string) $element['sql_innerjoin'];
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $query->select($select)->from($from);
+
+ if ($where) {
+ $query->where($where);
+ }
+
+ if ($order) {
+ $query->order($order);
+ }
+
+ if ($group) {
+ $query->group($group);
+ }
+
+ if ($lJoin) {
+ $query->join('LEFT', $lJoin);
+ }
+
+ if ($iJoin) {
+ $query->join('INNER', $iJoin);
+ }
+
+ $results = $db->setQuery($query)->loadObjectList();
+
+ // Skip the entire group if no items to iterate over.
+ if ($results) {
+ // Show the repeatable group heading node only if not set as hidden.
+ if (!$hidden) {
+ $child = static::parseXmlNode($element, $replace);
+ $parent->addChild($child);
+ }
+
+ // Iterate over the matching records, items goes in the same level (not $item->submenu) as this node.
+ if ('self' == (string) $element['sql_target']) {
+ foreach ($results as $result) {
+ static::loadXml($element->menuitem, $child, $result);
+ }
+ } else {
+ foreach ($results as $result) {
+ static::loadXml($element->menuitem, $parent, $result);
+ }
+ }
+ }
+ } else {
+ $item = static::parseXmlNode($element, $replace);
+
+ // Process the child nodes
+ static::loadXml($element->menuitem, $item, $replace);
+
+ $parent->addChild($item);
+ }
+ }
+ }
+
+ /**
+ * Create a menu item node from an xml element
+ *
+ * @param \SimpleXMLElement $node A menuitem element from preset xml
+ * @param string[] $replace The values to substitute in the title, link and element texts
+ *
+ * @return \stdClass
+ *
+ * @since 4.0.0
+ */
+ protected static function parseXmlNode($node, $replace = array())
+ {
+ $item = new AdministratorMenuItem();
+
+ $item->id = null;
+ $item->type = (string) $node['type'];
+ $item->title = (string) $node['title'];
+ $item->alias = (string) $node['alias'];
+ $item->link = (string) $node['link'];
+ $item->target = (string) $node['target'];
+ $item->element = (string) $node['element'];
+ $item->class = (string) $node['class'];
+ $item->icon = (string) $node['icon'];
+ $item->access = (int) $node['access'];
+ $item->scope = (string) $node['scope'] ?: 'default';
+ $item->ajaxbadge = (string) $node['ajax-badge'];
+ $item->dashboard = (string) $node['dashboard'];
+
+ $params = new Registry(trim($node->params));
+ $params->set('menu-permission', (string) $node['permission']);
+
+ if ($item->type == 'separator' && trim($item->title, '- ')) {
+ $params->set('text_separator', 1);
+ }
+
+ if ($item->type == 'heading' || $item->type == 'container') {
+ $item->link = '#';
+ }
+
+ if ((string) $node['quicktask']) {
+ $params->set('menu-quicktask', (string) $node['quicktask']);
+ $params->set('menu-quicktask-title', (string) $node['quicktask-title']);
+ $params->set('menu-quicktask-icon', (string) $node['quicktask-icon']);
+ $params->set('menu-quicktask-permission', (string) $node['quicktask-permission']);
+ }
+
+ // Translate attributes for iterator values
+ foreach ($replace as $var => $val) {
+ $item->title = str_replace("{sql:$var}", $val, $item->title);
+ $item->element = str_replace("{sql:$var}", $val, $item->element);
+ $item->link = str_replace("{sql:$var}", $val, $item->link);
+ $item->class = str_replace("{sql:$var}", $val, $item->class);
+ $item->icon = str_replace("{sql:$var}", $val, $item->icon);
+ $params->set('menu-quicktask', str_replace("{sql:$var}", $val, $params->get('menu-quicktask')));
+ }
+
+ $item->setParams($params);
+
+ return $item;
+ }
}
diff --git a/administrator/components/com_menus/src/Model/ItemModel.php b/administrator/components/com_menus/src/Model/ItemModel.php
index 01437f6a9a13c..698d5034d341f 100644
--- a/administrator/components/com_menus/src/Model/ItemModel.php
+++ b/administrator/components/com_menus/src/Model/ItemModel.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage'
- );
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- $menuTypeId = 0;
-
- if (!empty($record->menutype))
- {
- $menuTypeId = $this->getMenuTypeId($record->menutype);
- }
-
- return Factory::getUser()->authorise('core.delete', 'com_menus.menu.' . (int) $menuTypeId);
- }
-
- /**
- * Method to test whether the state of a record can be edited.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component.
- *
- * @since 3.6
- */
- protected function canEditState($record)
- {
- $menuTypeId = !empty($record->menutype) ? $this->getMenuTypeId($record->menutype) : 0;
- $assetKey = $menuTypeId ? 'com_menus.menu.' . (int) $menuTypeId : 'com_menus';
-
- return Factory::getUser()->authorise('core.edit.state', $assetKey);
- }
-
- /**
- * Batch copy menu items to a new menu or parent.
- *
- * @param integer $value The new menu or sub-item.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return mixed An array of new IDs on success, boolean false on failure.
- *
- * @since 1.6
- */
- protected function batchCopy($value, $pks, $contexts)
- {
- // $value comes as {menutype}.{parent_id}
- $parts = explode('.', $value);
- $menuType = $parts[0];
- $parentId = ArrayHelper::getValue($parts, 1, 0, 'int');
-
- $table = $this->getTable();
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $newIds = array();
-
- // Check that the parent exists
- if ($parentId)
- {
- if (!$table->load($parentId))
- {
- if ($error = $table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Non-fatal error
- $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
- $parentId = 0;
- }
- }
- }
-
- // If the parent is 0, set it to the ID of the root item in the tree
- if (empty($parentId))
- {
- if (!$parentId = $table->getRootId())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Check that user has create permission for menus
- $user = Factory::getUser();
-
- $menuTypeId = (int) $this->getMenuTypeId($menuType);
-
- if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId))
- {
- $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
-
- return false;
- }
-
- // We need to log the parent ID
- $parents = array();
-
- // Calculate the emergency stop count as a precaution against a runaway loop bug
- $query->select('COUNT(' . $db->quoteName('id') . ')')
- ->from($db->quoteName('#__menu'));
- $db->setQuery($query);
-
- try
- {
- $count = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Parent exists so let's proceed
- while (!empty($pks) && $count > 0)
- {
- // Pop the first id off the stack
- $pk = array_shift($pks);
-
- $table->reset();
-
- // Check that the row actually exists
- if (!$table->load($pk))
- {
- if ($error = $table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Not fatal error
- $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
- continue;
- }
- }
-
- // Copy is a bit tricky, because we also need to copy the children
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->where(
- [
- $db->quoteName('lft') . ' > :lft',
- $db->quoteName('rgt') . ' < :rgt',
- ]
- )
- ->bind(':lft', $table->lft, ParameterType::INTEGER)
- ->bind(':rgt', $table->rgt, ParameterType::INTEGER);
- $db->setQuery($query);
- $childIds = $db->loadColumn();
-
- // Add child IDs to the array only if they aren't already there.
- foreach ($childIds as $childId)
- {
- if (!in_array($childId, $pks))
- {
- $pks[] = $childId;
- }
- }
-
- // Make a copy of the old ID and Parent ID
- $oldId = $table->id;
- $oldParentId = $table->parent_id;
-
- // Reset the id because we are making a copy.
- $table->id = 0;
-
- // If we a copying children, the Old ID will turn up in the parents list
- // otherwise it's a new top level item
- $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId;
- $table->menutype = $menuType;
-
- // Set the new location in the tree for the node.
- $table->setLocation($table->parent_id, 'last-child');
-
- // @todo: Deal with ordering?
- // $table->ordering = 1;
- $table->level = null;
- $table->lft = null;
- $table->rgt = null;
- $table->home = 0;
-
- // Alter the title & alias
- list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
- $table->title = $title;
- $table->alias = $alias;
-
- // Check the row.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Store the row.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Get the new item ID
- $newId = $table->get('id');
-
- // Add the new ID to the array
- $newIds[$pk] = $newId;
-
- // Now we log the old 'parent' to the new 'parent'
- $parents[$oldId] = $table->id;
- $count--;
- }
-
- // Rebuild the hierarchy.
- if (!$table->rebuild())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Rebuild the tree path.
- if (!$table->rebuildPath($table->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return $newIds;
- }
-
- /**
- * Batch move menu items to a new menu or parent.
- *
- * @param integer $value The new menu or sub-item.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- protected function batchMove($value, $pks, $contexts)
- {
- // $value comes as {menutype}.{parent_id}
- $parts = explode('.', $value);
- $menuType = $parts[0];
- $parentId = ArrayHelper::getValue($parts, 1, 0, 'int');
-
- $table = $this->getTable();
- $db = $this->getDatabase();
-
- // Check that the parent exists.
- if ($parentId)
- {
- if (!$table->load($parentId))
- {
- if ($error = $table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Non-fatal error
- $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
- $parentId = 0;
- }
- }
- }
-
- // Check that user has create and edit permission for menus
- $user = Factory::getUser();
-
- $menuTypeId = (int) $this->getMenuTypeId($menuType);
-
- if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId))
- {
- $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
-
- return false;
- }
-
- if (!$user->authorise('core.edit', 'com_menus.menu.' . $menuTypeId))
- {
- $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_EDIT'));
-
- return false;
- }
-
- // We are going to store all the children and just moved the menutype
- $children = array();
-
- // Parent exists so let's proceed
- foreach ($pks as $pk)
- {
- // Check that the row actually exists
- if (!$table->load($pk))
- {
- if ($error = $table->getError())
- {
- // Fatal error
- $this->setError($error);
-
- return false;
- }
- else
- {
- // Not fatal error
- $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
- continue;
- }
- }
-
- // Set the new location in the tree for the node.
- $table->setLocation($parentId, 'last-child');
-
- // Set the new Parent Id
- $table->parent_id = $parentId;
-
- // Check if we are moving to a different menu
- if ($menuType != $table->menutype)
- {
- // Add the child node ids to the children array.
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt')
- ->bind(':lft', $table->lft, ParameterType::INTEGER)
- ->bind(':rgt', $table->rgt, ParameterType::INTEGER);
- $db->setQuery($query);
- $children = array_merge($children, (array) $db->loadColumn());
- }
-
- // Check the row.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Store the row.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Rebuild the tree path.
- if (!$table->rebuildPath())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Process the child rows
- if (!empty($children))
- {
- // Remove any duplicates and sanitize ids.
- $children = array_unique($children);
- $children = ArrayHelper::toInteger($children);
-
- // Update the menutype field in all nodes where necessary.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('menutype') . ' = :menuType')
- ->whereIn($db->quoteName('id'), $children)
- ->bind(':menuType', $menuType);
-
- try
- {
- $db->setQuery($query);
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to check if you can save a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function canSave($data = array(), $key = 'id')
- {
- return Factory::getUser()->authorise('core.edit', $this->option);
- }
-
- /**
- * Method to get the row form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return mixed A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // The folder and element vars are passed when saving the form.
- if (empty($data))
- {
- $item = $this->getItem();
-
- // The type should already be set.
- $this->setState('item.link', $item->link);
- }
- else
- {
- $this->setState('item.link', ArrayHelper::getValue($data, 'link'));
- $this->setState('item.type', ArrayHelper::getValue($data, 'type'));
- }
-
- $clientId = $this->getState('item.client_id');
-
- // Get the form.
- if ($clientId == 1)
- {
- $form = $this->loadForm('com_menus.item.admin', 'itemadmin', array('control' => 'jform', 'load_data' => $loadData), true);
- }
- else
- {
- $form = $this->loadForm('com_menus.item', 'item', array('control' => 'jform', 'load_data' => $loadData), true);
- }
-
- if (empty($form))
- {
- return false;
- }
-
- if ($loadData)
- {
- $data = $this->loadFormData();
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('menuordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is an article you can edit.
- $form->setFieldAttribute('menuordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- // Filter available menus
- $action = $this->getState('item.id') > 0 ? 'edit' : 'create';
-
- $form->setFieldAttribute('menutype', 'accesstype', $action);
- $form->setFieldAttribute('type', 'clientid', $clientId);
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data, providing it has an ID and it is the same.
- $itemData = (array) $this->getItem();
-
- // When a new item is requested, unset the access as it will be set later from the filter
- if (empty($itemData['id']))
- {
- unset($itemData['access']);
- }
-
- $sessionData = (array) Factory::getApplication()->getUserState('com_menus.edit.item.data', array());
-
- // Only merge if there is a session and itemId or itemid is null.
- if (isset($sessionData['id']) && isset($itemData['id']) && $sessionData['id'] === $itemData['id']
- || is_null($itemData['id']))
- {
- $data = array_merge($itemData, $sessionData);
- }
- else
- {
- $data = $itemData;
- }
-
- // For a new menu item, pre-select some filters (Status, Language, Access) in edit form if those have been selected in Menu Manager
- if (empty($data['id']))
- {
- // Get selected fields
- $filters = Factory::getApplication()->getUserState('com_menus.items.filter');
- $data['parent_id'] = $data['parent_id'] ?? ($filters['parent_id'] ?? null);
- $data['published'] = $data['published'] ?? ($filters['published'] ?? null);
- $data['language'] = $data['language'] ?? ($filters['language'] ?? null);
- $data['access'] = $data['access'] ?? ($filters['access'] ?? Factory::getApplication()->get('access'));
- }
-
- if (isset($data['menutype']) && !$this->getState('item.menutypeid'))
- {
- $menuTypeId = (int) $this->getMenuTypeId($data['menutype']);
-
- $this->setState('item.menutypeid', $menuTypeId);
- }
-
- $data = (object) $data;
-
- $this->preprocessData('com_menus.item', $data);
-
- return $data;
- }
-
- /**
- * Get the necessary data to load an item help screen.
- *
- * @return object An object with key, url, and local properties for loading the item help screen.
- *
- * @since 1.6
- */
- public function getHelp()
- {
- return (object) array('key' => $this->helpKey, 'url' => $this->helpURL, 'local' => $this->helpLocal);
- }
-
- /**
- * Method to get a menu item.
- *
- * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
- *
- * @return mixed Menu item data object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
-
- // Get a level row instance.
- $table = $this->getTable();
-
- // Attempt to load the row.
- $table->load($pk);
-
- // Check for a table object error.
- if ($error = $table->getError())
- {
- $this->setError($error);
-
- return false;
- }
-
- // Prime required properties.
-
- if ($type = $this->getState('item.type'))
- {
- $table->type = $type;
- }
-
- if (empty($table->id))
- {
- $table->parent_id = $this->getState('item.parent_id');
- $table->menutype = $this->getState('item.menutype');
- $table->client_id = $this->getState('item.client_id');
- $table->params = '{}';
- }
-
- // If the link has been set in the state, possibly changing link type.
- if ($link = $this->getState('item.link'))
- {
- // Check if we are changing away from the actual link type.
- if (MenusHelper::getLinkKey($table->link) !== MenusHelper::getLinkKey($link) && (int) $table->id === (int) $this->getState('item.id'))
- {
- $table->link = $link;
- }
- }
-
- switch ($table->type)
- {
- case 'alias':
- case 'url':
- $table->component_id = 0;
- $args = array();
-
- if ($table->link)
- {
- $q = parse_url($table->link, PHP_URL_QUERY);
-
- if ($q)
- {
- parse_str($q, $args);
- }
- }
-
- break;
-
- case 'separator':
- case 'heading':
- case 'container':
- $table->link = '';
- $table->component_id = 0;
- break;
-
- case 'component':
- default:
- // Enforce a valid type.
- $table->type = 'component';
-
- // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type.
- $args = [];
-
- if ($table->link)
- {
- $q = parse_url($table->link, PHP_URL_QUERY);
-
- if ($q)
- {
- parse_str($q, $args);
- }
- }
-
- if (isset($args['option']))
- {
- // Load the language file for the component.
- $lang = Factory::getLanguage();
- $lang->load($args['option'], JPATH_ADMINISTRATOR)
- || $lang->load($args['option'], JPATH_ADMINISTRATOR . '/components/' . $args['option']);
-
- // Determine the component id.
- $component = ComponentHelper::getComponent($args['option']);
-
- if (isset($component->id))
- {
- $table->component_id = $component->id;
- }
- }
- break;
- }
-
- // We have a valid type, inject it into the state for forms to use.
- $this->setState('item.type', $table->type);
-
- // Convert to the \Joomla\CMS\Object\CMSObject before adding the params.
- $properties = $table->getProperties(1);
- $result = ArrayHelper::toObject($properties);
-
- // Convert the params field to an array.
- $registry = new Registry($table->params);
- $result->params = $registry->toArray();
-
- // Merge the request arguments in to the params for a component.
- if ($table->type == 'component')
- {
- // Note that all request arguments become reserved parameter names.
- $result->request = $args;
- $result->params = array_merge($result->params, $args);
-
- // Special case for the Login menu item.
- // Display the login or logout redirect URL fields if not empty
- if ($table->link == 'index.php?option=com_users&view=login')
- {
- if (!empty($result->params['login_redirect_url']))
- {
- $result->params['loginredirectchoice'] = '0';
- }
-
- if (!empty($result->params['logout_redirect_url']))
- {
- $result->params['logoutredirectchoice'] = '0';
- }
- }
- }
-
- if ($table->type == 'alias')
- {
- // Note that all request arguments become reserved parameter names.
- $result->params = array_merge($result->params, $args);
- }
-
- if ($table->type == 'url')
- {
- // Note that all request arguments become reserved parameter names.
- $result->params = array_merge($result->params, $args);
- }
-
- // Load associated menu items, only supported for frontend for now
- if ($this->getState('item.client_id') == 0 && Associations::isEnabled())
- {
- if ($pk != null)
- {
- $result->associations = MenusHelper::getAssociations($pk);
- }
- else
- {
- $result->associations = array();
- }
- }
-
- $result->menuordering = $pk;
-
- return $result;
- }
-
- /**
- * Get the list of modules not in trash.
- *
- * @return mixed An array of module records (id, title, position), or false on error.
- *
- * @since 1.6
- */
- public function getModules()
- {
- $clientId = (int) $this->getState('item.client_id');
- $id = (int) $this->getState('item.id');
-
- // Currently any setting that affects target page for a backend menu is not supported, hence load no modules.
- if ($clientId == 1)
- {
- return false;
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- /**
- * Join on the module-to-menu mapping table.
- * We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number).
- * sqlsrv changes for modulelink to menu manager
- */
- $query->select(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.title'),
- $db->quoteName('a.position'),
- $db->quoteName('a.published'),
- $db->quoteName('map.menuid'),
- ]
- )
- ->from($db->quoteName('#__modules', 'a'))
- ->join(
- 'LEFT',
- $db->quoteName('#__modules_menu', 'map'),
- $db->quoteName('map.moduleid') . ' = ' . $db->quoteName('a.id')
- . ' AND ' . $db->quoteName('map.menuid') . ' IN (' . implode(',', $query->bindArray([0, $id, -$id])) . ')'
- );
-
- $subQuery = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName('#__modules_menu'))
- ->where(
- [
- $db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'),
- $db->quoteName('menuid') . ' < 0',
- ]
- );
-
- $query->select('(' . $subQuery . ') AS ' . $db->quoteName('except'));
-
- // Join on the asset groups table.
- $query->select($db->quoteName('ag.title', 'access_title'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
- ->where(
- [
- $db->quoteName('a.published') . ' >= 0',
- $db->quoteName('a.client_id') . ' = :clientId',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER)
- ->order(
- [
- $db->quoteName('a.position'),
- $db->quoteName('a.ordering'),
- ]
- );
-
- $db->setQuery($query);
-
- try
- {
- $result = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $result;
- }
-
- /**
- * Get the list of all view levels
- *
- * @return \stdClass[]|boolean An array of all view levels (id, title).
- *
- * @since 3.4
- */
- public function getViewLevels()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Get all the available view levels
- $query->select($db->quoteName('id'))
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__viewlevels'))
- ->order($db->quoteName('id'));
-
- $db->setQuery($query);
-
- try
- {
- $result = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $result;
- }
-
- /**
- * Returns a Table object, always creating it
- *
- * @param string $type The table type to instantiate.
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\Cms\Table\Table|\Joomla\Cms\Table\Nested A database object.
- *
- * @since 1.6
- */
- public function getTable($type = 'Menu', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
-
- /**
- * A protected method to get the where clause for the reorder.
- * This ensures that the row will be moved relative to a row with the same menutype.
- *
- * @param \Joomla\CMS\Table\Menu $table
- *
- * @return array An array of conditions to add to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('menutype') . ' = ' . $db->quote($table->menutype),
- ];
- }
-
- /**
- * Auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState('item.id', $pk);
-
- if (!$app->isClient('api'))
- {
- $parentId = $app->getUserState('com_menus.edit.item.parent_id');
- $menuType = $app->getUserStateFromRequest('com_menus.items.menutype', 'menutype', '', 'string');
- }
- else
- {
- $parentId = null;
- $menuType = $app->input->get('com_menus.items.menutype');
- }
-
- if (!$parentId)
- {
- $parentId = $app->input->getInt('parent_id');
- }
-
- $this->setState('item.parent_id', $parentId);
-
- // If we have a menutype we take client_id from there, unless forced otherwise
- if ($menuType)
- {
- $menuTypeObj = $this->getMenuType($menuType);
-
- // An invalid menutype will be handled as clientId = 0 and menuType = ''
- $menuType = (string) $menuTypeObj->menutype;
- $menuTypeId = (int) $menuTypeObj->client_id;
- $clientId = (int) $menuTypeObj->client_id;
- }
- else
- {
- $menuTypeId = 0;
- $clientId = $app->isClient('api') ? $app->input->get('client_id') :
- $app->getUserState('com_menus.items.client_id', 0);
- }
-
- // Forced client id will override/clear menuType if conflicted
- $forcedClientId = $app->input->get('client_id', null, 'string');
-
- if (!$app->isClient('api'))
- {
- // Set the menu type and client id on the list view state, so we return to this menu after saving.
- $app->setUserState('com_menus.items.menutype', $menuType);
- $app->setUserState('com_menus.items.client_id', $clientId);
- }
-
- // Current item if not new, we don't allow changing client id at all
- if ($pk)
- {
- $table = $this->getTable();
- $table->load($pk);
- $forcedClientId = $table->get('client_id', $forcedClientId);
- }
-
- if (isset($forcedClientId) && $forcedClientId != $clientId)
- {
- $clientId = $forcedClientId;
- $menuType = '';
- $menuTypeId = 0;
- }
-
- $this->setState('item.menutype', $menuType);
- $this->setState('item.client_id', $clientId);
- $this->setState('item.menutypeid', $menuTypeId);
-
- if (!($type = $app->getUserState('com_menus.edit.item.type')))
- {
- $type = $app->input->get('type');
-
- /**
- * Note: a new menu item will have no field type.
- * The field is required so the user has to change it.
- */
- }
-
- $this->setState('item.type', $type);
-
- $link = $app->isClient('api') ? $app->input->get('link') :
- $app->getUserState('com_menus.edit.item.link');
-
- if ($link)
- {
- $this->setState('item.link', $link);
- }
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_menus');
- $this->setState('params', $params);
- }
-
- /**
- * Loads the menutype object by a given menutype string
- *
- * @param string $menutype The given menutype
- *
- * @return \stdClass
- *
- * @since 3.7.0
- */
- protected function getMenuType($menutype)
- {
- $table = $this->getTable('MenuType');
-
- $table->load(array('menutype' => $menutype));
-
- return (object) $table->getProperties();
- }
-
- /**
- * Loads the menutype ID by a given menutype string
- *
- * @param string $menutype The given menutype
- *
- * @return integer
- *
- * @since 3.6
- */
- protected function getMenuTypeId($menutype)
- {
- $menu = $this->getMenuType($menutype);
-
- return (int) $menu->id;
- }
-
- /**
- * Method to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import.
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $link = $this->getState('item.link');
- $type = $this->getState('item.type');
- $clientId = $this->getState('item.client_id');
- $formFile = false;
-
- // Load the specific type file
- $typeFile = $clientId == 1 ? 'itemadmin_' . $type : 'item_' . $type;
- $clientInfo = ApplicationHelper::getClientInfo($clientId);
-
- // Initialise form with component view params if available.
- if ($type == 'component')
- {
- $link = $link ? htmlspecialchars_decode($link) : '';
-
- // Parse the link arguments.
- $args = [];
-
- if ($link)
- {
- parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args);
- }
-
- // Confirm that the option is defined.
- $option = '';
- $base = '';
-
- if (isset($args['option']))
- {
- // The option determines the base path to work with.
- $option = $args['option'];
- $base = $clientInfo->path . '/components/' . $option;
- }
-
- if (isset($args['view']))
- {
- $view = $args['view'];
-
- // Determine the layout to search for.
- if (isset($args['layout']))
- {
- $layout = $args['layout'];
- }
- else
- {
- $layout = 'default';
- }
-
- // Check for the layout XML file. Use standard xml file if it exists.
- $tplFolders = array(
- $base . '/tmpl/' . $view,
- $base . '/views/' . $view . '/tmpl',
- $base . '/view/' . $view . '/tmpl',
- );
- $path = Path::find($tplFolders, $layout . '.xml');
-
- if (is_file($path))
- {
- $formFile = $path;
- }
-
- // If custom layout, get the xml file from the template folder
- // template folder is first part of file name -- template:folder
- if (!$formFile && (strpos($layout, ':') > 0))
- {
- list($altTmpl, $altLayout) = explode(':', $layout);
-
- $templatePath = Path::clean($clientInfo->path . '/templates/' . $altTmpl . '/html/' . $option . '/' . $view . '/' . $altLayout . '.xml');
-
- if (is_file($templatePath))
- {
- $formFile = $templatePath;
- }
- }
- }
-
- // Now check for a view manifest file
- if (!$formFile)
- {
- if (isset($view))
- {
- $metadataFolders = array(
- $base . '/view/' . $view,
- $base . '/views/' . $view
- );
- $metaPath = Path::find($metadataFolders, 'metadata.xml');
-
- if (is_file($path = Path::clean($metaPath)))
- {
- $formFile = $path;
- }
- }
- elseif ($base)
- {
- // Now check for a component manifest file
- $path = Path::clean($base . '/metadata.xml');
-
- if (is_file($path))
- {
- $formFile = $path;
- }
- }
- }
- }
-
- if ($formFile)
- {
- // If an XML file was found in the component, load it first.
- // We need to qualify the full path to avoid collisions with component file names.
-
- if ($form->loadFile($formFile, true, '/metadata') == false)
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Get the help data from the XML file if present.
- $help = $xml->xpath('/metadata/layout/help');
- }
- else
- {
- // We don't have a component. Load the form XML to get the help path
- $xmlFile = Path::find(JPATH_ADMINISTRATOR . '/components/com_menus/forms', $typeFile . '.xml');
-
- if ($xmlFile)
- {
- if (!$xml = simplexml_load_file($xmlFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Get the help data from the XML file if present.
- $help = $xml->xpath('/form/help');
- }
- }
-
- if (!empty($help))
- {
- $helpKey = trim((string) $help[0]['key']);
- $helpURL = trim((string) $help[0]['url']);
- $helpLoc = trim((string) $help[0]['local']);
-
- $this->helpKey = $helpKey ?: $this->helpKey;
- $this->helpURL = $helpURL ?: $this->helpURL;
- $this->helpLocal = (($helpLoc == 'true') || ($helpLoc == '1') || ($helpLoc == 'local'));
- }
-
- if (!$form->loadFile($typeFile, true, false))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Association menu items, we currently do not support this for admin menu… may be later
- if ($clientId == 0 && Associations::isEnabled())
- {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (count($languages) > 1)
- {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
- $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Menus\Administrator\Field');
-
- foreach ($languages as $language)
- {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_menu');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE');
- $option->addAttribute('value', '');
- }
-
- $form->load($addform, false);
- }
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method rebuild the entire nested set tree.
- *
- * @return boolean Boolean true on success, boolean false
- *
- * @since 1.6
- */
- public function rebuild()
- {
- // Initialise variables.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $table = $this->getTable();
-
- try
- {
- $rebuildResult = $table->rebuild();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (!$rebuildResult)
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $query->select(
- [
- $db->quoteName('id'),
- $db->quoteName('params'),
- ]
- )
- ->from($db->quoteName('#__menu'))
- ->where(
- [
- $db->quoteName('params') . ' NOT LIKE ' . $db->quote('{%'),
- $db->quoteName('params') . ' <> ' . $db->quote(''),
- ]
- );
- $db->setQuery($query);
-
- try
- {
- $items = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('params') . ' = :params')
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':params', $params)
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
-
- foreach ($items as &$item)
- {
- // Update query parameters.
- $id = $item->id;
- $params = new Registry($item->params);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $pk = isset($data['id']) ? $data['id'] : (int) $this->getState('item.id');
- $isNew = true;
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $table = $this->getTable();
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the on save events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Load the row if saving an existing item.
- if ($pk > 0)
- {
- $table->load($pk);
- $isNew = false;
- }
-
- if (!$isNew)
- {
- if ($table->parent_id == $data['parent_id'])
- {
- // If first is chosen make the item the first child of the selected parent.
- if ($data['menuordering'] == -1)
- {
- $table->setLocation($data['parent_id'], 'first-child');
- }
- // If last is chosen make it the last child of the selected parent.
- elseif ($data['menuordering'] == -2)
- {
- $table->setLocation($data['parent_id'], 'last-child');
- }
- // Don't try to put an item after itself. All other ones put after the selected item.
- // $data['id'] is empty means it's a save as copy
- elseif ($data['menuordering'] && $table->id != $data['menuordering'] || empty($data['id']))
- {
- $table->setLocation($data['menuordering'], 'after');
- }
- // \Just leave it where it is if no change is made.
- elseif ($data['menuordering'] && $table->id == $data['menuordering'])
- {
- unset($data['menuordering']);
- }
- }
- // Set the new parent id if parent id not matched and put in last position
- else
- {
- $table->setLocation($data['parent_id'], 'last-child');
- }
-
- // Check if we are moving to a different menu
- if ($data['menutype'] != $table->menutype)
- {
- // Add the child node ids to the children array.
- $query->clear()
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt);
- $db->setQuery($query);
- $children = (array) $db->loadColumn();
- }
- }
- // We have a new item, so it is not a change.
- else
- {
- $menuType = $this->getMenuType($data['menutype']);
-
- $data['client_id'] = $menuType->client_id;
-
- $table->setLocation($data['parent_id'], 'last-child');
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Alter the title & alias for save2copy when required. Also, unset the home record.
- if (Factory::getApplication()->input->get('task') === 'save2copy' && $data['id'] === 0)
- {
- $origTable = $this->getTable();
- $origTable->load($this->getState('item.id'));
-
- if ($table->title === $origTable->title)
- {
- list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
- $table->title = $title;
- $table->alias = $alias;
- }
-
- if ($table->alias === $origTable->alias)
- {
- $table->alias = '';
- }
-
- $table->published = 0;
- $table->home = 0;
- }
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data));
-
- // Store the data.
- if (in_array(false, $result, true)|| !$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew));
-
- // Rebuild the tree path.
- if (!$table->rebuildPath($table->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Process the child rows
- if (!empty($children))
- {
- // Remove any duplicates and sanitize ids.
- $children = array_unique($children);
- $children = ArrayHelper::toInteger($children);
-
- // Update the menutype field in all nodes where necessary.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('menutype') . ' = :menutype')
- ->whereIn($db->quoteName('id'), $children)
- ->bind(':menutype', $data['menutype']);
-
- try
- {
- $db->setQuery($query);
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- $this->setState('item.id', $table->id);
- $this->setState('item.menutype', $table->menutype);
-
- // Load associated menu items, for now not supported for admin menu… may be later
- if ($table->get('client_id') == 0 && Associations::isEnabled())
- {
- // Adding self to the association
- $associations = isset($data['associations']) ? $data['associations'] : array();
-
- // Unset any invalid associations
- $associations = ArrayHelper::toInteger($associations);
-
- foreach ($associations as $tag => $id)
- {
- if (!$id)
- {
- unset($associations[$tag]);
- }
- }
-
- // Detecting all item menus
- $all_language = $table->language == '*';
-
- if ($all_language && !empty($associations))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice');
- }
-
- // Get associationskey for edited item
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('key'))
- ->from($db->quoteName('#__associations'))
- ->where(
- [
- $db->quoteName('context') . ' = :context',
- $db->quoteName('id') . ' = :id',
- ]
- )
- ->bind(':context', $this->associationsContext)
- ->bind(':id', $table->id, ParameterType::INTEGER);
- $db->setQuery($query);
- $oldKey = $db->loadResult();
-
- if ($associations || $oldKey !== null)
- {
- // Deleting old associations for the associated items
- $where = [];
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__associations'))
- ->where($db->quoteName('context') . ' = :context')
- ->bind(':context', $this->associationsContext);
-
- if ($associations)
- {
- $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
- }
-
- if ($oldKey !== null)
- {
- $where[] = $db->quoteName('key') . ' = :oldKey';
- $query->bind(':oldKey', $oldKey);
- }
-
- $query->extendWhere('AND', $where, 'OR');
-
- try
- {
- $db->setQuery($query);
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- // Adding self to the association
- if (!$all_language)
- {
- $associations[$table->language] = (int) $table->id;
- }
-
- if (count($associations) > 1)
- {
- // Adding new association for these items
- $key = md5(json_encode($associations));
- $query = $db->getQuery(true)
- ->insert($db->quoteName('#__associations'))
- ->columns(
- [
- $db->quoteName('id'),
- $db->quoteName('context'),
- $db->quoteName('key'),
- ]
- );
-
- foreach ($associations as $id)
- {
- $query->values(
- implode(
- ',',
- $query->bindArray(
- [$id, $this->associationsContext, $key],
- [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
- )
- )
- );
- }
-
- try
- {
- $db->setQuery($query);
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- if (isset($data['link']))
- {
- $base = Uri::base();
- $juri = Uri::getInstance($base . $data['link']);
- $option = $juri->getVar('option');
-
- // Clean the cache
- parent::cleanCache($option);
- }
-
- if (Factory::getApplication()->input->get('task') === 'editAssociations')
- {
- return $this->redirectToAssociations($data);
- }
-
- return true;
- }
-
- /**
- * Method to save the reordered nested set tree.
- * First we save the new order values in the lft values of the changed ids.
- * Then we invoke the table rebuild to implement the new ordering.
- *
- * @param array $idArray Rows identifiers to be reordered
- * @param array $lftArray lft values of rows to be reordered
- *
- * @return boolean false on failure or error, true otherwise.
- *
- * @since 1.6
- */
- public function saveorder($idArray = null, $lftArray = null)
- {
- // Get an instance of the table object.
- $table = $this->getTable();
-
- if (!$table->saveorder($idArray, $lftArray))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the home state of one or more items.
- *
- * @param array $pks A list of the primary keys to change.
- * @param integer $value The value of the home state.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function setHome(&$pks, $value = 1)
- {
- $table = $this->getTable();
- $pks = (array) $pks;
-
- $languages = array();
- $onehome = false;
-
- // Remember that we can set a home page for different languages,
- // so we need to loop through the primary key array.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- if (!array_key_exists($table->language, $languages))
- {
- $languages[$table->language] = true;
-
- if ($table->home == $value)
- {
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALREADY_HOME'), 'notice');
- }
- elseif ($table->menutype == 'main')
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_MENUTYPE_HOME'), 'error');
- }
- else
- {
- $table->home = $value;
-
- if ($table->language == '*')
- {
- $table->published = 1;
- }
-
- if (!$this->canSave($table))
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
- }
- elseif (!$table->check())
- {
- // Prune the items that failed pre-save checks.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage($table->getError(), 'error');
- }
- elseif (!$table->store())
- {
- // Prune the items that could not be stored.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage($table->getError(), 'error');
- }
- }
- }
- else
- {
- unset($pks[$i]);
-
- if (!$onehome)
- {
- $onehome = true;
- Factory::getApplication()->enqueueMessage(Text::sprintf('COM_MENUS_ERROR_ONE_HOME'), 'notice');
- }
- }
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array $pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function publish(&$pks, $value = 1)
- {
- $table = $this->getTable();
- $pks = (array) $pks;
-
- // Default menu item existence checks.
- if ($value != 1)
- {
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk) && $table->home && $table->language == '*')
- {
- // Prune items that you can't change.
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'), 'error');
- unset($pks[$i]);
- break;
- }
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- // Ensure that previous checks doesn't empty the array
- if (empty($pks))
- {
- return true;
- }
-
- return parent::publish($pks, $value);
- }
-
- /**
- * Method to change the title & alias.
- *
- * @param integer $parentId The id of the parent.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 1.6
- */
- protected function generateNewTitle($parentId, $alias, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('alias' => $alias, 'parent_id' => $parentId)))
- {
- if ($title == $table->title)
- {
- $title = StringHelper::increment($title);
- }
-
- $alias = StringHelper::increment($alias, 'dash');
- }
-
- return array($title, $alias);
- }
-
- /**
- * Custom clean the cache
- *
- * @param string $group Cache group name.
- * @param integer $clientId @deprecated 5.0 No Longer Used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_menus');
- parent::cleanCache('com_modules');
- parent::cleanCache('mod_menu');
- }
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.4
+ */
+ public $typeAlias = 'com_menus.item';
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_menus.item';
+
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_MENUS_ITEM';
+
+ /**
+ * @var string The help screen key for the menu item.
+ * @since 1.6
+ */
+ protected $helpKey = 'Menu_Item:_New_Item';
+
+ /**
+ * @var string The help screen base URL for the menu item.
+ * @since 1.6
+ */
+ protected $helpURL;
+
+ /**
+ * @var boolean True to use local lookup for the help screen.
+ * @since 1.6
+ */
+ protected $helpLocal = false;
+
+ /**
+ * Batch copy/move command. If set to false,
+ * the batch copy/move command is not supported
+ *
+ * @var string
+ */
+ protected $batch_copymove = 'menu_id';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage'
+ );
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ $menuTypeId = 0;
+
+ if (!empty($record->menutype)) {
+ $menuTypeId = $this->getMenuTypeId($record->menutype);
+ }
+
+ return Factory::getUser()->authorise('core.delete', 'com_menus.menu.' . (int) $menuTypeId);
+ }
+
+ /**
+ * Method to test whether the state of a record can be edited.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission for the component.
+ *
+ * @since 3.6
+ */
+ protected function canEditState($record)
+ {
+ $menuTypeId = !empty($record->menutype) ? $this->getMenuTypeId($record->menutype) : 0;
+ $assetKey = $menuTypeId ? 'com_menus.menu.' . (int) $menuTypeId : 'com_menus';
+
+ return Factory::getUser()->authorise('core.edit.state', $assetKey);
+ }
+
+ /**
+ * Batch copy menu items to a new menu or parent.
+ *
+ * @param integer $value The new menu or sub-item.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return mixed An array of new IDs on success, boolean false on failure.
+ *
+ * @since 1.6
+ */
+ protected function batchCopy($value, $pks, $contexts)
+ {
+ // $value comes as {menutype}.{parent_id}
+ $parts = explode('.', $value);
+ $menuType = $parts[0];
+ $parentId = ArrayHelper::getValue($parts, 1, 0, 'int');
+
+ $table = $this->getTable();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $newIds = array();
+
+ // Check that the parent exists
+ if ($parentId) {
+ if (!$table->load($parentId)) {
+ if ($error = $table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Non-fatal error
+ $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
+ $parentId = 0;
+ }
+ }
+ }
+
+ // If the parent is 0, set it to the ID of the root item in the tree
+ if (empty($parentId)) {
+ if (!$parentId = $table->getRootId()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Check that user has create permission for menus
+ $user = Factory::getUser();
+
+ $menuTypeId = (int) $this->getMenuTypeId($menuType);
+
+ if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) {
+ $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
+
+ return false;
+ }
+
+ // We need to log the parent ID
+ $parents = array();
+
+ // Calculate the emergency stop count as a precaution against a runaway loop bug
+ $query->select('COUNT(' . $db->quoteName('id') . ')')
+ ->from($db->quoteName('#__menu'));
+ $db->setQuery($query);
+
+ try {
+ $count = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Parent exists so let's proceed
+ while (!empty($pks) && $count > 0) {
+ // Pop the first id off the stack
+ $pk = array_shift($pks);
+
+ $table->reset();
+
+ // Check that the row actually exists
+ if (!$table->load($pk)) {
+ if ($error = $table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Not fatal error
+ $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ continue;
+ }
+ }
+
+ // Copy is a bit tricky, because we also need to copy the children
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->where(
+ [
+ $db->quoteName('lft') . ' > :lft',
+ $db->quoteName('rgt') . ' < :rgt',
+ ]
+ )
+ ->bind(':lft', $table->lft, ParameterType::INTEGER)
+ ->bind(':rgt', $table->rgt, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $childIds = $db->loadColumn();
+
+ // Add child IDs to the array only if they aren't already there.
+ foreach ($childIds as $childId) {
+ if (!in_array($childId, $pks)) {
+ $pks[] = $childId;
+ }
+ }
+
+ // Make a copy of the old ID and Parent ID
+ $oldId = $table->id;
+ $oldParentId = $table->parent_id;
+
+ // Reset the id because we are making a copy.
+ $table->id = 0;
+
+ // If we a copying children, the Old ID will turn up in the parents list
+ // otherwise it's a new top level item
+ $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId;
+ $table->menutype = $menuType;
+
+ // Set the new location in the tree for the node.
+ $table->setLocation($table->parent_id, 'last-child');
+
+ // @todo: Deal with ordering?
+ // $table->ordering = 1;
+ $table->level = null;
+ $table->lft = null;
+ $table->rgt = null;
+ $table->home = 0;
+
+ // Alter the title & alias
+ list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
+ $table->title = $title;
+ $table->alias = $alias;
+
+ // Check the row.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Store the row.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Get the new item ID
+ $newId = $table->get('id');
+
+ // Add the new ID to the array
+ $newIds[$pk] = $newId;
+
+ // Now we log the old 'parent' to the new 'parent'
+ $parents[$oldId] = $table->id;
+ $count--;
+ }
+
+ // Rebuild the hierarchy.
+ if (!$table->rebuild()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Rebuild the tree path.
+ if (!$table->rebuildPath($table->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return $newIds;
+ }
+
+ /**
+ * Batch move menu items to a new menu or parent.
+ *
+ * @param integer $value The new menu or sub-item.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ protected function batchMove($value, $pks, $contexts)
+ {
+ // $value comes as {menutype}.{parent_id}
+ $parts = explode('.', $value);
+ $menuType = $parts[0];
+ $parentId = ArrayHelper::getValue($parts, 1, 0, 'int');
+
+ $table = $this->getTable();
+ $db = $this->getDatabase();
+
+ // Check that the parent exists.
+ if ($parentId) {
+ if (!$table->load($parentId)) {
+ if ($error = $table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Non-fatal error
+ $this->setError(Text::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
+ $parentId = 0;
+ }
+ }
+ }
+
+ // Check that user has create and edit permission for menus
+ $user = Factory::getUser();
+
+ $menuTypeId = (int) $this->getMenuTypeId($menuType);
+
+ if (!$user->authorise('core.create', 'com_menus.menu.' . $menuTypeId)) {
+ $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_CREATE'));
+
+ return false;
+ }
+
+ if (!$user->authorise('core.edit', 'com_menus.menu.' . $menuTypeId)) {
+ $this->setError(Text::_('COM_MENUS_BATCH_MENU_ITEM_CANNOT_EDIT'));
+
+ return false;
+ }
+
+ // We are going to store all the children and just moved the menutype
+ $children = array();
+
+ // Parent exists so let's proceed
+ foreach ($pks as $pk) {
+ // Check that the row actually exists
+ if (!$table->load($pk)) {
+ if ($error = $table->getError()) {
+ // Fatal error
+ $this->setError($error);
+
+ return false;
+ } else {
+ // Not fatal error
+ $this->setError(Text::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
+ continue;
+ }
+ }
+
+ // Set the new location in the tree for the node.
+ $table->setLocation($parentId, 'last-child');
+
+ // Set the new Parent Id
+ $table->parent_id = $parentId;
+
+ // Check if we are moving to a different menu
+ if ($menuType != $table->menutype) {
+ // Add the child node ids to the children array.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('lft') . ' BETWEEN :lft AND :rgt')
+ ->bind(':lft', $table->lft, ParameterType::INTEGER)
+ ->bind(':rgt', $table->rgt, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $children = array_merge($children, (array) $db->loadColumn());
+ }
+
+ // Check the row.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Store the row.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Rebuild the tree path.
+ if (!$table->rebuildPath()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Process the child rows
+ if (!empty($children)) {
+ // Remove any duplicates and sanitize ids.
+ $children = array_unique($children);
+ $children = ArrayHelper::toInteger($children);
+
+ // Update the menutype field in all nodes where necessary.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('menutype') . ' = :menuType')
+ ->whereIn($db->quoteName('id'), $children)
+ ->bind(':menuType', $menuType);
+
+ try {
+ $db->setQuery($query);
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to check if you can save a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function canSave($data = array(), $key = 'id')
+ {
+ return Factory::getUser()->authorise('core.edit', $this->option);
+ }
+
+ /**
+ * Method to get the row form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return mixed A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // The folder and element vars are passed when saving the form.
+ if (empty($data)) {
+ $item = $this->getItem();
+
+ // The type should already be set.
+ $this->setState('item.link', $item->link);
+ } else {
+ $this->setState('item.link', ArrayHelper::getValue($data, 'link'));
+ $this->setState('item.type', ArrayHelper::getValue($data, 'type'));
+ }
+
+ $clientId = $this->getState('item.client_id');
+
+ // Get the form.
+ if ($clientId == 1) {
+ $form = $this->loadForm('com_menus.item.admin', 'itemadmin', array('control' => 'jform', 'load_data' => $loadData), true);
+ } else {
+ $form = $this->loadForm('com_menus.item', 'item', array('control' => 'jform', 'load_data' => $loadData), true);
+ }
+
+ if (empty($form)) {
+ return false;
+ }
+
+ if ($loadData) {
+ $data = $this->loadFormData();
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('menuordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is an article you can edit.
+ $form->setFieldAttribute('menuordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ // Filter available menus
+ $action = $this->getState('item.id') > 0 ? 'edit' : 'create';
+
+ $form->setFieldAttribute('menutype', 'accesstype', $action);
+ $form->setFieldAttribute('type', 'clientid', $clientId);
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data, providing it has an ID and it is the same.
+ $itemData = (array) $this->getItem();
+
+ // When a new item is requested, unset the access as it will be set later from the filter
+ if (empty($itemData['id'])) {
+ unset($itemData['access']);
+ }
+
+ $sessionData = (array) Factory::getApplication()->getUserState('com_menus.edit.item.data', array());
+
+ // Only merge if there is a session and itemId or itemid is null.
+ if (
+ isset($sessionData['id']) && isset($itemData['id']) && $sessionData['id'] === $itemData['id']
+ || is_null($itemData['id'])
+ ) {
+ $data = array_merge($itemData, $sessionData);
+ } else {
+ $data = $itemData;
+ }
+
+ // For a new menu item, pre-select some filters (Status, Language, Access) in edit form if those have been selected in Menu Manager
+ if (empty($data['id'])) {
+ // Get selected fields
+ $filters = Factory::getApplication()->getUserState('com_menus.items.filter');
+ $data['parent_id'] = $data['parent_id'] ?? ($filters['parent_id'] ?? null);
+ $data['published'] = $data['published'] ?? ($filters['published'] ?? null);
+ $data['language'] = $data['language'] ?? ($filters['language'] ?? null);
+ $data['access'] = $data['access'] ?? ($filters['access'] ?? Factory::getApplication()->get('access'));
+ }
+
+ if (isset($data['menutype']) && !$this->getState('item.menutypeid')) {
+ $menuTypeId = (int) $this->getMenuTypeId($data['menutype']);
+
+ $this->setState('item.menutypeid', $menuTypeId);
+ }
+
+ $data = (object) $data;
+
+ $this->preprocessData('com_menus.item', $data);
+
+ return $data;
+ }
+
+ /**
+ * Get the necessary data to load an item help screen.
+ *
+ * @return object An object with key, url, and local properties for loading the item help screen.
+ *
+ * @since 1.6
+ */
+ public function getHelp()
+ {
+ return (object) array('key' => $this->helpKey, 'url' => $this->helpURL, 'local' => $this->helpLocal);
+ }
+
+ /**
+ * Method to get a menu item.
+ *
+ * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
+ *
+ * @return mixed Menu item data object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ $pk = (!empty($pk)) ? $pk : (int) $this->getState('item.id');
+
+ // Get a level row instance.
+ $table = $this->getTable();
+
+ // Attempt to load the row.
+ $table->load($pk);
+
+ // Check for a table object error.
+ if ($error = $table->getError()) {
+ $this->setError($error);
+
+ return false;
+ }
+
+ // Prime required properties.
+
+ if ($type = $this->getState('item.type')) {
+ $table->type = $type;
+ }
+
+ if (empty($table->id)) {
+ $table->parent_id = $this->getState('item.parent_id');
+ $table->menutype = $this->getState('item.menutype');
+ $table->client_id = $this->getState('item.client_id');
+ $table->params = '{}';
+ }
+
+ // If the link has been set in the state, possibly changing link type.
+ if ($link = $this->getState('item.link')) {
+ // Check if we are changing away from the actual link type.
+ if (MenusHelper::getLinkKey($table->link) !== MenusHelper::getLinkKey($link) && (int) $table->id === (int) $this->getState('item.id')) {
+ $table->link = $link;
+ }
+ }
+
+ switch ($table->type) {
+ case 'alias':
+ case 'url':
+ $table->component_id = 0;
+ $args = array();
+
+ if ($table->link) {
+ $q = parse_url($table->link, PHP_URL_QUERY);
+
+ if ($q) {
+ parse_str($q, $args);
+ }
+ }
+
+ break;
+
+ case 'separator':
+ case 'heading':
+ case 'container':
+ $table->link = '';
+ $table->component_id = 0;
+ break;
+
+ case 'component':
+ default:
+ // Enforce a valid type.
+ $table->type = 'component';
+
+ // Ensure the integrity of the component_id field is maintained, particularly when changing the menu item type.
+ $args = [];
+
+ if ($table->link) {
+ $q = parse_url($table->link, PHP_URL_QUERY);
+
+ if ($q) {
+ parse_str($q, $args);
+ }
+ }
+
+ if (isset($args['option'])) {
+ // Load the language file for the component.
+ $lang = Factory::getLanguage();
+ $lang->load($args['option'], JPATH_ADMINISTRATOR)
+ || $lang->load($args['option'], JPATH_ADMINISTRATOR . '/components/' . $args['option']);
+
+ // Determine the component id.
+ $component = ComponentHelper::getComponent($args['option']);
+
+ if (isset($component->id)) {
+ $table->component_id = $component->id;
+ }
+ }
+ break;
+ }
+
+ // We have a valid type, inject it into the state for forms to use.
+ $this->setState('item.type', $table->type);
+
+ // Convert to the \Joomla\CMS\Object\CMSObject before adding the params.
+ $properties = $table->getProperties(1);
+ $result = ArrayHelper::toObject($properties);
+
+ // Convert the params field to an array.
+ $registry = new Registry($table->params);
+ $result->params = $registry->toArray();
+
+ // Merge the request arguments in to the params for a component.
+ if ($table->type == 'component') {
+ // Note that all request arguments become reserved parameter names.
+ $result->request = $args;
+ $result->params = array_merge($result->params, $args);
+
+ // Special case for the Login menu item.
+ // Display the login or logout redirect URL fields if not empty
+ if ($table->link == 'index.php?option=com_users&view=login') {
+ if (!empty($result->params['login_redirect_url'])) {
+ $result->params['loginredirectchoice'] = '0';
+ }
+
+ if (!empty($result->params['logout_redirect_url'])) {
+ $result->params['logoutredirectchoice'] = '0';
+ }
+ }
+ }
+
+ if ($table->type == 'alias') {
+ // Note that all request arguments become reserved parameter names.
+ $result->params = array_merge($result->params, $args);
+ }
+
+ if ($table->type == 'url') {
+ // Note that all request arguments become reserved parameter names.
+ $result->params = array_merge($result->params, $args);
+ }
+
+ // Load associated menu items, only supported for frontend for now
+ if ($this->getState('item.client_id') == 0 && Associations::isEnabled()) {
+ if ($pk != null) {
+ $result->associations = MenusHelper::getAssociations($pk);
+ } else {
+ $result->associations = array();
+ }
+ }
+
+ $result->menuordering = $pk;
+
+ return $result;
+ }
+
+ /**
+ * Get the list of modules not in trash.
+ *
+ * @return mixed An array of module records (id, title, position), or false on error.
+ *
+ * @since 1.6
+ */
+ public function getModules()
+ {
+ $clientId = (int) $this->getState('item.client_id');
+ $id = (int) $this->getState('item.id');
+
+ // Currently any setting that affects target page for a backend menu is not supported, hence load no modules.
+ if ($clientId == 1) {
+ return false;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ /**
+ * Join on the module-to-menu mapping table.
+ * We are only interested if the module is displayed on ALL or THIS menu item (or the inverse ID number).
+ * sqlsrv changes for modulelink to menu manager
+ */
+ $query->select(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.position'),
+ $db->quoteName('a.published'),
+ $db->quoteName('map.menuid'),
+ ]
+ )
+ ->from($db->quoteName('#__modules', 'a'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__modules_menu', 'map'),
+ $db->quoteName('map.moduleid') . ' = ' . $db->quoteName('a.id')
+ . ' AND ' . $db->quoteName('map.menuid') . ' IN (' . implode(',', $query->bindArray([0, $id, -$id])) . ')'
+ );
+
+ $subQuery = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName('#__modules_menu'))
+ ->where(
+ [
+ $db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'),
+ $db->quoteName('menuid') . ' < 0',
+ ]
+ );
+
+ $query->select('(' . $subQuery . ') AS ' . $db->quoteName('except'));
+
+ // Join on the asset groups table.
+ $query->select($db->quoteName('ag.title', 'access_title'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
+ ->where(
+ [
+ $db->quoteName('a.published') . ' >= 0',
+ $db->quoteName('a.client_id') . ' = :clientId',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER)
+ ->order(
+ [
+ $db->quoteName('a.position'),
+ $db->quoteName('a.ordering'),
+ ]
+ );
+
+ $db->setQuery($query);
+
+ try {
+ $result = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the list of all view levels
+ *
+ * @return \stdClass[]|boolean An array of all view levels (id, title).
+ *
+ * @since 3.4
+ */
+ public function getViewLevels()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Get all the available view levels
+ $query->select($db->quoteName('id'))
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__viewlevels'))
+ ->order($db->quoteName('id'));
+
+ $db->setQuery($query);
+
+ try {
+ $result = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a Table object, always creating it
+ *
+ * @param string $type The table type to instantiate.
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\Cms\Table\Table|\Joomla\Cms\Table\Nested A database object.
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'Menu', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
+
+ /**
+ * A protected method to get the where clause for the reorder.
+ * This ensures that the row will be moved relative to a row with the same menutype.
+ *
+ * @param \Joomla\CMS\Table\Menu $table
+ *
+ * @return array An array of conditions to add to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('menutype') . ' = ' . $db->quote($table->menutype),
+ ];
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState('item.id', $pk);
+
+ if (!$app->isClient('api')) {
+ $parentId = $app->getUserState('com_menus.edit.item.parent_id');
+ $menuType = $app->getUserStateFromRequest('com_menus.items.menutype', 'menutype', '', 'string');
+ } else {
+ $parentId = null;
+ $menuType = $app->input->get('com_menus.items.menutype');
+ }
+
+ if (!$parentId) {
+ $parentId = $app->input->getInt('parent_id');
+ }
+
+ $this->setState('item.parent_id', $parentId);
+
+ // If we have a menutype we take client_id from there, unless forced otherwise
+ if ($menuType) {
+ $menuTypeObj = $this->getMenuType($menuType);
+
+ // An invalid menutype will be handled as clientId = 0 and menuType = ''
+ $menuType = (string) $menuTypeObj->menutype;
+ $menuTypeId = (int) $menuTypeObj->client_id;
+ $clientId = (int) $menuTypeObj->client_id;
+ } else {
+ $menuTypeId = 0;
+ $clientId = $app->isClient('api') ? $app->input->get('client_id') :
+ $app->getUserState('com_menus.items.client_id', 0);
+ }
+
+ // Forced client id will override/clear menuType if conflicted
+ $forcedClientId = $app->input->get('client_id', null, 'string');
+
+ if (!$app->isClient('api')) {
+ // Set the menu type and client id on the list view state, so we return to this menu after saving.
+ $app->setUserState('com_menus.items.menutype', $menuType);
+ $app->setUserState('com_menus.items.client_id', $clientId);
+ }
+
+ // Current item if not new, we don't allow changing client id at all
+ if ($pk) {
+ $table = $this->getTable();
+ $table->load($pk);
+ $forcedClientId = $table->get('client_id', $forcedClientId);
+ }
+
+ if (isset($forcedClientId) && $forcedClientId != $clientId) {
+ $clientId = $forcedClientId;
+ $menuType = '';
+ $menuTypeId = 0;
+ }
+
+ $this->setState('item.menutype', $menuType);
+ $this->setState('item.client_id', $clientId);
+ $this->setState('item.menutypeid', $menuTypeId);
+
+ if (!($type = $app->getUserState('com_menus.edit.item.type'))) {
+ $type = $app->input->get('type');
+
+ /**
+ * Note: a new menu item will have no field type.
+ * The field is required so the user has to change it.
+ */
+ }
+
+ $this->setState('item.type', $type);
+
+ $link = $app->isClient('api') ? $app->input->get('link') :
+ $app->getUserState('com_menus.edit.item.link');
+
+ if ($link) {
+ $this->setState('item.link', $link);
+ }
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_menus');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Loads the menutype object by a given menutype string
+ *
+ * @param string $menutype The given menutype
+ *
+ * @return \stdClass
+ *
+ * @since 3.7.0
+ */
+ protected function getMenuType($menutype)
+ {
+ $table = $this->getTable('MenuType');
+
+ $table->load(array('menutype' => $menutype));
+
+ return (object) $table->getProperties();
+ }
+
+ /**
+ * Loads the menutype ID by a given menutype string
+ *
+ * @param string $menutype The given menutype
+ *
+ * @return integer
+ *
+ * @since 3.6
+ */
+ protected function getMenuTypeId($menutype)
+ {
+ $menu = $this->getMenuType($menutype);
+
+ return (int) $menu->id;
+ }
+
+ /**
+ * Method to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $link = $this->getState('item.link');
+ $type = $this->getState('item.type');
+ $clientId = $this->getState('item.client_id');
+ $formFile = false;
+
+ // Load the specific type file
+ $typeFile = $clientId == 1 ? 'itemadmin_' . $type : 'item_' . $type;
+ $clientInfo = ApplicationHelper::getClientInfo($clientId);
+
+ // Initialise form with component view params if available.
+ if ($type == 'component') {
+ $link = $link ? htmlspecialchars_decode($link) : '';
+
+ // Parse the link arguments.
+ $args = [];
+
+ if ($link) {
+ parse_str(parse_url(htmlspecialchars_decode($link), PHP_URL_QUERY), $args);
+ }
+
+ // Confirm that the option is defined.
+ $option = '';
+ $base = '';
+
+ if (isset($args['option'])) {
+ // The option determines the base path to work with.
+ $option = $args['option'];
+ $base = $clientInfo->path . '/components/' . $option;
+ }
+
+ if (isset($args['view'])) {
+ $view = $args['view'];
+
+ // Determine the layout to search for.
+ if (isset($args['layout'])) {
+ $layout = $args['layout'];
+ } else {
+ $layout = 'default';
+ }
+
+ // Check for the layout XML file. Use standard xml file if it exists.
+ $tplFolders = array(
+ $base . '/tmpl/' . $view,
+ $base . '/views/' . $view . '/tmpl',
+ $base . '/view/' . $view . '/tmpl',
+ );
+ $path = Path::find($tplFolders, $layout . '.xml');
+
+ if (is_file($path)) {
+ $formFile = $path;
+ }
+
+ // If custom layout, get the xml file from the template folder
+ // template folder is first part of file name -- template:folder
+ if (!$formFile && (strpos($layout, ':') > 0)) {
+ list($altTmpl, $altLayout) = explode(':', $layout);
+
+ $templatePath = Path::clean($clientInfo->path . '/templates/' . $altTmpl . '/html/' . $option . '/' . $view . '/' . $altLayout . '.xml');
+
+ if (is_file($templatePath)) {
+ $formFile = $templatePath;
+ }
+ }
+ }
+
+ // Now check for a view manifest file
+ if (!$formFile) {
+ if (isset($view)) {
+ $metadataFolders = array(
+ $base . '/view/' . $view,
+ $base . '/views/' . $view
+ );
+ $metaPath = Path::find($metadataFolders, 'metadata.xml');
+
+ if (is_file($path = Path::clean($metaPath))) {
+ $formFile = $path;
+ }
+ } elseif ($base) {
+ // Now check for a component manifest file
+ $path = Path::clean($base . '/metadata.xml');
+
+ if (is_file($path)) {
+ $formFile = $path;
+ }
+ }
+ }
+ }
+
+ if ($formFile) {
+ // If an XML file was found in the component, load it first.
+ // We need to qualify the full path to avoid collisions with component file names.
+
+ if ($form->loadFile($formFile, true, '/metadata') == false) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Get the help data from the XML file if present.
+ $help = $xml->xpath('/metadata/layout/help');
+ } else {
+ // We don't have a component. Load the form XML to get the help path
+ $xmlFile = Path::find(JPATH_ADMINISTRATOR . '/components/com_menus/forms', $typeFile . '.xml');
+
+ if ($xmlFile) {
+ if (!$xml = simplexml_load_file($xmlFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Get the help data from the XML file if present.
+ $help = $xml->xpath('/form/help');
+ }
+ }
+
+ if (!empty($help)) {
+ $helpKey = trim((string) $help[0]['key']);
+ $helpURL = trim((string) $help[0]['url']);
+ $helpLoc = trim((string) $help[0]['local']);
+
+ $this->helpKey = $helpKey ?: $this->helpKey;
+ $this->helpURL = $helpURL ?: $this->helpURL;
+ $this->helpLocal = (($helpLoc == 'true') || ($helpLoc == '1') || ($helpLoc == 'local'));
+ }
+
+ if (!$form->loadFile($typeFile, true, false)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Association menu items, we currently do not support this for admin menu… may be later
+ if ($clientId == 0 && Associations::isEnabled()) {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (count($languages) > 1) {
+ $addform = new \SimpleXMLElement('');
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+ $fieldset->addAttribute('addfieldprefix', 'Joomla\Component\Menus\Administrator\Field');
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('type', 'modal_menu');
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+ $option = $field->addChild('option', 'COM_MENUS_ITEM_FIELD_ASSOCIATION_NO_VALUE');
+ $option->addAttribute('value', '');
+ }
+
+ $form->load($addform, false);
+ }
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method rebuild the entire nested set tree.
+ *
+ * @return boolean Boolean true on success, boolean false
+ *
+ * @since 1.6
+ */
+ public function rebuild()
+ {
+ // Initialise variables.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $table = $this->getTable();
+
+ try {
+ $rebuildResult = $table->rebuild();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (!$rebuildResult) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $query->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('params'),
+ ]
+ )
+ ->from($db->quoteName('#__menu'))
+ ->where(
+ [
+ $db->quoteName('params') . ' NOT LIKE ' . $db->quote('{%'),
+ $db->quoteName('params') . ' <> ' . $db->quote(''),
+ ]
+ );
+ $db->setQuery($query);
+
+ try {
+ $items = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('params') . ' = :params')
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':params', $params)
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ foreach ($items as &$item) {
+ // Update query parameters.
+ $id = $item->id;
+ $params = new Registry($item->params);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $pk = isset($data['id']) ? $data['id'] : (int) $this->getState('item.id');
+ $isNew = true;
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $table = $this->getTable();
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the on save events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Load the row if saving an existing item.
+ if ($pk > 0) {
+ $table->load($pk);
+ $isNew = false;
+ }
+
+ if (!$isNew) {
+ if ($table->parent_id == $data['parent_id']) {
+ // If first is chosen make the item the first child of the selected parent.
+ if ($data['menuordering'] == -1) {
+ $table->setLocation($data['parent_id'], 'first-child');
+ } elseif ($data['menuordering'] == -2) {
+ // If last is chosen make it the last child of the selected parent.
+ $table->setLocation($data['parent_id'], 'last-child');
+ } elseif ($data['menuordering'] && $table->id != $data['menuordering'] || empty($data['id'])) {
+ // Don't try to put an item after itself. All other ones put after the selected item.
+ // $data['id'] is empty means it's a save as copy
+ $table->setLocation($data['menuordering'], 'after');
+ } elseif ($data['menuordering'] && $table->id == $data['menuordering']) {
+ // \Just leave it where it is if no change is made.
+ unset($data['menuordering']);
+ }
+ } else {
+ // Set the new parent id if parent id not matched and put in last position
+ $table->setLocation($data['parent_id'], 'last-child');
+ }
+
+ // Check if we are moving to a different menu
+ if ($data['menutype'] != $table->menutype) {
+ // Add the child node ids to the children array.
+ $query->clear()
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt);
+ $db->setQuery($query);
+ $children = (array) $db->loadColumn();
+ }
+ } else {
+ // We have a new item, so it is not a change.
+ $menuType = $this->getMenuType($data['menutype']);
+
+ $data['client_id'] = $menuType->client_id;
+
+ $table->setLocation($data['parent_id'], 'last-child');
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Alter the title & alias for save2copy when required. Also, unset the home record.
+ if (Factory::getApplication()->input->get('task') === 'save2copy' && $data['id'] === 0) {
+ $origTable = $this->getTable();
+ $origTable->load($this->getState('item.id'));
+
+ if ($table->title === $origTable->title) {
+ list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
+ $table->title = $title;
+ $table->alias = $alias;
+ }
+
+ if ($table->alias === $origTable->alias) {
+ $table->alias = '';
+ }
+
+ $table->published = 0;
+ $table->home = 0;
+ }
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew, $data));
+
+ // Store the data.
+ if (in_array(false, $result, true) || !$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew));
+
+ // Rebuild the tree path.
+ if (!$table->rebuildPath($table->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Process the child rows
+ if (!empty($children)) {
+ // Remove any duplicates and sanitize ids.
+ $children = array_unique($children);
+ $children = ArrayHelper::toInteger($children);
+
+ // Update the menutype field in all nodes where necessary.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('menutype') . ' = :menutype')
+ ->whereIn($db->quoteName('id'), $children)
+ ->bind(':menutype', $data['menutype']);
+
+ try {
+ $db->setQuery($query);
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ $this->setState('item.id', $table->id);
+ $this->setState('item.menutype', $table->menutype);
+
+ // Load associated menu items, for now not supported for admin menu… may be later
+ if ($table->get('client_id') == 0 && Associations::isEnabled()) {
+ // Adding self to the association
+ $associations = isset($data['associations']) ? $data['associations'] : array();
+
+ // Unset any invalid associations
+ $associations = ArrayHelper::toInteger($associations);
+
+ foreach ($associations as $tag => $id) {
+ if (!$id) {
+ unset($associations[$tag]);
+ }
+ }
+
+ // Detecting all item menus
+ $all_language = $table->language == '*';
+
+ if ($all_language && !empty($associations)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALL_LANGUAGE_ASSOCIATED'), 'notice');
+ }
+
+ // Get associationskey for edited item
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('key'))
+ ->from($db->quoteName('#__associations'))
+ ->where(
+ [
+ $db->quoteName('context') . ' = :context',
+ $db->quoteName('id') . ' = :id',
+ ]
+ )
+ ->bind(':context', $this->associationsContext)
+ ->bind(':id', $table->id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $oldKey = $db->loadResult();
+
+ if ($associations || $oldKey !== null) {
+ // Deleting old associations for the associated items
+ $where = [];
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__associations'))
+ ->where($db->quoteName('context') . ' = :context')
+ ->bind(':context', $this->associationsContext);
+
+ if ($associations) {
+ $where[] = $db->quoteName('id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')';
+ }
+
+ if ($oldKey !== null) {
+ $where[] = $db->quoteName('key') . ' = :oldKey';
+ $query->bind(':oldKey', $oldKey);
+ }
+
+ $query->extendWhere('AND', $where, 'OR');
+
+ try {
+ $db->setQuery($query);
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ // Adding self to the association
+ if (!$all_language) {
+ $associations[$table->language] = (int) $table->id;
+ }
+
+ if (count($associations) > 1) {
+ // Adding new association for these items
+ $key = md5(json_encode($associations));
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__associations'))
+ ->columns(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('context'),
+ $db->quoteName('key'),
+ ]
+ );
+
+ foreach ($associations as $id) {
+ $query->values(
+ implode(
+ ',',
+ $query->bindArray(
+ [$id, $this->associationsContext, $key],
+ [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
+ )
+ )
+ );
+ }
+
+ try {
+ $db->setQuery($query);
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ if (isset($data['link'])) {
+ $base = Uri::base();
+ $juri = Uri::getInstance($base . $data['link']);
+ $option = $juri->getVar('option');
+
+ // Clean the cache
+ parent::cleanCache($option);
+ }
+
+ if (Factory::getApplication()->input->get('task') === 'editAssociations') {
+ return $this->redirectToAssociations($data);
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to save the reordered nested set tree.
+ * First we save the new order values in the lft values of the changed ids.
+ * Then we invoke the table rebuild to implement the new ordering.
+ *
+ * @param array $idArray Rows identifiers to be reordered
+ * @param array $lftArray lft values of rows to be reordered
+ *
+ * @return boolean false on failure or error, true otherwise.
+ *
+ * @since 1.6
+ */
+ public function saveorder($idArray = null, $lftArray = null)
+ {
+ // Get an instance of the table object.
+ $table = $this->getTable();
+
+ if (!$table->saveorder($idArray, $lftArray)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the home state of one or more items.
+ *
+ * @param array $pks A list of the primary keys to change.
+ * @param integer $value The value of the home state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function setHome(&$pks, $value = 1)
+ {
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ $languages = array();
+ $onehome = false;
+
+ // Remember that we can set a home page for different languages,
+ // so we need to loop through the primary key array.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ if (!array_key_exists($table->language, $languages)) {
+ $languages[$table->language] = true;
+
+ if ($table->home == $value) {
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_ALREADY_HOME'), 'notice');
+ } elseif ($table->menutype == 'main') {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('COM_MENUS_ERROR_MENUTYPE_HOME'), 'error');
+ } else {
+ $table->home = $value;
+
+ if ($table->language == '*') {
+ $table->published = 1;
+ }
+
+ if (!$this->canSave($table)) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+ } elseif (!$table->check()) {
+ // Prune the items that failed pre-save checks.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage($table->getError(), 'error');
+ } elseif (!$table->store()) {
+ // Prune the items that could not be stored.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage($table->getError(), 'error');
+ }
+ }
+ } else {
+ unset($pks[$i]);
+
+ if (!$onehome) {
+ $onehome = true;
+ Factory::getApplication()->enqueueMessage(Text::sprintf('COM_MENUS_ERROR_ONE_HOME'), 'notice');
+ }
+ }
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array $pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ // Default menu item existence checks.
+ if ($value != 1) {
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk) && $table->home && $table->language == '*') {
+ // Prune items that you can't change.
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'), 'error');
+ unset($pks[$i]);
+ break;
+ }
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ // Ensure that previous checks doesn't empty the array
+ if (empty($pks)) {
+ return true;
+ }
+
+ return parent::publish($pks, $value);
+ }
+
+ /**
+ * Method to change the title & alias.
+ *
+ * @param integer $parentId The id of the parent.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 1.6
+ */
+ protected function generateNewTitle($parentId, $alias, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('alias' => $alias, 'parent_id' => $parentId))) {
+ if ($title == $table->title) {
+ $title = StringHelper::increment($title);
+ }
+
+ $alias = StringHelper::increment($alias, 'dash');
+ }
+
+ return array($title, $alias);
+ }
+
+ /**
+ * Custom clean the cache
+ *
+ * @param string $group Cache group name.
+ * @param integer $clientId @deprecated 5.0 No Longer Used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_menus');
+ parent::cleanCache('com_modules');
+ parent::cleanCache('mod_menu');
+ }
}
diff --git a/administrator/components/com_menus/src/Model/ItemsModel.php b/administrator/components/com_menus/src/Model/ItemsModel.php
index 313cdd999ee05..e8f90ac445f91 100644
--- a/administrator/components/com_menus/src/Model/ItemsModel.php
+++ b/administrator/components/com_menus/src/Model/ItemsModel.php
@@ -1,4 +1,5 @@
input->get('forcedLanguage', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search');
- $this->setState('filter.search', $search);
-
- $published = $this->getUserStateFromRequest($this->context . '.published', 'filter_published', '');
- $this->setState('filter.published', $published);
-
- $access = $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
- $this->setState('filter.access', $access);
-
- $parentId = $this->getUserStateFromRequest($this->context . '.filter.parent_id', 'filter_parent_id');
- $this->setState('filter.parent_id', $parentId);
-
- $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
- $this->setState('filter.level', $level);
-
- // Watch changes in client_id and menutype and keep sync whenever needed.
- $currentClientId = $app->getUserState($this->context . '.client_id', 0);
- $clientId = $app->input->getInt('client_id', $currentClientId);
-
- // Load mod_menu.ini file when client is administrator
- if ($clientId == 1)
- {
- Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
- }
-
- $currentMenuType = $app->getUserState($this->context . '.menutype', '');
- $menuType = $app->input->getString('menutype', $currentMenuType);
-
- // If client_id changed clear menutype and reset pagination
- if ($clientId != $currentClientId)
- {
- $menuType = '';
-
- $app->input->set('limitstart', 0);
- $app->input->set('menutype', '');
- }
-
- // If menutype changed reset pagination.
- if ($menuType != $currentMenuType)
- {
- $app->input->set('limitstart', 0);
- }
-
- if (!$menuType)
- {
- $app->setUserState($this->context . '.menutype', '');
- $this->setState('menutypetitle', '');
- $this->setState('menutypeid', '');
- }
- // Special menu types, if selected explicitly, will be allowed as a filter
- elseif ($menuType == 'main')
- {
- // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
- $app->input->set('client_id', 1);
-
- $app->setUserState($this->context . '.menutype', $menuType);
- $this->setState('menutypetitle', ucfirst($menuType));
- $this->setState('menutypeid', -1);
- }
- // Get the menutype object with appropriate checks.
- elseif ($cMenu = $this->getMenu($menuType, true))
- {
- // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
- $app->input->set('client_id', $cMenu->client_id);
-
- $app->setUserState($this->context . '.menutype', $menuType);
- $this->setState('menutypetitle', $cMenu->title);
- $this->setState('menutypeid', $cMenu->id);
- }
- // This menutype does not exist, leave client id unchanged but reset menutype and pagination
- else
- {
- $menuType = '';
-
- $app->input->set('limitstart', 0);
- $app->input->set('menutype', $menuType);
-
- $app->setUserState($this->context . '.menutype', $menuType);
- $this->setState('menutypetitle', '');
- $this->setState('menutypeid', '');
- }
-
- // Client id filter
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $this->setState('filter.client_id', $clientId);
-
- // Use a different filter file when client is administrator
- if ($clientId == 1)
- {
- $this->filterFormName = 'filter_itemsadmin';
- }
-
- $this->setState('filter.menutype', $menuType);
-
- $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
- $this->setState('filter.language', $language);
-
- // Component parameters.
- $params = ComponentHelper::getParams('com_menus');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language.
- if (!empty($forcedLanguage))
- {
- $this->setState('filter.language', $forcedLanguage);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.parent_id');
- $id .= ':' . $this->getState('filter.menutype');
- $id .= ':' . $this->getState('filter.client_id');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Builds an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery A query object.
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
- $clientId = (int) $this->getState('filter.client_id');
-
- // Select all fields from the table.
- $query->select(
- // We can't quote state values because they could contain expressions.
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.menutype'),
- $db->quoteName('a.title'),
- $db->quoteName('a.alias'),
- $db->quoteName('a.note'),
- $db->quoteName('a.path'),
- $db->quoteName('a.link'),
- $db->quoteName('a.type'),
- $db->quoteName('a.parent_id'),
- $db->quoteName('a.level'),
- $db->quoteName('a.component_id'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.browserNav'),
- $db->quoteName('a.access'),
- $db->quoteName('a.img'),
- $db->quoteName('a.template_style_id'),
- $db->quoteName('a.params'),
- $db->quoteName('a.lft'),
- $db->quoteName('a.rgt'),
- $db->quoteName('a.home'),
- $db->quoteName('a.language'),
- $db->quoteName('a.client_id'),
- $db->quoteName('a.publish_up'),
- $db->quoteName('a.publish_down'),
- ]
- )
- )
- ->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- $db->quoteName('l.sef', 'language_sef'),
- $db->quoteName('u.name', 'editor'),
- $db->quoteName('c.element', 'componentname'),
- $db->quoteName('ag.title', 'access_level'),
- $db->quoteName('mt.id', 'menutype_id'),
- $db->quoteName('mt.title', 'menutype_title'),
- $db->quoteName('e.enabled'),
- $db->quoteName('e.name'),
- 'CASE WHEN ' . $db->quoteName('a.type') . ' = ' . $db->quote('component')
- . ' THEN ' . $db->quoteName('a.published') . ' +2 * (' . $db->quoteName('e.enabled') . ' -1)'
- . ' ELSE ' . $db->quoteName('a.published') . ' END AS ' . $db->quoteName('published'),
- ]
- )
- ->from($db->quoteName('#__menu', 'a'));
-
- // Join over the language
- $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
-
- // Join over the users.
- $query->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('a.checked_out'));
-
- // Join over components
- $query->join('LEFT', $db->quoteName('#__extensions', 'c'), $db->quoteName('c.extension_id') . ' = ' . $db->quoteName('a.component_id'));
-
- // Join over the asset groups.
- $query->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'));
-
- // Join over the menu types.
- $query->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('a.menutype'));
-
- // Join over the extensions
- $query->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
-
- // Join over the associations.
- if (Associations::isEnabled())
- {
- $subQuery = $db->getQuery(true)
- ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
- ->from($db->quoteName('#__associations', 'asso1'))
- ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
- ->where(
- [
- $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
- $db->quoteName('asso1.context') . ' = ' . $db->quote('com_menus.item'),
- ]
- );
-
- $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
- }
-
- // Exclude the root category.
- $query->where(
- [
- $db->quoteName('a.id') . ' > 1',
- $db->quoteName('a.client_id') . ' = :clientId',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
-
- // Filter on the published state.
- $published = $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.published') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->where($db->quoteName('a.published') . ' IN (0, 1)');
- }
-
- // Filter by search in title, alias or id
- if ($search = trim($this->getState('filter.search', '')))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'link:') === 0)
- {
- if ($search = str_replace(' ', '%', trim(substr($search, 5))))
- {
- $query->where($db->quoteName('a.link') . ' LIKE :search')
- ->bind(':search', $search);
- }
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.title') . ' LIKE :search1',
- $db->quoteName('a.alias') . ' LIKE :search2',
- $db->quoteName('a.note') . ' LIKE :search3',
- ],
- 'OR'
- )
- ->bind([':search1', ':search2', ':search3'], $search);
- }
- }
-
- // Filter the items over the parent id if set.
- $parentId = (int) $this->getState('filter.parent_id');
- $level = (int) $this->getState('filter.level');
-
- if ($parentId)
- {
- // Create a subquery for the sub-items list
- $subQuery = $db->getQuery(true)
- ->select($db->quoteName('sub.id'))
- ->from($db->quoteName('#__menu', 'sub'))
- ->join(
- 'INNER',
- $db->quoteName('#__menu', 'this'),
- $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft')
- . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt')
- )
- ->where($db->quoteName('this.id') . ' = :parentId1');
-
- if ($level)
- {
- $subQuery->where($db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :level - 1');
- $query->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Add the subquery to the main query
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.parent_id') . ' = :parentId2',
- $db->quoteName('a.parent_id') . ' IN (' . (string) $subQuery . ')',
- ],
- 'OR'
- )
- ->bind([':parentId1', ':parentId2'], $parentId, ParameterType::INTEGER);
- }
-
- // Filter on the level.
- elseif ($level)
- {
- $query->where($db->quoteName('a.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter the items over the menu id if set.
- $menuType = $this->getState('filter.menutype');
-
- // A value "" means all
- if ($menuType == '')
- {
- // Load all menu types we have manage access
- $query2 = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id'),
- $db->quoteName('menutype'),
- ]
- )
- ->from($db->quoteName('#__menu_types'))
- ->where($db->quoteName('client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER)
- ->order($db->quoteName('title'));
-
- // Show protected items on explicit filter only
- $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main'));
-
- $menuTypes = $db->setQuery($query2)->loadObjectList();
-
- if ($menuTypes)
- {
- $types = array();
-
- foreach ($menuTypes as $type)
- {
- if ($user->authorise('core.manage', 'com_menus.menu.' . (int) $type->id))
- {
- $types[] = $type->menutype;
- }
- }
-
- if ($types)
- {
- $query->whereIn($db->quoteName('a.menutype'), $types);
- }
- else
- {
- $query->where(0);
- }
- }
- }
- // Default behavior => load all items from a specific menu
- elseif (strlen($menuType))
- {
- $query->where($db->quoteName('a.menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
- }
- // Empty menu type => error
- else
- {
- $query->where('1 != 1');
- }
-
- // Filter on the access level.
- if ($access = (int) $this->getState('filter.access'))
- {
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- if ($groups = $user->getAuthorisedViewLevels())
- {
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Method to allow derived classes to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 3.2
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $name = $form->getName();
-
- if ($name == 'com_menus.items.filter')
- {
- $clientId = $this->getState('filter.client_id');
- $form->setFieldAttribute('menutype', 'clientid', $clientId);
- }
- elseif (false !== strpos($name, 'com_menus.items.modal.'))
- {
- $form->removeField('client_id');
-
- $clientId = $this->getState('filter.client_id');
- $form->setFieldAttribute('menutype', 'clientid', $clientId);
- }
- }
-
- /**
- * Get the client id for a menu
- *
- * @param string $menuType The menutype identifier for the menu
- * @param boolean $check Flag whether to perform check against ACL as well as existence
- *
- * @return integer
- *
- * @since 3.7.0
- */
- protected function getMenu($menuType, $check = false)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('a') . '.*')
- ->from($db->quoteName('#__menu_types', 'a'))
- ->where($db->quoteName('menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
-
- $cMenu = $db->setQuery($query)->loadObject();
-
- if ($check)
- {
- // Check if menu type exists.
- if (!$cMenu)
- {
- Log::add(Text::_('COM_MENUS_ERROR_MENUTYPE_NOT_FOUND'), Log::ERROR, 'jerror');
-
- return false;
- }
- // Check if menu type is valid against ACL.
- elseif (!Factory::getUser()->authorise('core.manage', 'com_menus.menu.' . $cMenu->id))
- {
- Log::add(Text::_('JERROR_ALERTNOAUTHOR'), Log::ERROR, 'jerror');
-
- return false;
- }
- }
-
- return $cMenu;
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 3.0.1
- */
- public function getItems()
- {
- $store = $this->getStoreId();
-
- if (!isset($this->cache[$store]))
- {
- $items = parent::getItems();
- $lang = Factory::getLanguage();
- $client = $this->state->get('filter.client_id');
-
- if ($items)
- {
- foreach ($items as $item)
- {
- if ($extension = $item->componentname)
- {
- $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
- || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension);
- }
-
- // Translate component name
- if ($client === 1)
- {
- $item->title = Text::_($item->title);
- }
- }
- }
-
- $this->cache[$store] = $items;
- }
-
- return $this->cache[$store];
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'menutype', 'a.menutype', 'menutype_title',
+ 'title', 'a.title',
+ 'alias', 'a.alias',
+ 'published', 'a.published',
+ 'access', 'a.access', 'access_level',
+ 'language', 'a.language',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'lft', 'a.lft',
+ 'rgt', 'a.rgt',
+ 'level', 'a.level',
+ 'path', 'a.path',
+ 'client_id', 'a.client_id',
+ 'home', 'a.home',
+ 'parent_id', 'a.parent_id',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'a.ordering'
+ );
+
+ if (Associations::isEnabled()) {
+ $config['filter_fields'][] = 'association';
+ }
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search');
+ $this->setState('filter.search', $search);
+
+ $published = $this->getUserStateFromRequest($this->context . '.published', 'filter_published', '');
+ $this->setState('filter.published', $published);
+
+ $access = $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access');
+ $this->setState('filter.access', $access);
+
+ $parentId = $this->getUserStateFromRequest($this->context . '.filter.parent_id', 'filter_parent_id');
+ $this->setState('filter.parent_id', $parentId);
+
+ $level = $this->getUserStateFromRequest($this->context . '.filter.level', 'filter_level');
+ $this->setState('filter.level', $level);
+
+ // Watch changes in client_id and menutype and keep sync whenever needed.
+ $currentClientId = $app->getUserState($this->context . '.client_id', 0);
+ $clientId = $app->input->getInt('client_id', $currentClientId);
+
+ // Load mod_menu.ini file when client is administrator
+ if ($clientId == 1) {
+ Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
+ }
+
+ $currentMenuType = $app->getUserState($this->context . '.menutype', '');
+ $menuType = $app->input->getString('menutype', $currentMenuType);
+
+ // If client_id changed clear menutype and reset pagination
+ if ($clientId != $currentClientId) {
+ $menuType = '';
+
+ $app->input->set('limitstart', 0);
+ $app->input->set('menutype', '');
+ }
+
+ // If menutype changed reset pagination.
+ if ($menuType != $currentMenuType) {
+ $app->input->set('limitstart', 0);
+ }
+
+ if (!$menuType) {
+ $app->setUserState($this->context . '.menutype', '');
+ $this->setState('menutypetitle', '');
+ $this->setState('menutypeid', '');
+ } elseif ($menuType == 'main') {
+ // Special menu types, if selected explicitly, will be allowed as a filter
+ // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
+ $app->input->set('client_id', 1);
+
+ $app->setUserState($this->context . '.menutype', $menuType);
+ $this->setState('menutypetitle', ucfirst($menuType));
+ $this->setState('menutypeid', -1);
+ } elseif ($cMenu = $this->getMenu($menuType, true)) {
+ // Get the menutype object with appropriate checks.
+ // Adjust client_id to match the menutype. This is safe as client_id was not changed in this request.
+ $app->input->set('client_id', $cMenu->client_id);
+
+ $app->setUserState($this->context . '.menutype', $menuType);
+ $this->setState('menutypetitle', $cMenu->title);
+ $this->setState('menutypeid', $cMenu->id);
+ } else {
+ // This menutype does not exist, leave client id unchanged but reset menutype and pagination
+ $menuType = '';
+
+ $app->input->set('limitstart', 0);
+ $app->input->set('menutype', $menuType);
+
+ $app->setUserState($this->context . '.menutype', $menuType);
+ $this->setState('menutypetitle', '');
+ $this->setState('menutypeid', '');
+ }
+
+ // Client id filter
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $this->setState('filter.client_id', $clientId);
+
+ // Use a different filter file when client is administrator
+ if ($clientId == 1) {
+ $this->filterFormName = 'filter_itemsadmin';
+ }
+
+ $this->setState('filter.menutype', $menuType);
+
+ $language = $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '');
+ $this->setState('filter.language', $language);
+
+ // Component parameters.
+ $params = ComponentHelper::getParams('com_menus');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language.
+ if (!empty($forcedLanguage)) {
+ $this->setState('filter.language', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.parent_id');
+ $id .= ':' . $this->getState('filter.menutype');
+ $id .= ':' . $this->getState('filter.client_id');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Builds an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery A query object.
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+ $clientId = (int) $this->getState('filter.client_id');
+
+ // Select all fields from the table.
+ $query->select(
+ // We can't quote state values because they could contain expressions.
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.menutype'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.alias'),
+ $db->quoteName('a.note'),
+ $db->quoteName('a.path'),
+ $db->quoteName('a.link'),
+ $db->quoteName('a.type'),
+ $db->quoteName('a.parent_id'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.component_id'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.browserNav'),
+ $db->quoteName('a.access'),
+ $db->quoteName('a.img'),
+ $db->quoteName('a.template_style_id'),
+ $db->quoteName('a.params'),
+ $db->quoteName('a.lft'),
+ $db->quoteName('a.rgt'),
+ $db->quoteName('a.home'),
+ $db->quoteName('a.language'),
+ $db->quoteName('a.client_id'),
+ $db->quoteName('a.publish_up'),
+ $db->quoteName('a.publish_down'),
+ ]
+ )
+ )
+ ->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ $db->quoteName('l.sef', 'language_sef'),
+ $db->quoteName('u.name', 'editor'),
+ $db->quoteName('c.element', 'componentname'),
+ $db->quoteName('ag.title', 'access_level'),
+ $db->quoteName('mt.id', 'menutype_id'),
+ $db->quoteName('mt.title', 'menutype_title'),
+ $db->quoteName('e.enabled'),
+ $db->quoteName('e.name'),
+ 'CASE WHEN ' . $db->quoteName('a.type') . ' = ' . $db->quote('component')
+ . ' THEN ' . $db->quoteName('a.published') . ' +2 * (' . $db->quoteName('e.enabled') . ' -1)'
+ . ' ELSE ' . $db->quoteName('a.published') . ' END AS ' . $db->quoteName('published'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'a'));
+
+ // Join over the language
+ $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
+
+ // Join over the users.
+ $query->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('a.checked_out'));
+
+ // Join over components
+ $query->join('LEFT', $db->quoteName('#__extensions', 'c'), $db->quoteName('c.extension_id') . ' = ' . $db->quoteName('a.component_id'));
+
+ // Join over the asset groups.
+ $query->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'));
+
+ // Join over the menu types.
+ $query->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('a.menutype'));
+
+ // Join over the extensions
+ $query->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
+
+ // Join over the associations.
+ if (Associations::isEnabled()) {
+ $subQuery = $db->getQuery(true)
+ ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
+ ->from($db->quoteName('#__associations', 'asso1'))
+ ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
+ ->where(
+ [
+ $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
+ $db->quoteName('asso1.context') . ' = ' . $db->quote('com_menus.item'),
+ ]
+ );
+
+ $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+ }
+
+ // Exclude the root category.
+ $query->where(
+ [
+ $db->quoteName('a.id') . ' > 1',
+ $db->quoteName('a.client_id') . ' = :clientId',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+
+ // Filter on the published state.
+ $published = $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.published') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->where($db->quoteName('a.published') . ' IN (0, 1)');
+ }
+
+ // Filter by search in title, alias or id
+ if ($search = trim($this->getState('filter.search', ''))) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } elseif (stripos($search, 'link:') === 0) {
+ if ($search = str_replace(' ', '%', trim(substr($search, 5)))) {
+ $query->where($db->quoteName('a.link') . ' LIKE :search')
+ ->bind(':search', $search);
+ }
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.title') . ' LIKE :search1',
+ $db->quoteName('a.alias') . ' LIKE :search2',
+ $db->quoteName('a.note') . ' LIKE :search3',
+ ],
+ 'OR'
+ )
+ ->bind([':search1', ':search2', ':search3'], $search);
+ }
+ }
+
+ // Filter the items over the parent id if set.
+ $parentId = (int) $this->getState('filter.parent_id');
+ $level = (int) $this->getState('filter.level');
+
+ if ($parentId) {
+ // Create a subquery for the sub-items list
+ $subQuery = $db->getQuery(true)
+ ->select($db->quoteName('sub.id'))
+ ->from($db->quoteName('#__menu', 'sub'))
+ ->join(
+ 'INNER',
+ $db->quoteName('#__menu', 'this'),
+ $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft')
+ . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt')
+ )
+ ->where($db->quoteName('this.id') . ' = :parentId1');
+
+ if ($level) {
+ $subQuery->where($db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :level - 1');
+ $query->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Add the subquery to the main query
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.parent_id') . ' = :parentId2',
+ $db->quoteName('a.parent_id') . ' IN (' . (string) $subQuery . ')',
+ ],
+ 'OR'
+ )
+ ->bind([':parentId1', ':parentId2'], $parentId, ParameterType::INTEGER);
+ } elseif ($level) {
+ // Filter on the level.
+ $query->where($db->quoteName('a.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter the items over the menu id if set.
+ $menuType = $this->getState('filter.menutype');
+
+ // A value "" means all
+ if ($menuType == '') {
+ // Load all menu types we have manage access
+ $query2 = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('menutype'),
+ ]
+ )
+ ->from($db->quoteName('#__menu_types'))
+ ->where($db->quoteName('client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER)
+ ->order($db->quoteName('title'));
+
+ // Show protected items on explicit filter only
+ $query->where($db->quoteName('a.menutype') . ' != ' . $db->quote('main'));
+
+ $menuTypes = $db->setQuery($query2)->loadObjectList();
+
+ if ($menuTypes) {
+ $types = array();
+
+ foreach ($menuTypes as $type) {
+ if ($user->authorise('core.manage', 'com_menus.menu.' . (int) $type->id)) {
+ $types[] = $type->menutype;
+ }
+ }
+
+ if ($types) {
+ $query->whereIn($db->quoteName('a.menutype'), $types);
+ } else {
+ $query->where(0);
+ }
+ }
+ } elseif (strlen($menuType)) {
+ // Default behavior => load all items from a specific menu
+ $query->where($db->quoteName('a.menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+ } else {
+ // Empty menu type => error
+ $query->where('1 != 1');
+ }
+
+ // Filter on the access level.
+ if ($access = (int) $this->getState('filter.access')) {
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ if ($groups = $user->getAuthorisedViewLevels()) {
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 3.2
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $name = $form->getName();
+
+ if ($name == 'com_menus.items.filter') {
+ $clientId = $this->getState('filter.client_id');
+ $form->setFieldAttribute('menutype', 'clientid', $clientId);
+ } elseif (false !== strpos($name, 'com_menus.items.modal.')) {
+ $form->removeField('client_id');
+
+ $clientId = $this->getState('filter.client_id');
+ $form->setFieldAttribute('menutype', 'clientid', $clientId);
+ }
+ }
+
+ /**
+ * Get the client id for a menu
+ *
+ * @param string $menuType The menutype identifier for the menu
+ * @param boolean $check Flag whether to perform check against ACL as well as existence
+ *
+ * @return integer
+ *
+ * @since 3.7.0
+ */
+ protected function getMenu($menuType, $check = false)
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('a') . '.*')
+ ->from($db->quoteName('#__menu_types', 'a'))
+ ->where($db->quoteName('menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+
+ $cMenu = $db->setQuery($query)->loadObject();
+
+ if ($check) {
+ // Check if menu type exists.
+ if (!$cMenu) {
+ Log::add(Text::_('COM_MENUS_ERROR_MENUTYPE_NOT_FOUND'), Log::ERROR, 'jerror');
+
+ return false;
+ } elseif (!Factory::getUser()->authorise('core.manage', 'com_menus.menu.' . $cMenu->id)) {
+ // Check if menu type is valid against ACL.
+ Log::add(Text::_('JERROR_ALERTNOAUTHOR'), Log::ERROR, 'jerror');
+
+ return false;
+ }
+ }
+
+ return $cMenu;
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 3.0.1
+ */
+ public function getItems()
+ {
+ $store = $this->getStoreId();
+
+ if (!isset($this->cache[$store])) {
+ $items = parent::getItems();
+ $lang = Factory::getLanguage();
+ $client = $this->state->get('filter.client_id');
+
+ if ($items) {
+ foreach ($items as $item) {
+ if ($extension = $item->componentname) {
+ $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
+ || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension);
+ }
+
+ // Translate component name
+ if ($client === 1) {
+ $item->title = Text::_($item->title);
+ }
+ }
+ }
+
+ $this->cache[$store] = $items;
+ }
+
+ return $this->cache[$store];
+ }
}
diff --git a/administrator/components/com_menus/src/Model/MenuModel.php b/administrator/components/com_menus/src/Model/MenuModel.php
index 3a26565ace426..b6370f673347f 100644
--- a/administrator/components/com_menus/src/Model/MenuModel.php
+++ b/administrator/components/com_menus/src/Model/MenuModel.php
@@ -1,4 +1,5 @@
authorise('core.delete', 'com_menus.menu.' . (int) $record->id);
- }
-
- /**
- * Method to test whether the state of a record can be edited.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- return Factory::getUser()->authorise('core.edit.state', 'com_menus.menu.' . (int) $record->id);
- }
-
- /**
- * Returns a Table object, always creating it
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- *
- * @since 1.6
- */
- public function getTable($type = 'MenuType', $prefix = '\JTable', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
-
- /**
- * Auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $id = $app->input->getInt('id');
- $this->setState('menu.id', $id);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_menus');
- $this->setState('params', $params);
-
- // Load the clientId.
- $clientId = $app->getUserStateFromRequest('com_menus.menus.client_id', 'client_id', 0, 'int');
- $this->setState('client_id', $clientId);
- }
-
- /**
- * Method to get a menu item.
- *
- * @param integer $itemId The id of the menu item to get.
- *
- * @return mixed Menu item data object on success, false on failure.
- *
- * @since 1.6
- */
- public function &getItem($itemId = null)
- {
- $itemId = (!empty($itemId)) ? $itemId : (int) $this->getState('menu.id');
-
- // Get a menu item row instance.
- $table = $this->getTable();
-
- // Attempt to load the row.
- $return = $table->load($itemId);
-
- // Check for a table object error.
- if ($return === false && $table->getError())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $properties = $table->getProperties(1);
- $value = ArrayHelper::toObject($properties, CMSObject::class);
-
- return $value;
- }
-
- /**
- * Method to get the menu item form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_menus.menu', 'menu', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- if (!$this->getState('client_id', 0))
- {
- $form->removeField('preset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_menus.edit.menu.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- if (empty($data->id))
- {
- $data->client_id = $this->state->get('client_id', 0);
- }
- }
- else
- {
- unset($data['preset']);
- }
-
- $this->preprocessData('com_menus.menu', $data);
-
- return $data;
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see JFormRule
- * @see JFilterInput
- * @since 3.9.23
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', 'com_menus'))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $id = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('menu.id');
- $isNew = true;
-
- // Get a row instance.
- $table = $this->getTable();
-
- // Include the plugins for the save events.
- PluginHelper::importPlugin('content');
-
- // Load the row if saving an existing item.
- if ($id > 0)
- {
- $isNew = false;
- $table->load($id);
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before event.
- $result = Factory::getApplication()->triggerEvent('onContentBeforeSave', array($this->_context, &$table, $isNew, $data));
-
- // Store the data.
- if (in_array(false, $result, true) || !$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent('onContentAfterSave', array($this->_context, &$table, $isNew));
-
- $this->setState('menu.id', $table->id);
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to delete groups.
- *
- * @param array $itemIds An array of item ids.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- */
- public function delete($itemIds)
- {
- // Sanitize the ids.
- $itemIds = ArrayHelper::toInteger((array) $itemIds);
-
- // Get a group row instance.
- $table = $this->getTable();
-
- // Include the plugins for the delete events.
- PluginHelper::importPlugin('content');
-
- // Iterate the items to delete each one.
- foreach ($itemIds as $itemId)
- {
- if ($table->load($itemId))
- {
- // Trigger the before delete event.
- $result = Factory::getApplication()->triggerEvent('onContentBeforeDelete', array($this->_context, $table));
-
- if (in_array(false, $result, true) || !$table->delete($itemId))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after delete event.
- Factory::getApplication()->triggerEvent('onContentAfterDelete', array($this->_context, $table));
-
- // @todo: Delete the menu associations - Menu items and Modules
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Gets a list of all mod_mainmenu modules and collates them by menutype
- *
- * @return array
- *
- * @since 1.6
- */
- public function &getModules()
- {
- $db = $this->getDatabase();
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.title'),
- $db->quoteName('a.params'),
- $db->quoteName('a.position'),
- $db->quoteName('ag.title', 'access_title'),
- ]
- )
- ->from($db->quoteName('#__modules', 'a'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
- ->where($db->quoteName('a.module') . ' = ' . $db->quote('mod_menu'));
- $db->setQuery($query);
-
- $modules = $db->loadObjectList();
-
- $result = array();
-
- foreach ($modules as &$module)
- {
- $params = new Registry($module->params);
-
- $menuType = $params->get('menutype');
-
- if (!isset($result[$menuType]))
- {
- $result[$menuType] = array();
- }
-
- $result[$menuType][] = & $module;
- }
-
- return $result;
- }
-
- /**
- * Returns the extension elements for the given items
- *
- * @param array $itemIds The item ids
- *
- * @return array
- *
- * @since 4.2.0
- */
- public function getExtensionElementsForMenuItems(array $itemIds): array
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query
- ->select($db->quoteName('e.element'))
- ->from($db->quoteName('#__extensions', 'e'))
- ->join('INNER', $db->quoteName('#__menu', 'm'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
- ->whereIn($db->quoteName('m.id'), ArrayHelper::toInteger($itemIds));
-
- return $db->setQuery($query)->loadColumn();
- }
-
- /**
- * Custom clean the cache
- *
- * @param string $group Cache group name.
- * @param integer $clientId @deprecated 5.0 No Longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_menus');
- parent::cleanCache('com_modules');
- parent::cleanCache('mod_menu');
- }
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_MENUS_MENU';
+
+ /**
+ * Model context string.
+ *
+ * @var string
+ */
+ protected $_context = 'com_menus.menu';
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ return Factory::getUser()->authorise('core.delete', 'com_menus.menu.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether the state of a record can be edited.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ return Factory::getUser()->authorise('core.edit.state', 'com_menus.menu.' . (int) $record->id);
+ }
+
+ /**
+ * Returns a Table object, always creating it
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'MenuType', $prefix = '\JTable', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $id = $app->input->getInt('id');
+ $this->setState('menu.id', $id);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_menus');
+ $this->setState('params', $params);
+
+ // Load the clientId.
+ $clientId = $app->getUserStateFromRequest('com_menus.menus.client_id', 'client_id', 0, 'int');
+ $this->setState('client_id', $clientId);
+ }
+
+ /**
+ * Method to get a menu item.
+ *
+ * @param integer $itemId The id of the menu item to get.
+ *
+ * @return mixed Menu item data object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function &getItem($itemId = null)
+ {
+ $itemId = (!empty($itemId)) ? $itemId : (int) $this->getState('menu.id');
+
+ // Get a menu item row instance.
+ $table = $this->getTable();
+
+ // Attempt to load the row.
+ $return = $table->load($itemId);
+
+ // Check for a table object error.
+ if ($return === false && $table->getError()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $properties = $table->getProperties(1);
+ $value = ArrayHelper::toObject($properties, CMSObject::class);
+
+ return $value;
+ }
+
+ /**
+ * Method to get the menu item form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_menus.menu', 'menu', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ if (!$this->getState('client_id', 0)) {
+ $form->removeField('preset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_menus.edit.menu.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ if (empty($data->id)) {
+ $data->client_id = $this->state->get('client_id', 0);
+ }
+ } else {
+ unset($data['preset']);
+ }
+
+ $this->preprocessData('com_menus.menu', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see JFormRule
+ * @see JFilterInput
+ * @since 3.9.23
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', 'com_menus')) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $id = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('menu.id');
+ $isNew = true;
+
+ // Get a row instance.
+ $table = $this->getTable();
+
+ // Include the plugins for the save events.
+ PluginHelper::importPlugin('content');
+
+ // Load the row if saving an existing item.
+ if ($id > 0) {
+ $isNew = false;
+ $table->load($id);
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before event.
+ $result = Factory::getApplication()->triggerEvent('onContentBeforeSave', array($this->_context, &$table, $isNew, $data));
+
+ // Store the data.
+ if (in_array(false, $result, true) || !$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent('onContentAfterSave', array($this->_context, &$table, $isNew));
+
+ $this->setState('menu.id', $table->id);
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to delete groups.
+ *
+ * @param array $itemIds An array of item ids.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function delete($itemIds)
+ {
+ // Sanitize the ids.
+ $itemIds = ArrayHelper::toInteger((array) $itemIds);
+
+ // Get a group row instance.
+ $table = $this->getTable();
+
+ // Include the plugins for the delete events.
+ PluginHelper::importPlugin('content');
+
+ // Iterate the items to delete each one.
+ foreach ($itemIds as $itemId) {
+ if ($table->load($itemId)) {
+ // Trigger the before delete event.
+ $result = Factory::getApplication()->triggerEvent('onContentBeforeDelete', array($this->_context, $table));
+
+ if (in_array(false, $result, true) || !$table->delete($itemId)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after delete event.
+ Factory::getApplication()->triggerEvent('onContentAfterDelete', array($this->_context, $table));
+
+ // @todo: Delete the menu associations - Menu items and Modules
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Gets a list of all mod_mainmenu modules and collates them by menutype
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function &getModules()
+ {
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.params'),
+ $db->quoteName('a.position'),
+ $db->quoteName('ag.title', 'access_title'),
+ ]
+ )
+ ->from($db->quoteName('#__modules', 'a'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
+ ->where($db->quoteName('a.module') . ' = ' . $db->quote('mod_menu'));
+ $db->setQuery($query);
+
+ $modules = $db->loadObjectList();
+
+ $result = array();
+
+ foreach ($modules as &$module) {
+ $params = new Registry($module->params);
+
+ $menuType = $params->get('menutype');
+
+ if (!isset($result[$menuType])) {
+ $result[$menuType] = array();
+ }
+
+ $result[$menuType][] = & $module;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the extension elements for the given items
+ *
+ * @param array $itemIds The item ids
+ *
+ * @return array
+ *
+ * @since 4.2.0
+ */
+ public function getExtensionElementsForMenuItems(array $itemIds): array
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select($db->quoteName('e.element'))
+ ->from($db->quoteName('#__extensions', 'e'))
+ ->join('INNER', $db->quoteName('#__menu', 'm'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
+ ->whereIn($db->quoteName('m.id'), ArrayHelper::toInteger($itemIds));
+
+ return $db->setQuery($query)->loadColumn();
+ }
+
+ /**
+ * Custom clean the cache
+ *
+ * @param string $group Cache group name.
+ * @param integer $clientId @deprecated 5.0 No Longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_menus');
+ parent::cleanCache('com_modules');
+ parent::cleanCache('mod_menu');
+ }
}
diff --git a/administrator/components/com_menus/src/Model/MenusModel.php b/administrator/components/com_menus/src/Model/MenusModel.php
index 8ae09b42d569b..dd9152b6a2efe 100644
--- a/administrator/components/com_menus/src/Model/MenusModel.php
+++ b/administrator/components/com_menus/src/Model/MenusModel.php
@@ -1,4 +1,5 @@
getStoreId('getItems');
-
- // Try to load the data from internal storage.
- if (!empty($this->cache[$store]))
- {
- return $this->cache[$store];
- }
-
- // Load the list items.
- $items = parent::getItems();
-
- // If empty or an error, just return.
- if (empty($items))
- {
- return array();
- }
-
- // Getting the following metric by joins is WAY TOO SLOW.
- // Faster to do three queries for very large menu trees.
-
- // Get the menu types of menus in the list.
- $db = $this->getDatabase();
- $menuTypes = array_column((array) $items, 'menutype');
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('m.menutype'),
- 'COUNT(DISTINCT ' . $db->quoteName('m.id') . ') AS ' . $db->quoteName('count_published'),
- ]
- )
- ->from($db->quoteName('#__menu', 'm'))
- ->where($db->quoteName('m.published') . ' = :published')
- ->whereIn($db->quoteName('m.menutype'), $menuTypes, ParameterType::STRING)
- ->group($db->quoteName('m.menutype'))
- ->bind(':published', $published, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- // Get the published menu counts.
- try
- {
- $published = 1;
- $countPublished = $db->loadAssocList('menutype', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get the unpublished menu counts.
- try
- {
- $published = 0;
- $countUnpublished = $db->loadAssocList('menutype', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get the trashed menu counts.
- try
- {
- $published = -2;
- $countTrashed = $db->loadAssocList('menutype', 'count_published');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Inject the values back into the array.
- foreach ($items as $item)
- {
- $item->count_published = $countPublished[$item->menutype] ?? 0;
- $item->count_unpublished = $countUnpublished[$item->menutype] ?? 0;
- $item->count_trashed = $countTrashed[$item->menutype] ?? 0;
- }
-
- // Add the items to the internal cache.
- $this->cache[$store] = $items;
-
- return $this->cache[$store];
- }
-
- /**
- * Method to build an SQL query to load the list data.
- *
- * @return string An SQL query
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $clientId = (int) $this->getState('client_id');
-
- // Select all fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.menutype'),
- $db->quoteName('a.title'),
- $db->quoteName('a.description'),
- $db->quoteName('a.client_id'),
- ]
- )
- )
- ->from($db->quoteName('#__menu_types', 'a'))
- ->where(
- [
- $db->quoteName('a.id') . ' > 0',
- $db->quoteName('a.client_id') . ' = :clientId',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
-
- // Filter by search in title or menutype
- if ($search = trim($this->getState('filter.search', '')))
- {
- $search = '%' . str_replace(' ', '%', $search) . '%';
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.title') . ' LIKE :search1' ,
- $db->quoteName('a.menutype') . ' LIKE :search2',
- ],
- 'OR'
- )
- ->bind([':search1', ':search2'], $search);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.id')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.title', $direction = 'asc')
- {
- $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search');
- $this->setState('filter.search', $search);
-
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $this->setState('client_id', $clientId);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Gets the extension id of the core mod_menu module.
- *
- * @return integer
- *
- * @since 2.5
- */
- public function getModMenuId()
- {
- $clientId = (int) $this->getState('client_id');
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('e.extension_id'))
- ->from($db->quoteName('#__extensions', 'e'))
- ->where(
- [
- $db->quoteName('e.type') . ' = ' . $db->quote('module'),
- $db->quoteName('e.element') . ' = ' . $db->quote('mod_menu'),
- $db->quoteName('e.client_id') . ' = :clientId',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- $db->setQuery($query);
-
- return $db->loadResult();
- }
-
- /**
- * Gets a list of all mod_mainmenu modules and collates them by menutype
- *
- * @return array
- *
- * @since 1.6
- */
- public function &getModules()
- {
- $model = $this->bootComponent('com_menus')
- ->getMVCFactory()->createModel('Menu', 'Administrator', ['ignore_request' => true]);
- $result = $model->getModules();
-
- return $result;
- }
-
- /**
- * Returns the missing module languages.
- *
- * @return array
- *
- * @since _DEPLOY_VERSION__
- */
- public function getMissingModuleLanguages(): array
- {
- // Check custom administrator menu modules
- if (!ModuleHelper::isAdminMultilang())
- {
- return [];
- }
-
- $languages = LanguageHelper::getInstalledLanguages(1, true);
- $langCodes = [];
-
- foreach ($languages as $language)
- {
- if (isset($language->metadata['nativeName']))
- {
- $languageName = $language->metadata['nativeName'];
- }
- else
- {
- $languageName = $language->metadata['name'];
- }
-
- $langCodes[$language->metadata['tag']] = $languageName;
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('m.language'))
- ->from($db->quoteName('#__modules', 'm'))
- ->where(
- [
- $db->quoteName('m.module') . ' = ' . $db->quote('mod_menu'),
- $db->quoteName('m.published') . ' = 1',
- $db->quoteName('m.client_id') . ' = 1',
- ]
- )
- ->group($db->quoteName('m.language'));
-
- $mLanguages = $db->setQuery($query)->loadColumn();
-
- // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
- if (!in_array('*', $mLanguages) && count($langMissing = array_diff(array_keys($langCodes), $mLanguages)))
- {
- return array_intersect_key($langCodes, array_flip($langMissing));
- }
-
- return [];
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'menutype', 'a.menutype',
+ 'client_id', 'a.client_id',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Overrides the getItems method to attach additional metrics to the list.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 1.6.1
+ */
+ public function getItems()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId('getItems');
+
+ // Try to load the data from internal storage.
+ if (!empty($this->cache[$store])) {
+ return $this->cache[$store];
+ }
+
+ // Load the list items.
+ $items = parent::getItems();
+
+ // If empty or an error, just return.
+ if (empty($items)) {
+ return array();
+ }
+
+ // Getting the following metric by joins is WAY TOO SLOW.
+ // Faster to do three queries for very large menu trees.
+
+ // Get the menu types of menus in the list.
+ $db = $this->getDatabase();
+ $menuTypes = array_column((array) $items, 'menutype');
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('m.menutype'),
+ 'COUNT(DISTINCT ' . $db->quoteName('m.id') . ') AS ' . $db->quoteName('count_published'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'm'))
+ ->where($db->quoteName('m.published') . ' = :published')
+ ->whereIn($db->quoteName('m.menutype'), $menuTypes, ParameterType::STRING)
+ ->group($db->quoteName('m.menutype'))
+ ->bind(':published', $published, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ // Get the published menu counts.
+ try {
+ $published = 1;
+ $countPublished = $db->loadAssocList('menutype', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get the unpublished menu counts.
+ try {
+ $published = 0;
+ $countUnpublished = $db->loadAssocList('menutype', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get the trashed menu counts.
+ try {
+ $published = -2;
+ $countTrashed = $db->loadAssocList('menutype', 'count_published');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Inject the values back into the array.
+ foreach ($items as $item) {
+ $item->count_published = $countPublished[$item->menutype] ?? 0;
+ $item->count_unpublished = $countUnpublished[$item->menutype] ?? 0;
+ $item->count_trashed = $countTrashed[$item->menutype] ?? 0;
+ }
+
+ // Add the items to the internal cache.
+ $this->cache[$store] = $items;
+
+ return $this->cache[$store];
+ }
+
+ /**
+ * Method to build an SQL query to load the list data.
+ *
+ * @return string An SQL query
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $clientId = (int) $this->getState('client_id');
+
+ // Select all fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.menutype'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.description'),
+ $db->quoteName('a.client_id'),
+ ]
+ )
+ )
+ ->from($db->quoteName('#__menu_types', 'a'))
+ ->where(
+ [
+ $db->quoteName('a.id') . ' > 0',
+ $db->quoteName('a.client_id') . ' = :clientId',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+
+ // Filter by search in title or menutype
+ if ($search = trim($this->getState('filter.search', ''))) {
+ $search = '%' . str_replace(' ', '%', $search) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.title') . ' LIKE :search1' ,
+ $db->quoteName('a.menutype') . ' LIKE :search2',
+ ],
+ 'OR'
+ )
+ ->bind([':search1', ':search2'], $search);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.id')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.title', $direction = 'asc')
+ {
+ $search = $this->getUserStateFromRequest($this->context . '.search', 'filter_search');
+ $this->setState('filter.search', $search);
+
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $this->setState('client_id', $clientId);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Gets the extension id of the core mod_menu module.
+ *
+ * @return integer
+ *
+ * @since 2.5
+ */
+ public function getModMenuId()
+ {
+ $clientId = (int) $this->getState('client_id');
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('e.extension_id'))
+ ->from($db->quoteName('#__extensions', 'e'))
+ ->where(
+ [
+ $db->quoteName('e.type') . ' = ' . $db->quote('module'),
+ $db->quoteName('e.element') . ' = ' . $db->quote('mod_menu'),
+ $db->quoteName('e.client_id') . ' = :clientId',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ return $db->loadResult();
+ }
+
+ /**
+ * Gets a list of all mod_mainmenu modules and collates them by menutype
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function &getModules()
+ {
+ $model = $this->bootComponent('com_menus')
+ ->getMVCFactory()->createModel('Menu', 'Administrator', ['ignore_request' => true]);
+ $result = $model->getModules();
+
+ return $result;
+ }
+
+ /**
+ * Returns the missing module languages.
+ *
+ * @return array
+ *
+ * @since _DEPLOY_VERSION__
+ */
+ public function getMissingModuleLanguages(): array
+ {
+ // Check custom administrator menu modules
+ if (!ModuleHelper::isAdminMultilang()) {
+ return [];
+ }
+
+ $languages = LanguageHelper::getInstalledLanguages(1, true);
+ $langCodes = [];
+
+ foreach ($languages as $language) {
+ if (isset($language->metadata['nativeName'])) {
+ $languageName = $language->metadata['nativeName'];
+ } else {
+ $languageName = $language->metadata['name'];
+ }
+
+ $langCodes[$language->metadata['tag']] = $languageName;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('m.language'))
+ ->from($db->quoteName('#__modules', 'm'))
+ ->where(
+ [
+ $db->quoteName('m.module') . ' = ' . $db->quote('mod_menu'),
+ $db->quoteName('m.published') . ' = 1',
+ $db->quoteName('m.client_id') . ' = 1',
+ ]
+ )
+ ->group($db->quoteName('m.language'));
+
+ $mLanguages = $db->setQuery($query)->loadColumn();
+
+ // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
+ if (!in_array('*', $mLanguages) && count($langMissing = array_diff(array_keys($langCodes), $mLanguages))) {
+ return array_intersect_key($langCodes, array_flip($langMissing));
+ }
+
+ return [];
+ }
}
diff --git a/administrator/components/com_menus/src/Model/MenutypesModel.php b/administrator/components/com_menus/src/Model/MenutypesModel.php
index 85ed3f75a8e26..a05676f9bfbbd 100644
--- a/administrator/components/com_menus/src/Model/MenutypesModel.php
+++ b/administrator/components/com_menus/src/Model/MenutypesModel.php
@@ -1,4 +1,5 @@
input->get('client_id', 0);
-
- $this->state->set('client_id', $clientId);
- }
-
- /**
- * Method to get the reverse lookup of the base link URL to Title
- *
- * @return array Array of reverse lookup of the base link URL to Title
- *
- * @since 1.6
- */
- public function getReverseLookup()
- {
- if (empty($this->rlu))
- {
- $this->getTypeOptions();
- }
-
- return $this->rlu;
- }
-
- /**
- * Method to get the available menu item type options.
- *
- * @return array Array of groups with menu item types.
- *
- * @since 1.6
- */
- public function getTypeOptions()
- {
- $lang = Factory::getLanguage();
- $list = array();
-
- // Get the list of components.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('name'),
- $db->quoteName('element', 'option'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('type') . ' = ' . $db->quote('component'),
- $db->quoteName('enabled') . ' = 1',
- ]
- )
- ->order($db->quoteName('name') . ' ASC');
- $db->setQuery($query);
- $components = $db->loadObjectList();
-
- foreach ($components as $component)
- {
- $options = $this->getTypeOptionsByComponent($component->option);
-
- if ($options)
- {
- $list[$component->name] = $options;
-
- // Create the reverse lookup for link-to-name.
- foreach ($options as $option)
- {
- if (isset($option->request))
- {
- $this->addReverseLookupUrl($option);
-
- if (isset($option->request['option']))
- {
- $componentLanguageFolder = JPATH_ADMINISTRATOR . '/components/' . $option->request['option'];
- $lang->load($option->request['option'] . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($option->request['option'] . '.sys', $componentLanguageFolder);
- }
- }
- }
- }
- }
-
- // Allow a system plugin to insert dynamic menu types to the list shown in menus:
- Factory::getApplication()->triggerEvent('onAfterGetMenuTypeOptions', array(&$list, $this));
-
- return $list;
- }
-
- /**
- * Method to create the reverse lookup for link-to-name.
- * (can be used from onAfterGetMenuTypeOptions handlers)
- *
- * @param CMSObject $option Object with request array or string and title public variables
- *
- * @return void
- *
- * @since 3.1
- */
- public function addReverseLookupUrl($option)
- {
- $this->rlu[MenusHelper::getLinkKey($option->request)] = $option->get('title');
- }
-
- /**
- * Get menu types by component.
- *
- * @param string $component Component URL option.
- *
- * @return array
- *
- * @since 1.6
- */
- protected function getTypeOptionsByComponent($component)
- {
- $options = array();
- $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
- $mainXML = $client->path . '/components/' . $component . '/metadata.xml';
-
- if (is_file($mainXML))
- {
- $options = $this->getTypeOptionsFromXml($mainXML, $component);
- }
-
- if (empty($options))
- {
- $options = $this->getTypeOptionsFromMvc($component);
- }
-
- if ($client->id == 1 && empty($options))
- {
- $options = $this->getTypeOptionsFromManifest($component);
- }
-
- return $options;
- }
-
- /**
- * Get the menu types from an XML file
- *
- * @param string $file File path
- * @param string $component Component option as in URL
- *
- * @return array|boolean
- *
- * @since 1.6
- */
- protected function getTypeOptionsFromXml($file, $component)
- {
- $options = array();
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($file))
- {
- return false;
- }
-
- // Look for the first menu node off of the root node.
- if (!$menu = $xml->xpath('menu[1]'))
- {
- return false;
- }
- else
- {
- $menu = $menu[0];
- }
-
- // If we have no options to parse, just add the base component to the list of options.
- if (!empty($menu['options']) && $menu['options'] == 'none')
- {
- // Create the menu option for the component.
- $o = new CMSObject;
- $o->title = (string) $menu['name'];
- $o->description = (string) $menu['msg'];
- $o->request = array('option' => $component);
-
- $options[] = $o;
-
- return $options;
- }
-
- // Look for the first options node off of the menu node.
- if (!$optionsNode = $menu->xpath('options[1]'))
- {
- return false;
- }
- else
- {
- $optionsNode = $optionsNode[0];
- }
-
- // Make sure the options node has children.
- if (!$children = $optionsNode->children())
- {
- return false;
- }
-
- // Process each child as an option.
- foreach ($children as $child)
- {
- if ($child->getName() == 'option')
- {
- // Create the menu option for the component.
- $o = new CMSObject;
- $o->title = (string) $child['name'];
- $o->description = (string) $child['msg'];
- $o->request = array('option' => $component, (string) $optionsNode['var'] => (string) $child['value']);
-
- $options[] = $o;
- }
- elseif ($child->getName() == 'default')
- {
- // Create the menu option for the component.
- $o = new CMSObject;
- $o->title = (string) $child['name'];
- $o->description = (string) $child['msg'];
- $o->request = array('option' => $component);
-
- $options[] = $o;
- }
- }
-
- return $options;
- }
-
- /**
- * Get menu types from MVC
- *
- * @param string $component Component option like in URLs
- *
- * @return array|boolean
- *
- * @since 1.6
- */
- protected function getTypeOptionsFromMvc($component)
- {
- $options = array();
- $views = array();
-
- foreach ($this->getFolders($component) as $path)
- {
- if (!is_dir($path))
- {
- continue;
- }
-
- $views = array_merge($views, Folder::folders($path, '.', false, true));
- }
-
- foreach ($views as $viewPath)
- {
- $view = basename($viewPath);
-
- // Ignore private views.
- if (strpos($view, '_') !== 0)
- {
- // Determine if a metadata file exists for the view.
- $file = $viewPath . '/metadata.xml';
-
- if (is_file($file))
- {
- // Attempt to load the xml file.
- if ($xml = simplexml_load_file($file))
- {
- // Look for the first view node off of the root node.
- if ($menu = $xml->xpath('view[1]'))
- {
- $menu = $menu[0];
-
- // If the view is hidden from the menu, discard it and move on to the next view.
- if (!empty($menu['hidden']) && $menu['hidden'] == 'true')
- {
- unset($xml);
- continue;
- }
-
- // Do we have an options node or should we process layouts?
- // Look for the first options node off of the menu node.
- if ($optionsNode = $menu->xpath('options[1]'))
- {
- $optionsNode = $optionsNode[0];
-
- // Make sure the options node has children.
- if ($children = $optionsNode->children())
- {
- // Process each child as an option.
- foreach ($children as $child)
- {
- if ($child->getName() == 'option')
- {
- // Create the menu option for the component.
- $o = new CMSObject;
- $o->title = (string) $child['name'];
- $o->description = (string) $child['msg'];
- $o->request = array('option' => $component, 'view' => $view, (string) $optionsNode['var'] => (string) $child['value']);
-
- $options[] = $o;
- }
- elseif ($child->getName() == 'default')
- {
- // Create the menu option for the component.
- $o = new CMSObject;
- $o->title = (string) $child['name'];
- $o->description = (string) $child['msg'];
- $o->request = array('option' => $component, 'view' => $view);
-
- $options[] = $o;
- }
- }
- }
- }
- else
- {
- $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view));
- }
- }
-
- unset($xml);
- }
- }
- else
- {
- $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view));
- }
- }
- }
-
- return $options;
- }
-
- /**
- * Get menu types from Component manifest
- *
- * @param string $component Component option like in URLs
- *
- * @return array|boolean
- *
- * @since 3.7.0
- */
- protected function getTypeOptionsFromManifest($component)
- {
- // Load the component manifest
- $fileName = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml';
-
- if (!is_file($fileName))
- {
- return false;
- }
-
- if (!($manifest = simplexml_load_file($fileName)))
- {
- return false;
- }
-
- // Check for a valid XML root tag.
- if ($manifest->getName() != 'extension')
- {
- return false;
- }
-
- $options = array();
-
- // Start with the component root menu.
- $rootMenu = $manifest->administration->menu;
-
- // If the menu item doesn't exist or is hidden do nothing.
- if (!$rootMenu || in_array((string) $rootMenu['hidden'], array('true', 'hidden')))
- {
- return $options;
- }
-
- // Create the root menu option.
- $ro = new \stdClass;
- $ro->title = (string) trim($rootMenu);
- $ro->description = '';
- $ro->request = array('option' => $component);
-
- // Process submenu options.
- $submenu = $manifest->administration->submenu;
-
- if (!$submenu)
- {
- return $options;
- }
-
- foreach ($submenu->menu as $child)
- {
- $attributes = $child->attributes();
-
- $o = new \stdClass;
- $o->title = (string) trim($child);
- $o->description = '';
-
- if ((string) $attributes->link)
- {
- parse_str((string) $attributes->link, $request);
- }
- else
- {
- $request = array();
-
- $request['option'] = $component;
- $request['act'] = (string) $attributes->act;
- $request['task'] = (string) $attributes->task;
- $request['controller'] = (string) $attributes->controller;
- $request['view'] = (string) $attributes->view;
- $request['layout'] = (string) $attributes->layout;
- $request['sub'] = (string) $attributes->sub;
- }
-
- $o->request = array_filter($request, 'strlen');
- $options[] = new CMSObject($o);
-
- // Do not repeat the default view link (index.php?option=com_abc).
- if (count($o->request) == 1)
- {
- $ro = null;
- }
- }
-
- if ($ro)
- {
- $options[] = new CMSObject($ro);
- }
-
- return $options;
- }
-
- /**
- * Get the menu types from component layouts
- *
- * @param string $component Component option as in URLs
- * @param string $view Name of the view
- *
- * @return array
- *
- * @since 1.6
- */
- protected function getTypeOptionsFromLayouts($component, $view)
- {
- $options = array();
- $layouts = array();
- $layoutNames = array();
- $lang = Factory::getLanguage();
- $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
-
- // Get the views for this component.
- foreach ($this->getFolders($component) as $folder)
- {
- $path = $folder . '/' . $view . '/tmpl';
-
- if (!is_dir($path))
- {
- $path = $folder . '/' . $view;
- }
-
- if (!is_dir($path))
- {
- continue;
- }
-
- $layouts = array_merge($layouts, Folder::files($path, '.xml$', false, true));
- }
-
- // Build list of standard layout names
- foreach ($layouts as $layout)
- {
- // Ignore private layouts.
- if (strpos(basename($layout), '_') === false)
- {
- // Get the layout name.
- $layoutNames[] = basename($layout, '.xml');
- }
- }
-
- // Get the template layouts
- // @todo: This should only search one template -- the current template for this item (default of specified)
- $folders = Folder::folders($client->path . '/templates', '', false, true);
-
- // Array to hold association between template file names and templates
- $templateName = array();
-
- foreach ($folders as $folder)
- {
- if (is_dir($folder . '/html/' . $component . '/' . $view))
- {
- $template = basename($folder);
- $lang->load('tpl_' . $template . '.sys', $client->path)
- || $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template);
-
- $templateLayouts = Folder::files($folder . '/html/' . $component . '/' . $view, '.xml$', false, true);
-
- foreach ($templateLayouts as $layout)
- {
- // Get the layout name.
- $templateLayoutName = basename($layout, '.xml');
-
- // Add to the list only if it is not a standard layout
- if (array_search($templateLayoutName, $layoutNames) === false)
- {
- $layouts[] = $layout;
-
- // Set template name array so we can get the right template for the layout
- $templateName[$layout] = basename($folder);
- }
- }
- }
- }
-
- // Process the found layouts.
- foreach ($layouts as $layout)
- {
- // Ignore private layouts.
- if (strpos(basename($layout), '_') === false)
- {
- $file = $layout;
-
- // Get the layout name.
- $layout = basename($layout, '.xml');
-
- // Create the menu option for the layout.
- $o = new CMSObject;
- $o->title = ucfirst($layout);
- $o->description = '';
- $o->request = array('option' => $component, 'view' => $view);
-
- // Only add the layout request argument if not the default layout.
- if ($layout != 'default')
- {
- // If the template is set, add in format template:layout so we save the template name
- $o->request['layout'] = isset($templateName[$file]) ? $templateName[$file] . ':' . $layout : $layout;
- }
-
- // Load layout metadata if it exists.
- if (is_file($file))
- {
- // Attempt to load the xml file.
- if ($xml = simplexml_load_file($file))
- {
- // Look for the first view node off of the root node.
- if ($menu = $xml->xpath('layout[1]'))
- {
- $menu = $menu[0];
-
- // If the view is hidden from the menu, discard it and move on to the next view.
- if (!empty($menu['hidden']) && $menu['hidden'] == 'true')
- {
- unset($xml);
- unset($o);
- continue;
- }
-
- // Populate the title and description if they exist.
- if (!empty($menu['title']))
- {
- $o->title = trim((string) $menu['title']);
- }
-
- if (!empty($menu->message[0]))
- {
- $o->description = trim((string) $menu->message[0]);
- }
- }
- }
- }
-
- // Add the layout to the options array.
- $options[] = $o;
- }
- }
-
- return $options;
- }
-
- /**
- * Get the folders with template files for the given component.
- *
- * @param string $component Component option as in URLs
- *
- * @return array
- *
- * @since 4.0.0
- */
- private function getFolders($component)
- {
- $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
-
- if (!is_dir($client->path . '/components/' . $component))
- {
- return array();
- }
-
- $folders = Folder::folders($client->path . '/components/' . $component, '^view[s]?$', false, true);
- $folders = array_merge($folders, Folder::folders($client->path . '/components/' . $component, '^tmpl?$', false, true));
-
- if (!$folders)
- {
- return array();
- }
-
- return $folders;
- }
+ /**
+ * A reverse lookup of the base link URL to Title
+ *
+ * @var array
+ */
+ protected $rlu = array();
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * @return void
+ *
+ * @note Calling getState in this method will result in recursion.
+ * @since 3.0.1
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $clientId = Factory::getApplication()->input->get('client_id', 0);
+
+ $this->state->set('client_id', $clientId);
+ }
+
+ /**
+ * Method to get the reverse lookup of the base link URL to Title
+ *
+ * @return array Array of reverse lookup of the base link URL to Title
+ *
+ * @since 1.6
+ */
+ public function getReverseLookup()
+ {
+ if (empty($this->rlu)) {
+ $this->getTypeOptions();
+ }
+
+ return $this->rlu;
+ }
+
+ /**
+ * Method to get the available menu item type options.
+ *
+ * @return array Array of groups with menu item types.
+ *
+ * @since 1.6
+ */
+ public function getTypeOptions()
+ {
+ $lang = Factory::getLanguage();
+ $list = array();
+
+ // Get the list of components.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('name'),
+ $db->quoteName('element', 'option'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('type') . ' = ' . $db->quote('component'),
+ $db->quoteName('enabled') . ' = 1',
+ ]
+ )
+ ->order($db->quoteName('name') . ' ASC');
+ $db->setQuery($query);
+ $components = $db->loadObjectList();
+
+ foreach ($components as $component) {
+ $options = $this->getTypeOptionsByComponent($component->option);
+
+ if ($options) {
+ $list[$component->name] = $options;
+
+ // Create the reverse lookup for link-to-name.
+ foreach ($options as $option) {
+ if (isset($option->request)) {
+ $this->addReverseLookupUrl($option);
+
+ if (isset($option->request['option'])) {
+ $componentLanguageFolder = JPATH_ADMINISTRATOR . '/components/' . $option->request['option'];
+ $lang->load($option->request['option'] . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($option->request['option'] . '.sys', $componentLanguageFolder);
+ }
+ }
+ }
+ }
+ }
+
+ // Allow a system plugin to insert dynamic menu types to the list shown in menus:
+ Factory::getApplication()->triggerEvent('onAfterGetMenuTypeOptions', array(&$list, $this));
+
+ return $list;
+ }
+
+ /**
+ * Method to create the reverse lookup for link-to-name.
+ * (can be used from onAfterGetMenuTypeOptions handlers)
+ *
+ * @param CMSObject $option Object with request array or string and title public variables
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ public function addReverseLookupUrl($option)
+ {
+ $this->rlu[MenusHelper::getLinkKey($option->request)] = $option->get('title');
+ }
+
+ /**
+ * Get menu types by component.
+ *
+ * @param string $component Component URL option.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ protected function getTypeOptionsByComponent($component)
+ {
+ $options = array();
+ $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
+ $mainXML = $client->path . '/components/' . $component . '/metadata.xml';
+
+ if (is_file($mainXML)) {
+ $options = $this->getTypeOptionsFromXml($mainXML, $component);
+ }
+
+ if (empty($options)) {
+ $options = $this->getTypeOptionsFromMvc($component);
+ }
+
+ if ($client->id == 1 && empty($options)) {
+ $options = $this->getTypeOptionsFromManifest($component);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get the menu types from an XML file
+ *
+ * @param string $file File path
+ * @param string $component Component option as in URL
+ *
+ * @return array|boolean
+ *
+ * @since 1.6
+ */
+ protected function getTypeOptionsFromXml($file, $component)
+ {
+ $options = array();
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($file)) {
+ return false;
+ }
+
+ // Look for the first menu node off of the root node.
+ if (!$menu = $xml->xpath('menu[1]')) {
+ return false;
+ } else {
+ $menu = $menu[0];
+ }
+
+ // If we have no options to parse, just add the base component to the list of options.
+ if (!empty($menu['options']) && $menu['options'] == 'none') {
+ // Create the menu option for the component.
+ $o = new CMSObject();
+ $o->title = (string) $menu['name'];
+ $o->description = (string) $menu['msg'];
+ $o->request = array('option' => $component);
+
+ $options[] = $o;
+
+ return $options;
+ }
+
+ // Look for the first options node off of the menu node.
+ if (!$optionsNode = $menu->xpath('options[1]')) {
+ return false;
+ } else {
+ $optionsNode = $optionsNode[0];
+ }
+
+ // Make sure the options node has children.
+ if (!$children = $optionsNode->children()) {
+ return false;
+ }
+
+ // Process each child as an option.
+ foreach ($children as $child) {
+ if ($child->getName() == 'option') {
+ // Create the menu option for the component.
+ $o = new CMSObject();
+ $o->title = (string) $child['name'];
+ $o->description = (string) $child['msg'];
+ $o->request = array('option' => $component, (string) $optionsNode['var'] => (string) $child['value']);
+
+ $options[] = $o;
+ } elseif ($child->getName() == 'default') {
+ // Create the menu option for the component.
+ $o = new CMSObject();
+ $o->title = (string) $child['name'];
+ $o->description = (string) $child['msg'];
+ $o->request = array('option' => $component);
+
+ $options[] = $o;
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get menu types from MVC
+ *
+ * @param string $component Component option like in URLs
+ *
+ * @return array|boolean
+ *
+ * @since 1.6
+ */
+ protected function getTypeOptionsFromMvc($component)
+ {
+ $options = array();
+ $views = array();
+
+ foreach ($this->getFolders($component) as $path) {
+ if (!is_dir($path)) {
+ continue;
+ }
+
+ $views = array_merge($views, Folder::folders($path, '.', false, true));
+ }
+
+ foreach ($views as $viewPath) {
+ $view = basename($viewPath);
+
+ // Ignore private views.
+ if (strpos($view, '_') !== 0) {
+ // Determine if a metadata file exists for the view.
+ $file = $viewPath . '/metadata.xml';
+
+ if (is_file($file)) {
+ // Attempt to load the xml file.
+ if ($xml = simplexml_load_file($file)) {
+ // Look for the first view node off of the root node.
+ if ($menu = $xml->xpath('view[1]')) {
+ $menu = $menu[0];
+
+ // If the view is hidden from the menu, discard it and move on to the next view.
+ if (!empty($menu['hidden']) && $menu['hidden'] == 'true') {
+ unset($xml);
+ continue;
+ }
+
+ // Do we have an options node or should we process layouts?
+ // Look for the first options node off of the menu node.
+ if ($optionsNode = $menu->xpath('options[1]')) {
+ $optionsNode = $optionsNode[0];
+
+ // Make sure the options node has children.
+ if ($children = $optionsNode->children()) {
+ // Process each child as an option.
+ foreach ($children as $child) {
+ if ($child->getName() == 'option') {
+ // Create the menu option for the component.
+ $o = new CMSObject();
+ $o->title = (string) $child['name'];
+ $o->description = (string) $child['msg'];
+ $o->request = array('option' => $component, 'view' => $view, (string) $optionsNode['var'] => (string) $child['value']);
+
+ $options[] = $o;
+ } elseif ($child->getName() == 'default') {
+ // Create the menu option for the component.
+ $o = new CMSObject();
+ $o->title = (string) $child['name'];
+ $o->description = (string) $child['msg'];
+ $o->request = array('option' => $component, 'view' => $view);
+
+ $options[] = $o;
+ }
+ }
+ }
+ } else {
+ $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view));
+ }
+ }
+
+ unset($xml);
+ }
+ } else {
+ $options = array_merge($options, (array) $this->getTypeOptionsFromLayouts($component, $view));
+ }
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get menu types from Component manifest
+ *
+ * @param string $component Component option like in URLs
+ *
+ * @return array|boolean
+ *
+ * @since 3.7.0
+ */
+ protected function getTypeOptionsFromManifest($component)
+ {
+ // Load the component manifest
+ $fileName = JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml';
+
+ if (!is_file($fileName)) {
+ return false;
+ }
+
+ if (!($manifest = simplexml_load_file($fileName))) {
+ return false;
+ }
+
+ // Check for a valid XML root tag.
+ if ($manifest->getName() != 'extension') {
+ return false;
+ }
+
+ $options = array();
+
+ // Start with the component root menu.
+ $rootMenu = $manifest->administration->menu;
+
+ // If the menu item doesn't exist or is hidden do nothing.
+ if (!$rootMenu || in_array((string) $rootMenu['hidden'], array('true', 'hidden'))) {
+ return $options;
+ }
+
+ // Create the root menu option.
+ $ro = new \stdClass();
+ $ro->title = (string) trim($rootMenu);
+ $ro->description = '';
+ $ro->request = array('option' => $component);
+
+ // Process submenu options.
+ $submenu = $manifest->administration->submenu;
+
+ if (!$submenu) {
+ return $options;
+ }
+
+ foreach ($submenu->menu as $child) {
+ $attributes = $child->attributes();
+
+ $o = new \stdClass();
+ $o->title = (string) trim($child);
+ $o->description = '';
+
+ if ((string) $attributes->link) {
+ parse_str((string) $attributes->link, $request);
+ } else {
+ $request = array();
+
+ $request['option'] = $component;
+ $request['act'] = (string) $attributes->act;
+ $request['task'] = (string) $attributes->task;
+ $request['controller'] = (string) $attributes->controller;
+ $request['view'] = (string) $attributes->view;
+ $request['layout'] = (string) $attributes->layout;
+ $request['sub'] = (string) $attributes->sub;
+ }
+
+ $o->request = array_filter($request, 'strlen');
+ $options[] = new CMSObject($o);
+
+ // Do not repeat the default view link (index.php?option=com_abc).
+ if (count($o->request) == 1) {
+ $ro = null;
+ }
+ }
+
+ if ($ro) {
+ $options[] = new CMSObject($ro);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get the menu types from component layouts
+ *
+ * @param string $component Component option as in URLs
+ * @param string $view Name of the view
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ protected function getTypeOptionsFromLayouts($component, $view)
+ {
+ $options = array();
+ $layouts = array();
+ $layoutNames = array();
+ $lang = Factory::getLanguage();
+ $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
+
+ // Get the views for this component.
+ foreach ($this->getFolders($component) as $folder) {
+ $path = $folder . '/' . $view . '/tmpl';
+
+ if (!is_dir($path)) {
+ $path = $folder . '/' . $view;
+ }
+
+ if (!is_dir($path)) {
+ continue;
+ }
+
+ $layouts = array_merge($layouts, Folder::files($path, '.xml$', false, true));
+ }
+
+ // Build list of standard layout names
+ foreach ($layouts as $layout) {
+ // Ignore private layouts.
+ if (strpos(basename($layout), '_') === false) {
+ // Get the layout name.
+ $layoutNames[] = basename($layout, '.xml');
+ }
+ }
+
+ // Get the template layouts
+ // @todo: This should only search one template -- the current template for this item (default of specified)
+ $folders = Folder::folders($client->path . '/templates', '', false, true);
+
+ // Array to hold association between template file names and templates
+ $templateName = array();
+
+ foreach ($folders as $folder) {
+ if (is_dir($folder . '/html/' . $component . '/' . $view)) {
+ $template = basename($folder);
+ $lang->load('tpl_' . $template . '.sys', $client->path)
+ || $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template);
+
+ $templateLayouts = Folder::files($folder . '/html/' . $component . '/' . $view, '.xml$', false, true);
+
+ foreach ($templateLayouts as $layout) {
+ // Get the layout name.
+ $templateLayoutName = basename($layout, '.xml');
+
+ // Add to the list only if it is not a standard layout
+ if (array_search($templateLayoutName, $layoutNames) === false) {
+ $layouts[] = $layout;
+
+ // Set template name array so we can get the right template for the layout
+ $templateName[$layout] = basename($folder);
+ }
+ }
+ }
+ }
+
+ // Process the found layouts.
+ foreach ($layouts as $layout) {
+ // Ignore private layouts.
+ if (strpos(basename($layout), '_') === false) {
+ $file = $layout;
+
+ // Get the layout name.
+ $layout = basename($layout, '.xml');
+
+ // Create the menu option for the layout.
+ $o = new CMSObject();
+ $o->title = ucfirst($layout);
+ $o->description = '';
+ $o->request = array('option' => $component, 'view' => $view);
+
+ // Only add the layout request argument if not the default layout.
+ if ($layout != 'default') {
+ // If the template is set, add in format template:layout so we save the template name
+ $o->request['layout'] = isset($templateName[$file]) ? $templateName[$file] . ':' . $layout : $layout;
+ }
+
+ // Load layout metadata if it exists.
+ if (is_file($file)) {
+ // Attempt to load the xml file.
+ if ($xml = simplexml_load_file($file)) {
+ // Look for the first view node off of the root node.
+ if ($menu = $xml->xpath('layout[1]')) {
+ $menu = $menu[0];
+
+ // If the view is hidden from the menu, discard it and move on to the next view.
+ if (!empty($menu['hidden']) && $menu['hidden'] == 'true') {
+ unset($xml);
+ unset($o);
+ continue;
+ }
+
+ // Populate the title and description if they exist.
+ if (!empty($menu['title'])) {
+ $o->title = trim((string) $menu['title']);
+ }
+
+ if (!empty($menu->message[0])) {
+ $o->description = trim((string) $menu->message[0]);
+ }
+ }
+ }
+ }
+
+ // Add the layout to the options array.
+ $options[] = $o;
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get the folders with template files for the given component.
+ *
+ * @param string $component Component option as in URLs
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ private function getFolders($component)
+ {
+ $client = ApplicationHelper::getClientInfo($this->getState('client_id'));
+
+ if (!is_dir($client->path . '/components/' . $component)) {
+ return array();
+ }
+
+ $folders = Folder::folders($client->path . '/components/' . $component, '^view[s]?$', false, true);
+ $folders = array_merge($folders, Folder::folders($client->path . '/components/' . $component, '^tmpl?$', false, true));
+
+ if (!$folders) {
+ return array();
+ }
+
+ return $folders;
+ }
}
diff --git a/administrator/components/com_menus/src/Service/HTML/Menus.php b/administrator/components/com_menus/src/Service/HTML/Menus.php
index 4fac4b103af72..883193041fe81 100644
--- a/administrator/components/com_menus/src/Service/HTML/Menus.php
+++ b/administrator/components/com_menus/src/Service/HTML/Menus.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select(
- [
- $db->quoteName('m.id'),
- $db->quoteName('m.title'),
- $db->quoteName('l.sef', 'lang_sef'),
- $db->quoteName('l.lang_code'),
- $db->quoteName('mt.title', 'menu_title'),
- $db->quoteName('l.image'),
- $db->quoteName('l.title', 'language_title'),
- ]
- )
- ->from($db->quoteName('#__menu', 'm'))
- ->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('m.menutype'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('m.language') . ' = ' . $db->quoteName('l.lang_code'))
- ->whereIn($db->quoteName('m.id'), array_values($associations))
- ->where($db->quoteName('m.id') . ' != :itemid')
- ->bind(':itemid', $itemid, ParameterType::INTEGER);
- $db->setQuery($query);
+ // Get the associations
+ if ($associations = MenusHelper::getAssociations($itemid)) {
+ // Get the associated menu items
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('m.id'),
+ $db->quoteName('m.title'),
+ $db->quoteName('l.sef', 'lang_sef'),
+ $db->quoteName('l.lang_code'),
+ $db->quoteName('mt.title', 'menu_title'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.title', 'language_title'),
+ ]
+ )
+ ->from($db->quoteName('#__menu', 'm'))
+ ->join('LEFT', $db->quoteName('#__menu_types', 'mt'), $db->quoteName('mt.menutype') . ' = ' . $db->quoteName('m.menutype'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('m.language') . ' = ' . $db->quoteName('l.lang_code'))
+ ->whereIn($db->quoteName('m.id'), array_values($associations))
+ ->where($db->quoteName('m.id') . ' != :itemid')
+ ->bind(':itemid', $itemid, ParameterType::INTEGER);
+ $db->setQuery($query);
- try
- {
- $items = $db->loadObjectList('id');
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
+ try {
+ $items = $db->loadObjectList('id');
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
- // Construct html
- if ($items)
- {
- $languages = LanguageHelper::getContentLanguages(array(0, 1));
- $content_languages = array_column($languages, 'lang_code');
+ // Construct html
+ if ($items) {
+ $languages = LanguageHelper::getContentLanguages(array(0, 1));
+ $content_languages = array_column($languages, 'lang_code');
- foreach ($items as &$item)
- {
- if (in_array($item->lang_code, $content_languages))
- {
- $text = $item->lang_code;
- $url = Route::_('index.php?option=com_menus&task=item.edit&id=' . (int) $item->id);
- $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $item->menu_title);
- $classes = 'badge bg-secondary';
+ foreach ($items as &$item) {
+ if (in_array($item->lang_code, $content_languages)) {
+ $text = $item->lang_code;
+ $url = Route::_('index.php?option=com_menus&task=item.edit&id=' . (int) $item->id);
+ $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('COM_MENUS_MENU_SPRINTF', $item->menu_title);
+ $classes = 'badge bg-secondary';
- $item->link = '' . $text . ' '
- . '' . $tooltip . '
';
- }
- else
- {
- // Display warning if Content Language is trashed or deleted
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
- }
- }
- }
+ $item->link = '' . $text . ' '
+ . '' . $tooltip . '
';
+ } else {
+ // Display warning if Content Language is trashed or deleted
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
+ }
+ }
+ }
- $html = LayoutHelper::render('joomla.content.associations', $items);
- }
+ $html = LayoutHelper::render('joomla.content.associations', $items);
+ }
- return $html;
- }
+ return $html;
+ }
- /**
- * Returns a visibility state on a grid
- *
- * @param integer $params Params of item.
- *
- * @return string The Html code
- *
- * @since 3.7.0
- */
- public function visibility($params)
- {
- $registry = new Registry;
+ /**
+ * Returns a visibility state on a grid
+ *
+ * @param integer $params Params of item.
+ *
+ * @return string The Html code
+ *
+ * @since 3.7.0
+ */
+ public function visibility($params)
+ {
+ $registry = new Registry();
- try
- {
- $registry->loadString($params);
- }
- catch (\Exception $e)
- {
- // Invalid JSON
- }
+ try {
+ $registry->loadString($params);
+ } catch (\Exception $e) {
+ // Invalid JSON
+ }
- $show_menu = $registry->get('menu_show');
+ $show_menu = $registry->get('menu_show');
- return ($show_menu === 0) ? '' . Text::_('COM_MENUS_LABEL_HIDDEN') . ' ' : '';
- }
+ return ($show_menu === 0) ? '' . Text::_('COM_MENUS_LABEL_HIDDEN') . ' ' : '';
+ }
}
diff --git a/administrator/components/com_menus/src/Table/MenuTable.php b/administrator/components/com_menus/src/Table/MenuTable.php
index 33a60da57b6ff..e70e094cded10 100644
--- a/administrator/components/com_menus/src/Table/MenuTable.php
+++ b/administrator/components/com_menus/src/Table/MenuTable.php
@@ -1,4 +1,5 @@
getDbo();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('menuid') . ' = :pk')
- ->bind(':pk', $pk, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- }
+ if ($return) {
+ // Delete key from the #__modules_menu table
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('menuid') . ' = :pk')
+ ->bind(':pk', $pk, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ }
- return $return;
- }
+ return $return;
+ }
- /**
- * Overloaded check function
- *
- * @return boolean True on success, false on failure
- *
- * @see JTable::check
- * @since 4.0.0
- */
- public function check()
- {
- $return = parent::check();
+ /**
+ * Overloaded check function
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @see JTable::check
+ * @since 4.0.0
+ */
+ public function check()
+ {
+ $return = parent::check();
- if ($return)
- {
- // Set publish_up to null date if not set
- if (!$this->publish_up)
- {
- $this->publish_up = null;
- }
+ if ($return) {
+ // Set publish_up to null date if not set
+ if (!$this->publish_up) {
+ $this->publish_up = null;
+ }
- // Set publish_down to null date if not set
- if (!$this->publish_down)
- {
- $this->publish_down = null;
- }
+ // Set publish_down to null date if not set
+ if (!$this->publish_down) {
+ $this->publish_down = null;
+ }
- // Check the publish down date is not earlier than publish up.
- if (!is_null($this->publish_down) && !is_null($this->publish_up) && $this->publish_down < $this->publish_up)
- {
- $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
+ // Check the publish down date is not earlier than publish up.
+ if (!is_null($this->publish_down) && !is_null($this->publish_up) && $this->publish_down < $this->publish_up) {
+ $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
- return false;
- }
+ return false;
+ }
- if ((int) $this->home)
- {
- // Set the publish down/up always for home.
- $this->publish_up = null;
- $this->publish_down = null;
- }
- }
+ if ((int) $this->home) {
+ // Set the publish down/up always for home.
+ $this->publish_up = null;
+ $this->publish_down = null;
+ }
+ }
- return $return;
- }
+ return $return;
+ }
}
diff --git a/administrator/components/com_menus/src/Table/MenuTypeTable.php b/administrator/components/com_menus/src/Table/MenuTypeTable.php
index 108656290abae..b1a2ebe9eb0bf 100644
--- a/administrator/components/com_menus/src/Table/MenuTypeTable.php
+++ b/administrator/components/com_menus/src/Table/MenuTypeTable.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->modules = $this->get('Modules');
- $this->levels = $this->get('ViewLevels');
- $this->canDo = ContentHelper::getActions('com_menus', 'menu', (int) $this->state->get('item.menutypeid'));
-
- // Check if we're allowed to edit this item
- // No need to check for create, because then the moduletype select is empty
- if (!empty($this->item->id) && !$this->canDo->get('core.edit'))
- {
- throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // If we are forcing a language in modal (used for associations).
- if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
- {
- // Set the language field to the forcedLanguage and disable changing it.
- $this->form->setValue('language', null, $forcedLanguage);
- $this->form->setFieldAttribute('language', 'readonly', 'true');
-
- // Only allow to select categories with All language or with the forced language.
- $this->form->setFieldAttribute('parent_id', 'language', '*,' . $forcedLanguage);
- }
-
- parent::display($tpl);
- $this->addToolbar();
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $input = Factory::getApplication()->input;
- $input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
- $canDo = $this->canDo;
- $clientId = $this->state->get('item.client_id', 0);
-
- ToolbarHelper::title(Text::_($isNew ? 'COM_MENUS_VIEW_NEW_ITEM_TITLE' : 'COM_MENUS_VIEW_EDIT_ITEM_TITLE'), 'list menu-add');
-
- $toolbarButtons = [];
-
- // If a new item, can save the item. Allow users with edit permissions to apply changes to prevent returning to grid.
- if ($isNew && $canDo->get('core.create'))
- {
- if ($canDo->get('core.edit'))
- {
- ToolbarHelper::apply('item.apply');
- }
-
- $toolbarButtons[] = ['save', 'item.save'];
- }
-
- // If not checked out, can save the item.
- if (!$isNew && !$checkedOut && $canDo->get('core.edit'))
- {
- ToolbarHelper::apply('item.apply');
-
- $toolbarButtons[] = ['save', 'item.save'];
- }
-
- // If the user can create new items, allow them to see Save & New
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'item.save2new'];
- }
-
- // If an existing item, can save to a copy only if we have create rights.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'item.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (!$isNew && Associations::isEnabled() && ComponentHelper::isEnabled('com_associations') && $clientId != 1)
- {
- ToolbarHelper::custom('item.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
- }
-
- if ($isNew)
- {
- ToolbarHelper::cancel('item.cancel');
- }
- else
- {
- ToolbarHelper::cancel('item.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
-
- // Get the help information for the menu item.
- $lang = Factory::getLanguage();
-
- $help = $this->get('Help');
-
- if ($lang->hasKey($help->url))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($help->url);
- $lang->setDebug($debug);
- }
- else
- {
- $url = $help->url;
- }
-
- ToolbarHelper::help($help->key, $help->local, $url);
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var CMSObject
+ */
+ protected $item;
+
+ /**
+ * @var mixed
+ */
+ protected $modules;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ * @since 3.7.0
+ */
+ protected $canDo;
+
+ /**
+ * A list of view levels containing the id and title of the view level
+ *
+ * @var \stdClass[]
+ * @since 4.0.0
+ */
+ protected $levels;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->modules = $this->get('Modules');
+ $this->levels = $this->get('ViewLevels');
+ $this->canDo = ContentHelper::getActions('com_menus', 'menu', (int) $this->state->get('item.menutypeid'));
+
+ // Check if we're allowed to edit this item
+ // No need to check for create, because then the moduletype select is empty
+ if (!empty($this->item->id) && !$this->canDo->get('core.edit')) {
+ throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('parent_id', 'language', '*,' . $forcedLanguage);
+ }
+
+ parent::display($tpl);
+ $this->addToolbar();
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $input = Factory::getApplication()->input;
+ $input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
+ $canDo = $this->canDo;
+ $clientId = $this->state->get('item.client_id', 0);
+
+ ToolbarHelper::title(Text::_($isNew ? 'COM_MENUS_VIEW_NEW_ITEM_TITLE' : 'COM_MENUS_VIEW_EDIT_ITEM_TITLE'), 'list menu-add');
+
+ $toolbarButtons = [];
+
+ // If a new item, can save the item. Allow users with edit permissions to apply changes to prevent returning to grid.
+ if ($isNew && $canDo->get('core.create')) {
+ if ($canDo->get('core.edit')) {
+ ToolbarHelper::apply('item.apply');
+ }
+
+ $toolbarButtons[] = ['save', 'item.save'];
+ }
+
+ // If not checked out, can save the item.
+ if (!$isNew && !$checkedOut && $canDo->get('core.edit')) {
+ ToolbarHelper::apply('item.apply');
+
+ $toolbarButtons[] = ['save', 'item.save'];
+ }
+
+ // If the user can create new items, allow them to see Save & New
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'item.save2new'];
+ }
+
+ // If an existing item, can save to a copy only if we have create rights.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'item.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (!$isNew && Associations::isEnabled() && ComponentHelper::isEnabled('com_associations') && $clientId != 1) {
+ ToolbarHelper::custom('item.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
+ }
+
+ if ($isNew) {
+ ToolbarHelper::cancel('item.cancel');
+ } else {
+ ToolbarHelper::cancel('item.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+
+ // Get the help information for the menu item.
+ $lang = Factory::getLanguage();
+
+ $help = $this->get('Help');
+
+ if ($lang->hasKey($help->url)) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($help->url);
+ $lang->setDebug($debug);
+ } else {
+ $url = $help->url;
+ }
+
+ ToolbarHelper::help($help->key, $help->local, $url);
+ }
}
diff --git a/administrator/components/com_menus/src/View/Items/HtmlView.php b/administrator/components/com_menus/src/View/Items/HtmlView.php
index fe1c86bdd9521..3182c810e376b 100644
--- a/administrator/components/com_menus/src/View/Items/HtmlView.php
+++ b/administrator/components/com_menus/src/View/Items/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->total = $this->get('Total');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->ordering = array();
-
- // Preprocess the list of items to find ordering divisions.
- foreach ($this->items as $item)
- {
- $this->ordering[$item->parent_id][] = $item->id;
-
- // Item type text
- switch ($item->type)
- {
- case 'url':
- $value = Text::_('COM_MENUS_TYPE_EXTERNAL_URL');
- break;
-
- case 'alias':
- $value = Text::_('COM_MENUS_TYPE_ALIAS');
- break;
-
- case 'separator':
- $value = Text::_('COM_MENUS_TYPE_SEPARATOR');
- break;
-
- case 'heading':
- $value = Text::_('COM_MENUS_TYPE_HEADING');
- break;
-
- case 'container':
- $value = Text::_('COM_MENUS_TYPE_CONTAINER');
- break;
-
- case 'component':
- default:
- // Load language
- $lang->load($item->componentname . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($item->componentname . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->componentname);
-
- if (!empty($item->componentname))
- {
- $titleParts = array();
- $titleParts[] = Text::_($item->componentname);
- $vars = null;
-
- parse_str($item->link, $vars);
-
- if (isset($vars['view']))
- {
- // Attempt to load the view xml file.
- $file = JPATH_SITE . '/components/' . $item->componentname . '/views/' . $vars['view'] . '/metadata.xml';
-
- if (!is_file($file))
- {
- $file = JPATH_SITE . '/components/' . $item->componentname . '/view/' . $vars['view'] . '/metadata.xml';
- }
-
- if (is_file($file) && $xml = simplexml_load_file($file))
- {
- // Look for the first view node off of the root node.
- if ($view = $xml->xpath('view[1]'))
- {
- // Add view title if present.
- if (!empty($view[0]['title']))
- {
- $viewTitle = trim((string) $view[0]['title']);
-
- // Check if the key is valid. Needed due to B/C so we don't show untranslated keys. This check should be removed with Joomla 4.
- if ($lang->hasKey($viewTitle))
- {
- $titleParts[] = Text::_($viewTitle);
- }
- }
- }
- }
-
- $vars['layout'] = $vars['layout'] ?? 'default';
-
- // Attempt to load the layout xml file.
- // If Alternative Menu Item, get template folder for layout file
- if (strpos($vars['layout'], ':') > 0)
- {
- // Use template folder for layout file
- $temp = explode(':', $vars['layout']);
- $file = JPATH_SITE . '/templates/' . $temp[0] . '/html/' . $item->componentname . '/' . $vars['view'] . '/' . $temp[1] . '.xml';
-
- // Load template language file
- $lang->load('tpl_' . $temp[0] . '.sys', JPATH_SITE)
- || $lang->load('tpl_' . $temp[0] . '.sys', JPATH_SITE . '/templates/' . $temp[0]);
- }
- else
- {
- $base = $this->state->get('filter.client_id') == 0 ? JPATH_SITE : JPATH_ADMINISTRATOR;
-
- // Get XML file from component folder for standard layouts
- $file = $base . '/components/' . $item->componentname . '/tmpl/' . $vars['view']
- . '/' . $vars['layout'] . '.xml';
-
- if (!file_exists($file))
- {
- $file = $base . '/components/' . $item->componentname . '/views/'
- . $vars['view'] . '/tmpl/' . $vars['layout'] . '.xml';
-
- if (!file_exists($file))
- {
- $file = $base . '/components/' . $item->componentname . '/view/'
- . $vars['view'] . '/tmpl/' . $vars['layout'] . '.xml';
- }
- }
- }
-
- if (is_file($file) && $xml = simplexml_load_file($file))
- {
- // Look for the first view node off of the root node.
- if ($layout = $xml->xpath('layout[1]'))
- {
- if (!empty($layout[0]['title']))
- {
- $titleParts[] = Text::_(trim((string) $layout[0]['title']));
- }
- }
-
- if (!empty($layout[0]->message[0]))
- {
- $item->item_type_desc = Text::_(trim((string) $layout[0]->message[0]));
- }
- }
-
- unset($xml);
-
- // Special case if neither a view nor layout title is found
- if (count($titleParts) == 1)
- {
- $titleParts[] = $vars['view'];
- }
- }
-
- $value = implode(' » ', $titleParts);
- }
- else
- {
- if (preg_match("/^index.php\?option=([a-zA-Z\-0-9_]*)/", $item->link, $result))
- {
- $value = Text::sprintf('COM_MENUS_TYPE_UNEXISTING', $result[1]);
- }
- else
- {
- $value = Text::_('COM_MENUS_TYPE_UNKNOWN');
- }
- }
- break;
- }
-
- $item->item_type = $value;
- $item->protected = $item->menutype == 'main';
- }
-
- // Levels filter.
- $options = array();
- $options[] = HTMLHelper::_('select.option', '1', Text::_('J1'));
- $options[] = HTMLHelper::_('select.option', '2', Text::_('J2'));
- $options[] = HTMLHelper::_('select.option', '3', Text::_('J3'));
- $options[] = HTMLHelper::_('select.option', '4', Text::_('J4'));
- $options[] = HTMLHelper::_('select.option', '5', Text::_('J5'));
- $options[] = HTMLHelper::_('select.option', '6', Text::_('J6'));
- $options[] = HTMLHelper::_('select.option', '7', Text::_('J7'));
- $options[] = HTMLHelper::_('select.option', '8', Text::_('J8'));
- $options[] = HTMLHelper::_('select.option', '9', Text::_('J9'));
- $options[] = HTMLHelper::_('select.option', '10', Text::_('J10'));
-
- $this->f_levels = $options;
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- else
- {
- // In menu associations modal we need to remove language filter if forcing a language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
- $languageXml = new \SimpleXMLElement(' ');
- $this->filterForm->setField($languageXml, 'filter', true);
-
- // Also, unset the active language filter so the search tools is not open by default with this filter.
- unset($this->activeFilters['language']);
- }
- }
-
- // Allow a system plugin to insert dynamic menu types to the list shown in menus:
- Factory::getApplication()->triggerEvent('onBeforeRenderMenuItems', array($this));
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $menutypeId = (int) $this->state->get('menutypeid');
-
- $canDo = ContentHelper::getActions('com_menus', 'menu', (int) $menutypeId);
- $user = $this->getCurrentUser();
-
- // Get the menu title
- $menuTypeTitle = $this->get('State')->get('menutypetitle');
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- if ($menuTypeTitle)
- {
- ToolbarHelper::title(Text::sprintf('COM_MENUS_VIEW_ITEMS_MENU_TITLE', $menuTypeTitle), 'list menumgr');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_MENUS_VIEW_ITEMS_ALL_TITLE'), 'list menumgr');
- }
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('item.add');
- }
-
- $protected = $this->state->get('filter.menutype') == 'main';
-
- if (($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin')) && !$protected
- || $canDo->get('core.edit.state') && $this->state->get('filter.client_id') == 0)
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state') && !$protected)
- {
- $childBar->publish('items.publish')->listCheck(true);
-
- $childBar->unpublish('items.unpublish')->listCheck(true);
- }
-
- if ($this->getCurrentUser()->authorise('core.admin') && !$protected)
- {
- $childBar->checkin('items.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2)
- {
- if ($this->state->get('filter.client_id') == 0)
- {
- $childBar->makeDefault('items.setDefault')->listCheck(true);
- }
-
- if (!$protected)
- {
- $childBar->trash('items.trash')->listCheck(true);
- }
- }
-
- // Add a batch button
- if (!$protected && $user->authorise('core.create', 'com_menus')
- && $user->authorise('core.edit', 'com_menus')
- && $user->authorise('core.edit.state', 'com_menus'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if ($this->getCurrentUser()->authorise('core.admin'))
- {
- $toolbar->standardButton('refresh')
- ->text('JTOOLBAR_REBUILD')
- ->task('items.rebuild');
- }
-
- if (!$protected && $this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('items.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_menus');
- }
-
- $toolbar->help('Menus:_Items');
- }
+ /**
+ * Array used for displaying the levels filter
+ *
+ * @var \stdClass[]
+ * @since 4.0.0
+ */
+ protected $f_levels;
+
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Ordering of the items
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $ordering;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $lang = Factory::getLanguage();
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->total = $this->get('Total');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->ordering = array();
+
+ // Preprocess the list of items to find ordering divisions.
+ foreach ($this->items as $item) {
+ $this->ordering[$item->parent_id][] = $item->id;
+
+ // Item type text
+ switch ($item->type) {
+ case 'url':
+ $value = Text::_('COM_MENUS_TYPE_EXTERNAL_URL');
+ break;
+
+ case 'alias':
+ $value = Text::_('COM_MENUS_TYPE_ALIAS');
+ break;
+
+ case 'separator':
+ $value = Text::_('COM_MENUS_TYPE_SEPARATOR');
+ break;
+
+ case 'heading':
+ $value = Text::_('COM_MENUS_TYPE_HEADING');
+ break;
+
+ case 'container':
+ $value = Text::_('COM_MENUS_TYPE_CONTAINER');
+ break;
+
+ case 'component':
+ default:
+ // Load language
+ $lang->load($item->componentname . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($item->componentname . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->componentname);
+
+ if (!empty($item->componentname)) {
+ $titleParts = array();
+ $titleParts[] = Text::_($item->componentname);
+ $vars = null;
+
+ parse_str($item->link, $vars);
+
+ if (isset($vars['view'])) {
+ // Attempt to load the view xml file.
+ $file = JPATH_SITE . '/components/' . $item->componentname . '/views/' . $vars['view'] . '/metadata.xml';
+
+ if (!is_file($file)) {
+ $file = JPATH_SITE . '/components/' . $item->componentname . '/view/' . $vars['view'] . '/metadata.xml';
+ }
+
+ if (is_file($file) && $xml = simplexml_load_file($file)) {
+ // Look for the first view node off of the root node.
+ if ($view = $xml->xpath('view[1]')) {
+ // Add view title if present.
+ if (!empty($view[0]['title'])) {
+ $viewTitle = trim((string) $view[0]['title']);
+
+ // Check if the key is valid. Needed due to B/C so we don't show untranslated keys. This check should be removed with Joomla 4.
+ if ($lang->hasKey($viewTitle)) {
+ $titleParts[] = Text::_($viewTitle);
+ }
+ }
+ }
+ }
+
+ $vars['layout'] = $vars['layout'] ?? 'default';
+
+ // Attempt to load the layout xml file.
+ // If Alternative Menu Item, get template folder for layout file
+ if (strpos($vars['layout'], ':') > 0) {
+ // Use template folder for layout file
+ $temp = explode(':', $vars['layout']);
+ $file = JPATH_SITE . '/templates/' . $temp[0] . '/html/' . $item->componentname . '/' . $vars['view'] . '/' . $temp[1] . '.xml';
+
+ // Load template language file
+ $lang->load('tpl_' . $temp[0] . '.sys', JPATH_SITE)
+ || $lang->load('tpl_' . $temp[0] . '.sys', JPATH_SITE . '/templates/' . $temp[0]);
+ } else {
+ $base = $this->state->get('filter.client_id') == 0 ? JPATH_SITE : JPATH_ADMINISTRATOR;
+
+ // Get XML file from component folder for standard layouts
+ $file = $base . '/components/' . $item->componentname . '/tmpl/' . $vars['view']
+ . '/' . $vars['layout'] . '.xml';
+
+ if (!file_exists($file)) {
+ $file = $base . '/components/' . $item->componentname . '/views/'
+ . $vars['view'] . '/tmpl/' . $vars['layout'] . '.xml';
+
+ if (!file_exists($file)) {
+ $file = $base . '/components/' . $item->componentname . '/view/'
+ . $vars['view'] . '/tmpl/' . $vars['layout'] . '.xml';
+ }
+ }
+ }
+
+ if (is_file($file) && $xml = simplexml_load_file($file)) {
+ // Look for the first view node off of the root node.
+ if ($layout = $xml->xpath('layout[1]')) {
+ if (!empty($layout[0]['title'])) {
+ $titleParts[] = Text::_(trim((string) $layout[0]['title']));
+ }
+ }
+
+ if (!empty($layout[0]->message[0])) {
+ $item->item_type_desc = Text::_(trim((string) $layout[0]->message[0]));
+ }
+ }
+
+ unset($xml);
+
+ // Special case if neither a view nor layout title is found
+ if (count($titleParts) == 1) {
+ $titleParts[] = $vars['view'];
+ }
+ }
+
+ $value = implode(' » ', $titleParts);
+ } else {
+ if (preg_match("/^index.php\?option=([a-zA-Z\-0-9_]*)/", $item->link, $result)) {
+ $value = Text::sprintf('COM_MENUS_TYPE_UNEXISTING', $result[1]);
+ } else {
+ $value = Text::_('COM_MENUS_TYPE_UNKNOWN');
+ }
+ }
+ break;
+ }
+
+ $item->item_type = $value;
+ $item->protected = $item->menutype == 'main';
+ }
+
+ // Levels filter.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('J1'));
+ $options[] = HTMLHelper::_('select.option', '2', Text::_('J2'));
+ $options[] = HTMLHelper::_('select.option', '3', Text::_('J3'));
+ $options[] = HTMLHelper::_('select.option', '4', Text::_('J4'));
+ $options[] = HTMLHelper::_('select.option', '5', Text::_('J5'));
+ $options[] = HTMLHelper::_('select.option', '6', Text::_('J6'));
+ $options[] = HTMLHelper::_('select.option', '7', Text::_('J7'));
+ $options[] = HTMLHelper::_('select.option', '8', Text::_('J8'));
+ $options[] = HTMLHelper::_('select.option', '9', Text::_('J9'));
+ $options[] = HTMLHelper::_('select.option', '10', Text::_('J10'));
+
+ $this->f_levels = $options;
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // In menu associations modal we need to remove language filter if forcing a language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement(' ');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+ }
+ }
+
+ // Allow a system plugin to insert dynamic menu types to the list shown in menus:
+ Factory::getApplication()->triggerEvent('onBeforeRenderMenuItems', array($this));
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $menutypeId = (int) $this->state->get('menutypeid');
+
+ $canDo = ContentHelper::getActions('com_menus', 'menu', (int) $menutypeId);
+ $user = $this->getCurrentUser();
+
+ // Get the menu title
+ $menuTypeTitle = $this->get('State')->get('menutypetitle');
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ if ($menuTypeTitle) {
+ ToolbarHelper::title(Text::sprintf('COM_MENUS_VIEW_ITEMS_MENU_TITLE', $menuTypeTitle), 'list menumgr');
+ } else {
+ ToolbarHelper::title(Text::_('COM_MENUS_VIEW_ITEMS_ALL_TITLE'), 'list menumgr');
+ }
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('item.add');
+ }
+
+ $protected = $this->state->get('filter.menutype') == 'main';
+
+ if (
+ ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin')) && !$protected
+ || $canDo->get('core.edit.state') && $this->state->get('filter.client_id') == 0
+ ) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state') && !$protected) {
+ $childBar->publish('items.publish')->listCheck(true);
+
+ $childBar->unpublish('items.unpublish')->listCheck(true);
+ }
+
+ if ($this->getCurrentUser()->authorise('core.admin') && !$protected) {
+ $childBar->checkin('items.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2) {
+ if ($this->state->get('filter.client_id') == 0) {
+ $childBar->makeDefault('items.setDefault')->listCheck(true);
+ }
+
+ if (!$protected) {
+ $childBar->trash('items.trash')->listCheck(true);
+ }
+ }
+
+ // Add a batch button
+ if (
+ !$protected && $user->authorise('core.create', 'com_menus')
+ && $user->authorise('core.edit', 'com_menus')
+ && $user->authorise('core.edit.state', 'com_menus')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if ($this->getCurrentUser()->authorise('core.admin')) {
+ $toolbar->standardButton('refresh')
+ ->text('JTOOLBAR_REBUILD')
+ ->task('items.rebuild');
+ }
+
+ if (!$protected && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('items.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_menus');
+ }
+
+ $toolbar->help('Menus:_Items');
+ }
}
diff --git a/administrator/components/com_menus/src/View/Menu/HtmlView.php b/administrator/components/com_menus/src/View/Menu/HtmlView.php
index 4ceeff9dd6030..13c422e68c50a 100644
--- a/administrator/components/com_menus/src/View/Menu/HtmlView.php
+++ b/administrator/components/com_menus/src/View/Menu/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- $this->canDo = ContentHelper::getActions('com_menus', 'menu', $this->item->id);
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- parent::display($tpl);
- $this->addToolbar();
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $input = Factory::getApplication()->input;
- $input->set('hidemainmenu', true);
-
- $isNew = ($this->item->id == 0);
-
- ToolbarHelper::title(Text::_($isNew ? 'COM_MENUS_VIEW_NEW_MENU_TITLE' : 'COM_MENUS_VIEW_EDIT_MENU_TITLE'), 'list menu');
-
- $toolbarButtons = [];
-
- // If a new item, can save the item. Allow users with edit permissions to apply changes to prevent returning to grid.
- if ($isNew && $this->canDo->get('core.create'))
- {
- if ($this->canDo->get('core.edit'))
- {
- ToolbarHelper::apply('menu.apply');
- }
-
- $toolbarButtons[] = ['save', 'menu.save'];
- }
-
- // If user can edit, can save the item.
- if (!$isNew && $this->canDo->get('core.edit'))
- {
- ToolbarHelper::apply('menu.apply');
-
- $toolbarButtons[] = ['save', 'menu.save'];
- }
-
- // If the user can create new items, allow them to see Save & New
- if ($this->canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'menu.save2new'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if ($isNew)
- {
- ToolbarHelper::cancel('menu.cancel');
- }
- else
- {
- ToolbarHelper::cancel('menu.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Menus:_Edit');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ */
+ protected $canDo;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ $this->canDo = ContentHelper::getActions('com_menus', 'menu', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ parent::display($tpl);
+ $this->addToolbar();
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $input = Factory::getApplication()->input;
+ $input->set('hidemainmenu', true);
+
+ $isNew = ($this->item->id == 0);
+
+ ToolbarHelper::title(Text::_($isNew ? 'COM_MENUS_VIEW_NEW_MENU_TITLE' : 'COM_MENUS_VIEW_EDIT_MENU_TITLE'), 'list menu');
+
+ $toolbarButtons = [];
+
+ // If a new item, can save the item. Allow users with edit permissions to apply changes to prevent returning to grid.
+ if ($isNew && $this->canDo->get('core.create')) {
+ if ($this->canDo->get('core.edit')) {
+ ToolbarHelper::apply('menu.apply');
+ }
+
+ $toolbarButtons[] = ['save', 'menu.save'];
+ }
+
+ // If user can edit, can save the item.
+ if (!$isNew && $this->canDo->get('core.edit')) {
+ ToolbarHelper::apply('menu.apply');
+
+ $toolbarButtons[] = ['save', 'menu.save'];
+ }
+
+ // If the user can create new items, allow them to see Save & New
+ if ($this->canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'menu.save2new'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if ($isNew) {
+ ToolbarHelper::cancel('menu.cancel');
+ } else {
+ ToolbarHelper::cancel('menu.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Menus:_Edit');
+ }
}
diff --git a/administrator/components/com_menus/src/View/Menu/XmlView.php b/administrator/components/com_menus/src/View/Menu/XmlView.php
index c10e4a5a56909..1f45e4b4d6f6c 100644
--- a/administrator/components/com_menus/src/View/Menu/XmlView.php
+++ b/administrator/components/com_menus/src/View/Menu/XmlView.php
@@ -1,4 +1,5 @@
input->getCmd('menutype');
-
- if ($menutype)
- {
- $root = MenusHelper::getMenuItems($menutype, true);
- }
-
- if (!$root->hasChildren())
- {
- Log::add(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), Log::WARNING, 'jerror');
-
- $app->redirect(Route::_('index.php?option=com_menus&view=menus', false));
-
- return;
- }
-
- $this->items = $root->getChildren(true);
-
- $xml = new \SimpleXMLElement(' '
- );
-
- foreach ($this->items as $item)
- {
- $this->addXmlChild($xml, $item);
- }
-
- if (headers_sent($file, $line))
- {
- Log::add("Headers already sent at $file:$line.", Log::ERROR, 'jerror');
-
- return;
- }
-
- header('content-type: application/xml');
- header('content-disposition: attachment; filename="' . $menutype . '.xml"');
- header("Cache-Control: no-cache, must-revalidate");
- header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
-
- $dom = new \DOMDocument;
- $dom->preserveWhiteSpace = true;
- $dom->formatOutput = true;
- $dom->loadXML($xml->asXML());
-
- echo $dom->saveXML();
-
- $app->close();
- }
-
- /**
- * Add a child node to the xml
- *
- * @param \SimpleXMLElement $xml The current XML node which would become the parent to the new node
- * @param \stdClass $item The menuitem object to create the child XML node from
- *
- * @return void
- *
- * @since 3.8.0
- */
- protected function addXmlChild($xml, $item)
- {
- $node = $xml->addChild('menuitem');
-
- $node['type'] = $item->type;
-
- if ($item->title)
- {
- $node['title'] = htmlentities($item->title, ENT_XML1);
- }
-
- if ($item->link)
- {
- $node['link'] = $item->link;
- }
-
- if ($item->element)
- {
- $node['element'] = $item->element;
- }
-
- if (isset($item->class) && $item->class)
- {
- $node['class'] = htmlentities($item->class, ENT_XML1);
- }
-
- if ($item->access)
- {
- $node['access'] = $item->access;
- }
-
- if ($item->browserNav)
- {
- $node['target'] = '_blank';
- }
-
- if ($item->getParams() && $hideitems = $item->getParams()->get('hideitems'))
- {
- $item->getParams()->set('hideitems', $this->getModel('Menu')->getExtensionElementsForMenuItems($hideitems));
-
- $node->addChild('params', htmlentities((string) $item->getParams(), ENT_XML1));
- }
-
- if (isset($item->submenu))
- {
- foreach ($item->submenu as $sub)
- {
- $this->addXmlChild($node, $sub);
- }
- }
- }
+ /**
+ * @var \stdClass[]
+ *
+ * @since 3.8.0
+ */
+ protected $items;
+
+ /**
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 3.8.0
+ */
+ protected $state;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ public function display($tpl = null)
+ {
+ $app = Factory::getApplication();
+ $menutype = $app->input->getCmd('menutype');
+
+ if ($menutype) {
+ $root = MenusHelper::getMenuItems($menutype, true);
+ }
+
+ if (!$root->hasChildren()) {
+ Log::add(Text::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), Log::WARNING, 'jerror');
+
+ $app->redirect(Route::_('index.php?option=com_menus&view=menus', false));
+
+ return;
+ }
+
+ $this->items = $root->getChildren(true);
+
+ $xml = new \SimpleXMLElement(' ');
+
+ foreach ($this->items as $item) {
+ $this->addXmlChild($xml, $item);
+ }
+
+ if (headers_sent($file, $line)) {
+ Log::add("Headers already sent at $file:$line.", Log::ERROR, 'jerror');
+
+ return;
+ }
+
+ header('content-type: application/xml');
+ header('content-disposition: attachment; filename="' . $menutype . '.xml"');
+ header("Cache-Control: no-cache, must-revalidate");
+ header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+
+ $dom = new \DOMDocument();
+ $dom->preserveWhiteSpace = true;
+ $dom->formatOutput = true;
+ $dom->loadXML($xml->asXML());
+
+ echo $dom->saveXML();
+
+ $app->close();
+ }
+
+ /**
+ * Add a child node to the xml
+ *
+ * @param \SimpleXMLElement $xml The current XML node which would become the parent to the new node
+ * @param \stdClass $item The menuitem object to create the child XML node from
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ protected function addXmlChild($xml, $item)
+ {
+ $node = $xml->addChild('menuitem');
+
+ $node['type'] = $item->type;
+
+ if ($item->title) {
+ $node['title'] = htmlentities($item->title, ENT_XML1);
+ }
+
+ if ($item->link) {
+ $node['link'] = $item->link;
+ }
+
+ if ($item->element) {
+ $node['element'] = $item->element;
+ }
+
+ if (isset($item->class) && $item->class) {
+ $node['class'] = htmlentities($item->class, ENT_XML1);
+ }
+
+ if ($item->access) {
+ $node['access'] = $item->access;
+ }
+
+ if ($item->browserNav) {
+ $node['target'] = '_blank';
+ }
+
+ if ($item->getParams() && $hideitems = $item->getParams()->get('hideitems')) {
+ $item->getParams()->set('hideitems', $this->getModel('Menu')->getExtensionElementsForMenuItems($hideitems));
+
+ $node->addChild('params', htmlentities((string) $item->getParams(), ENT_XML1));
+ }
+
+ if (isset($item->submenu)) {
+ foreach ($item->submenu as $sub) {
+ $this->addXmlChild($node, $sub);
+ }
+ }
+ }
}
diff --git a/administrator/components/com_menus/src/View/Menus/HtmlView.php b/administrator/components/com_menus/src/View/Menus/HtmlView.php
index e3ca25e2e3a13..9a9a81d36257e 100644
--- a/administrator/components/com_menus/src/View/Menus/HtmlView.php
+++ b/administrator/components/com_menus/src/View/Menus/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->modules = $this->get('Modules');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
-
- if ($this->getLayout() == 'default')
- {
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_menus');
-
- ToolbarHelper::title(Text::_('COM_MENUS_VIEW_MENUS_TITLE'), 'list menumgr');
-
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::addNew('menu.add');
- }
-
- if ($canDo->get('core.delete'))
- {
- ToolbarHelper::divider();
- ToolbarHelper::deleteList('COM_MENUS_MENU_CONFIRM_DELETE', 'menus.delete', 'JTOOLBAR_DELETE');
- }
-
- if ($canDo->get('core.admin') && $this->state->get('client_id') == 1)
- {
- ToolbarHelper::custom('menu.exportXml', 'download', '', 'COM_MENUS_MENU_EXPORT_BUTTON', true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::divider();
- ToolbarHelper::preferences('com_menus');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Menus');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * List of all mod_mainmenu modules collated by menutype
+ *
+ * @var array
+ */
+ protected $modules;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->modules = $this->get('Modules');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+
+ if ($this->getLayout() == 'default') {
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_menus');
+
+ ToolbarHelper::title(Text::_('COM_MENUS_VIEW_MENUS_TITLE'), 'list menumgr');
+
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::addNew('menu.add');
+ }
+
+ if ($canDo->get('core.delete')) {
+ ToolbarHelper::divider();
+ ToolbarHelper::deleteList('COM_MENUS_MENU_CONFIRM_DELETE', 'menus.delete', 'JTOOLBAR_DELETE');
+ }
+
+ if ($canDo->get('core.admin') && $this->state->get('client_id') == 1) {
+ ToolbarHelper::custom('menu.exportXml', 'download', '', 'COM_MENUS_MENU_EXPORT_BUTTON', true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::divider();
+ ToolbarHelper::preferences('com_menus');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Menus');
+ }
}
diff --git a/administrator/components/com_menus/src/View/Menutypes/HtmlView.php b/administrator/components/com_menus/src/View/Menutypes/HtmlView.php
index 5844c40e6fb82..28ff53640b912 100644
--- a/administrator/components/com_menus/src/View/Menutypes/HtmlView.php
+++ b/administrator/components/com_menus/src/View/Menutypes/HtmlView.php
@@ -1,4 +1,5 @@
recordId = $app->input->getInt('recordId');
-
- $types = $this->get('TypeOptions');
-
- $this->addCustomTypes($types);
-
- $sortedTypes = array();
-
- foreach ($types as $name => $list)
- {
- $tmp = array();
-
- foreach ($list as $item)
- {
- $tmp[Text::_($item->title)] = $item;
- }
-
- uksort($tmp, 'strcasecmp');
- $sortedTypes[Text::_($name)] = $tmp;
- }
-
- uksort($sortedTypes, 'strcasecmp');
-
- $this->types = $sortedTypes;
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.0
- */
- protected function addToolbar()
- {
- // Add page title
- ToolbarHelper::title(Text::_('COM_MENUS'), 'list menumgr');
-
- // Get the toolbar object instance
- $bar = Toolbar::getInstance('toolbar');
-
- // Cancel
- $title = Text::_('JTOOLBAR_CANCEL');
- $dhtml = "
+ /**
+ * The menu type id
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $recordId;
+
+ /**
+ * Array of menu types
+ *
+ * @var CMSObject[]
+ *
+ * @since 3.7.0
+ */
+ protected $types;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $app = Factory::getApplication();
+ $this->recordId = $app->input->getInt('recordId');
+
+ $types = $this->get('TypeOptions');
+
+ $this->addCustomTypes($types);
+
+ $sortedTypes = array();
+
+ foreach ($types as $name => $list) {
+ $tmp = array();
+
+ foreach ($list as $item) {
+ $tmp[Text::_($item->title)] = $item;
+ }
+
+ uksort($tmp, 'strcasecmp');
+ $sortedTypes[Text::_($name)] = $tmp;
+ }
+
+ uksort($sortedTypes, 'strcasecmp');
+
+ $this->types = $sortedTypes;
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.0
+ */
+ protected function addToolbar()
+ {
+ // Add page title
+ ToolbarHelper::title(Text::_('COM_MENUS'), 'list menumgr');
+
+ // Get the toolbar object instance
+ $bar = Toolbar::getInstance('toolbar');
+
+ // Cancel
+ $title = Text::_('JTOOLBAR_CANCEL');
+ $dhtml = "
$title ";
- $bar->appendButton('Custom', $dhtml, 'new');
- }
-
- /**
- * Method to add system link types to the link types array
- *
- * @param array $types The list of link types
- *
- * @return void
- *
- * @since 3.7.0
- */
- protected function addCustomTypes(&$types)
- {
- if (empty($types))
- {
- $types = array();
- }
-
- // Adding System Links
- $list = array();
- $o = new CMSObject;
- $o->title = 'COM_MENUS_TYPE_EXTERNAL_URL';
- $o->type = 'url';
- $o->description = 'COM_MENUS_TYPE_EXTERNAL_URL_DESC';
- $o->request = null;
- $list[] = $o;
-
- $o = new CMSObject;
- $o->title = 'COM_MENUS_TYPE_ALIAS';
- $o->type = 'alias';
- $o->description = 'COM_MENUS_TYPE_ALIAS_DESC';
- $o->request = null;
- $list[] = $o;
-
- $o = new CMSObject;
- $o->title = 'COM_MENUS_TYPE_SEPARATOR';
- $o->type = 'separator';
- $o->description = 'COM_MENUS_TYPE_SEPARATOR_DESC';
- $o->request = null;
- $list[] = $o;
-
- $o = new CMSObject;
- $o->title = 'COM_MENUS_TYPE_HEADING';
- $o->type = 'heading';
- $o->description = 'COM_MENUS_TYPE_HEADING_DESC';
- $o->request = null;
- $list[] = $o;
-
- if ($this->get('state')->get('client_id') == 1)
- {
- $o = new CMSObject;
- $o->title = 'COM_MENUS_TYPE_CONTAINER';
- $o->type = 'container';
- $o->description = 'COM_MENUS_TYPE_CONTAINER_DESC';
- $o->request = null;
- $list[] = $o;
- }
-
- $types['COM_MENUS_TYPE_SYSTEM'] = $list;
- }
+ $bar->appendButton('Custom', $dhtml, 'new');
+ }
+
+ /**
+ * Method to add system link types to the link types array
+ *
+ * @param array $types The list of link types
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ protected function addCustomTypes(&$types)
+ {
+ if (empty($types)) {
+ $types = array();
+ }
+
+ // Adding System Links
+ $list = array();
+ $o = new CMSObject();
+ $o->title = 'COM_MENUS_TYPE_EXTERNAL_URL';
+ $o->type = 'url';
+ $o->description = 'COM_MENUS_TYPE_EXTERNAL_URL_DESC';
+ $o->request = null;
+ $list[] = $o;
+
+ $o = new CMSObject();
+ $o->title = 'COM_MENUS_TYPE_ALIAS';
+ $o->type = 'alias';
+ $o->description = 'COM_MENUS_TYPE_ALIAS_DESC';
+ $o->request = null;
+ $list[] = $o;
+
+ $o = new CMSObject();
+ $o->title = 'COM_MENUS_TYPE_SEPARATOR';
+ $o->type = 'separator';
+ $o->description = 'COM_MENUS_TYPE_SEPARATOR_DESC';
+ $o->request = null;
+ $list[] = $o;
+
+ $o = new CMSObject();
+ $o->title = 'COM_MENUS_TYPE_HEADING';
+ $o->type = 'heading';
+ $o->description = 'COM_MENUS_TYPE_HEADING_DESC';
+ $o->request = null;
+ $list[] = $o;
+
+ if ($this->get('state')->get('client_id') == 1) {
+ $o = new CMSObject();
+ $o->title = 'COM_MENUS_TYPE_CONTAINER';
+ $o->type = 'container';
+ $o->description = 'COM_MENUS_TYPE_CONTAINER_DESC';
+ $o->request = null;
+ $list[] = $o;
+ }
+
+ $types['COM_MENUS_TYPE_SYSTEM'] = $list;
+ }
}
diff --git a/administrator/components/com_menus/tmpl/item/edit.php b/administrator/components/com_menus/tmpl/item/edit.php
index dc9f5991e1774..bd89bd47306ef 100644
--- a/administrator/components/com_menus/tmpl/item/edit.php
+++ b/administrator/components/com_menus/tmpl/item/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_menus.admin-item-edit');
+ ->useScript('form.validate')
+ ->useScript('com_menus.admin-item-edit');
$assoc = Associations::isEnabled();
$input = Factory::getApplication()->input;
@@ -40,166 +41,158 @@
$lang = Factory::getLanguage()->getTag();
// Load mod_menu.ini file when client is administrator
-if ($clientId === 1)
-{
- Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
+if ($clientId === 1) {
+ Factory::getLanguage()->load('mod_menu', JPATH_ADMINISTRATOR);
}
?>
diff --git a/administrator/components/com_menus/tmpl/item/edit_container.php b/administrator/components/com_menus/tmpl/item/edit_container.php
index 09d6ef94bec7f..53f7d07060971 100644
--- a/administrator/components/com_menus/tmpl/item/edit_container.php
+++ b/administrator/components/com_menus/tmpl/item/edit_container.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
@@ -18,92 +20,86 @@
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('joomla.treeselectmenu')
- ->useStyle('com_menus.admin-item-edit-container')
- ->useScript('com_menus.admin-item-edit-container');
+ ->useStyle('com_menus.admin-item-edit-container')
+ ->useScript('com_menus.admin-item-edit-container');
?>
diff --git a/administrator/components/com_menus/tmpl/item/edit_modules.php b/administrator/components/com_menus/tmpl/item/edit_modules.php
index 2088aa583a49b..d8f7ae83481a8 100644
--- a/administrator/components/com_menus/tmpl/item/edit_modules.php
+++ b/administrator/components/com_menus/tmpl/item/edit_modules.php
@@ -1,4 +1,5 @@
levels as $key => $value)
-{
- $allLevels[$value->id] = $value->title;
+foreach ($this->levels as $key => $value) {
+ $allLevels[$value->id] = $value->title;
}
$this->document->addScriptOptions('menus-edit-modules', ['viewLevels' => $allLevels, 'itemId' => $this->item->id]);
@@ -23,26 +23,26 @@
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useStyle('com_menus.admin-item-edit-modules')
- ->useScript('com_menus.admin-item-edit-modules');
+ ->useScript('com_menus.admin-item-edit-modules');
// Set up the bootstrap modal that will be used for all module editors
echo HTMLHelper::_(
- 'bootstrap.renderModal',
- 'moduleEditModal',
- array(
- 'title' => Text::_('COM_MENUS_EDIT_MODULE_SETTINGS'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'bodyHeight' => '70',
- 'modalWidth' => '80',
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
+ 'bootstrap.renderModal',
+ 'moduleEditModal',
+ array(
+ 'title' => Text::_('COM_MENUS_EDIT_MODULE_SETTINGS'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'bodyHeight' => '70',
+ 'modalWidth' => '80',
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
);
?>
@@ -53,97 +53,97 @@
echo LayoutHelper::render('joomla.menu.edit_modules', $this); ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- modules as $i => &$module) : ?>
- menuid)) : ?>
- except || $module->menuid < 0) : ?>
-
-
-
-
-
-
-
- published) : ?>
-
-
-
-
-
-
-
- escape($module->title); ?>
-
-
- escape($module->access_title); ?>
-
-
- escape($module->position); ?>
-
-
-
- published) : ?>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ modules as $i => &$module) : ?>
+ menuid)) : ?>
+ except || $module->menuid < 0) : ?>
+
+
+
+
+
+
+
+ published) : ?>
+
+
+
+
+
+
+
+ escape($module->title); ?>
+
+
+ escape($module->access_title); ?>
+
+
+ escape($module->position); ?>
+
+
+
+ published) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_menus/tmpl/item/modal.php b/administrator/components/com_menus/tmpl/item/modal.php
index 2a62e7aa4c3a1..f5c2253fc4208 100644
--- a/administrator/components/com_menus/tmpl/item/modal.php
+++ b/administrator/components/com_menus/tmpl/item/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_menus/tmpl/items/default.php b/administrator/components/com_menus/tmpl/items/default.php
index 7b8a79ba495f3..33900dd7c4346 100644
--- a/administrator/components/com_menus/tmpl/items/default.php
+++ b/administrator/components/com_menus/tmpl/items/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$app = Factory::getApplication();
@@ -32,10 +33,9 @@
$saveOrder = ($listOrder == 'a.lft' && strtolower($listDirn) == 'asc');
$menuType = (string) $app->getUserState('com_menus.items.menutype', '');
-if ($saveOrder && $menuType && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_menus&task=items.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && $menuType && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_menus&task=items.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$assoc = Associations::isEnabled() && $this->state->get('filter.client_id') == 0;
@@ -43,248 +43,241 @@
?>
diff --git a/administrator/components/com_menus/tmpl/items/default_batch_body.php b/administrator/components/com_menus/tmpl/items/default_batch_body.php
index acb8b8e97b71f..8a0c950eab285 100644
--- a/administrator/components/com_menus/tmpl/items/default_batch_body.php
+++ b/administrator/components/com_menus/tmpl/items/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -15,73 +17,72 @@
use Joomla\CMS\Layout\LayoutHelper;
$options = [
- HTMLHelper::_('select.option', 'c', Text::_('JLIB_HTML_BATCH_COPY')),
- HTMLHelper::_('select.option', 'm', Text::_('JLIB_HTML_BATCH_MOVE'))
+ HTMLHelper::_('select.option', 'c', Text::_('JLIB_HTML_BATCH_COPY')),
+ HTMLHelper::_('select.option', 'm', Text::_('JLIB_HTML_BATCH_MOVE'))
];
$published = (int) $this->state->get('filter.published');
$clientId = (int) $this->state->get('filter.client_id');
$menuType = Factory::getApplication()->getUserState('com_menus.items.menutype', '');
-if ($clientId == 1)
-{
- /** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = $this->document->getWebAssetManager();
- $wa->useScript('com_menus.batch-body');
- $wa->useScript('joomla.batch-copymove');
+if ($clientId == 1) {
+ /** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = $this->document->getWebAssetManager();
+ $wa->useScript('com_menus.batch-body');
+ $wa->useScript('joomla.batch-copymove');
}
?>
-
-
-
-
-
- = 0) : ?>
-
-
-
-
+
+
+
+
+
+
+
diff --git a/administrator/components/com_menus/tmpl/items/default_batch_footer.php b/administrator/components/com_menus/tmpl/items/default_batch_footer.php
index 6ce0e9df6c672..4f2df096275cf 100644
--- a/administrator/components/com_menus/tmpl/items/default_batch_footer.php
+++ b/administrator/components/com_menus/tmpl/items/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -16,10 +18,10 @@
$menuType = Factory::getApplication()->getUserState('com_menus.items.menutype', '');
?>
-
+
-= 0 && $clientId == 1)): ?>
-
-
-
+= 0 && $clientId == 1)) : ?>
+
+
+
diff --git a/administrator/components/com_menus/tmpl/items/modal.php b/administrator/components/com_menus/tmpl/items/modal.php
index f4c6afef18278..e592075877504 100644
--- a/administrator/components/com_menus/tmpl/items/modal.php
+++ b/administrator/components/com_menus/tmpl/items/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if ($app->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
@@ -35,162 +35,155 @@
$link = 'index.php?option=com_menus&view=items&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
$multilang = Multilanguage::isEnabled();
-if (!empty($editor))
-{
- // This view is used also in com_menus. Load the xtd script only if the editor is set!
- $this->document->addScriptOptions('xtd-menus', array('editor' => $editor));
- $onclick = "jSelectMenuItem";
- $link = 'index.php?option=com_menus&view=items&layout=modal&tmpl=component&editor=' . $editor . '&' . Session::getFormToken() . '=1';
+if (!empty($editor)) {
+ // This view is used also in com_menus. Load the xtd script only if the editor is set!
+ $this->document->addScriptOptions('xtd-menus', array('editor' => $editor));
+ $onclick = "jSelectMenuItem";
+ $link = 'index.php?option=com_menus&view=items&layout=modal&tmpl=component&editor=' . $editor . '&' . Session::getFormToken() . '=1';
}
?>
diff --git a/administrator/components/com_menus/tmpl/menu/edit.php b/administrator/components/com_menus/tmpl/menu/edit.php
index 7959fda2742d5..186b659dce12b 100644
--- a/administrator/components/com_menus/tmpl/menu/edit.php
+++ b/administrator/components/com_menus/tmpl/menu/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('core')
- ->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('keepalive')
+ ->useScript('form.validate');
Text::script('ERROR');
?>
diff --git a/administrator/components/com_menus/tmpl/menus/default.php b/administrator/components/com_menus/tmpl/menus/default.php
index 29d4da506d5da..09cc238824ea0 100644
--- a/administrator/components/com_menus/tmpl/menus/default.php
+++ b/administrator/components/com_menus/tmpl/menus/default.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
@@ -18,8 +20,8 @@
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect')
- ->useScript('com_menus.admin-menus');
+ ->useScript('multiselect')
+ ->useScript('com_menus.admin-menus');
$uri = Uri::getInstance();
$return = base64_encode($uri);
@@ -29,242 +31,240 @@
$modMenuId = (int) $this->get('ModMenuId');
$itemIds = [];
-foreach ($this->items as $item)
-{
- if ($user->authorise('core.edit', 'com_menus'))
- {
- $itemIds[] = $item->id;
- }
+foreach ($this->items as $item) {
+ if ($user->authorise('core.edit', 'com_menus')) {
+ $itemIds[] = $item->id;
+ }
}
$this->document->addScriptOptions('menus-default', ['items' => $itemIds]);
?>
diff --git a/administrator/components/com_menus/tmpl/menutypes/default.php b/administrator/components/com_menus/tmpl/menutypes/default.php
index 9a5c7efeae1b1..a0a32a9d6f658 100644
--- a/administrator/components/com_menus/tmpl/menutypes/default.php
+++ b/administrator/components/com_menus/tmpl/menutypes/default.php
@@ -1,4 +1,5 @@
'slide1')); ?>
-
- types as $name => $list) : ?>
-
-
-
-
+
+ types as $name => $list) : ?>
+
+
+
+
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Messages'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Messages'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Messages'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Messages'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MessagesComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MessagesComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_messages/src/Controller/ConfigController.php b/administrator/components/com_messages/src/Controller/ConfigController.php
index cf76dd1ddb86d..37d5b9097b388 100644
--- a/administrator/components/com_messages/src/Controller/ConfigController.php
+++ b/administrator/components/com_messages/src/Controller/ConfigController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $model = $this->getModel('Config');
- $data = $this->input->post->get('jform', array(), 'array');
-
- // Validate the posted data.
- $form = $model->getForm();
-
- if (!$form)
- {
- throw new \Exception($model->getError(), 500);
- }
-
- $data = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($data === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $this->app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Redirect back to the main list.
- $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
-
- return false;
- }
-
- // Attempt to save the data.
- if (!$model->save($data))
- {
- // Redirect back to the main list.
- $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED', $model->getError()), 'warning');
- $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
-
- return false;
- }
-
- // Redirect to the list screen.
- $this->setMessage(Text::_('COM_MESSAGES_CONFIG_SAVED'));
- $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
-
- return true;
- }
-
- /**
- * Cancel operation.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function cancel()
- {
- $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
- }
+ /**
+ * Method to save a record.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $model = $this->getModel('Config');
+ $data = $this->input->post->get('jform', array(), 'array');
+
+ // Validate the posted data.
+ $form = $model->getForm();
+
+ if (!$form) {
+ throw new \Exception($model->getError(), 500);
+ }
+
+ $data = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($data === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $this->app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Redirect back to the main list.
+ $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
+
+ return false;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($data)) {
+ // Redirect back to the main list.
+ $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED', $model->getError()), 'warning');
+ $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
+
+ return false;
+ }
+
+ // Redirect to the list screen.
+ $this->setMessage(Text::_('COM_MESSAGES_CONFIG_SAVED'));
+ $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
+
+ return true;
+ }
+
+ /**
+ * Cancel operation.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function cancel()
+ {
+ $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
+ }
}
diff --git a/administrator/components/com_messages/src/Controller/DisplayController.php b/administrator/components/com_messages/src/Controller/DisplayController.php
index f7115d296aa79..55c8662bf06a8 100644
--- a/administrator/components/com_messages/src/Controller/DisplayController.php
+++ b/administrator/components/com_messages/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'messages');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if ($view == 'message' && $layout == 'edit' && !$this->checkEditId('com_messages.edit.message', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
-
- return false;
- }
-
- return parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'messages';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached.
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $view = $this->input->get('view', 'messages');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if ($view == 'message' && $layout == 'edit' && !$this->checkEditId('com_messages.edit.message', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_messages&view=messages', false));
+
+ return false;
+ }
+
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_messages/src/Controller/MessageController.php b/administrator/components/com_messages/src/Controller/MessageController.php
index 44b8361751cf1..f8b0349cb4fbe 100644
--- a/administrator/components/com_messages/src/Controller/MessageController.php
+++ b/administrator/components/com_messages/src/Controller/MessageController.php
@@ -1,4 +1,5 @@
input->getInt('reply_id'))
- {
- $this->setRedirect('index.php?option=com_messages&view=message&layout=edit&reply_id=' . $replyId);
- }
- else
- {
- $this->setMessage(Text::_('COM_MESSAGES_INVALID_REPLY_ID'));
- $this->setRedirect('index.php?option=com_messages&view=messages');
- }
- }
+ /**
+ * Reply to an existing message.
+ *
+ * This is a simple redirect to the compose form.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function reply()
+ {
+ if ($replyId = $this->input->getInt('reply_id')) {
+ $this->setRedirect('index.php?option=com_messages&view=message&layout=edit&reply_id=' . $replyId);
+ } else {
+ $this->setMessage(Text::_('COM_MESSAGES_INVALID_REPLY_ID'));
+ $this->setRedirect('index.php?option=com_messages&view=messages');
+ }
+ }
}
diff --git a/administrator/components/com_messages/src/Controller/MessagesController.php b/administrator/components/com_messages/src/Controller/MessagesController.php
index 0d160b86d827f..38a2bd3d52c58 100644
--- a/administrator/components/com_messages/src/Controller/MessagesController.php
+++ b/administrator/components/com_messages/src/Controller/MessagesController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Message', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_messages/src/Extension/MessagesComponent.php b/administrator/components/com_messages/src/Extension/MessagesComponent.php
index ab09ea87e936d..959f8607c2dd8 100644
--- a/administrator/components/com_messages/src/Extension/MessagesComponent.php
+++ b/administrator/components/com_messages/src/Extension/MessagesComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('messages', new Messages);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('messages', new Messages());
+ }
}
diff --git a/administrator/components/com_messages/src/Field/MessageStatesField.php b/administrator/components/com_messages/src/Field/MessageStatesField.php
index 5e5dbfdc15fc6..8dac33b2bfa12 100644
--- a/administrator/components/com_messages/src/Field/MessageStatesField.php
+++ b/administrator/components/com_messages/src/Field/MessageStatesField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select('id')
- ->from('#__usergroups');
- $db->setQuery($query);
+ /**
+ * Method to get the filtering groups (null means no filtering)
+ *
+ * @return array|null array of filtering groups or null.
+ *
+ * @since 1.6
+ */
+ protected function getGroups()
+ {
+ // Compute usergroups
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('id')
+ ->from('#__usergroups');
+ $db->setQuery($query);
- try
- {
- $groups = $db->loadColumn();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'notice');
+ try {
+ $groups = $db->loadColumn();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'notice');
- return null;
- }
+ return null;
+ }
- foreach ($groups as $i => $group)
- {
- if (Access::checkGroup($group, 'core.admin'))
- {
- continue;
- }
+ foreach ($groups as $i => $group) {
+ if (Access::checkGroup($group, 'core.admin')) {
+ continue;
+ }
- if (!Access::checkGroup($group, 'core.manage', 'com_messages'))
- {
- unset($groups[$i]);
- continue;
- }
+ if (!Access::checkGroup($group, 'core.manage', 'com_messages')) {
+ unset($groups[$i]);
+ continue;
+ }
- if (!Access::checkGroup($group, 'core.login.admin'))
- {
- unset($groups[$i]);
- }
- }
+ if (!Access::checkGroup($group, 'core.login.admin')) {
+ unset($groups[$i]);
+ }
+ }
- return array_values($groups);
- }
+ return array_values($groups);
+ }
- /**
- * Method to get the users to exclude from the list of users
- *
- * @return array|null array of users to exclude or null to to not exclude them
- *
- * @since 1.6
- */
- protected function getExcluded()
- {
- return array(Factory::getUser()->id);
- }
+ /**
+ * Method to get the users to exclude from the list of users
+ *
+ * @return array|null array of users to exclude or null to to not exclude them
+ *
+ * @since 1.6
+ */
+ protected function getExcluded()
+ {
+ return array(Factory::getUser()->id);
+ }
}
diff --git a/administrator/components/com_messages/src/Helper/MessagesHelper.php b/administrator/components/com_messages/src/Helper/MessagesHelper.php
index 28598ba737f4b..c9750020d417f 100644
--- a/administrator/components/com_messages/src/Helper/MessagesHelper.php
+++ b/administrator/components/com_messages/src/Helper/MessagesHelper.php
@@ -1,4 +1,5 @@
setState('user.id', $user->get('id'));
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_messages');
- $this->setState('params', $params);
- }
-
- /**
- * Method to get a single record.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function &getItem()
- {
- $item = new CMSObject;
- $userid = (int) $this->getState('user.id');
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('cfg_name'),
- $db->quoteName('cfg_value'),
- ]
- )
- ->from($db->quoteName('#__messages_cfg'))
- ->where($db->quoteName('user_id') . ' = :userid')
- ->bind(':userid', $userid, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $rows = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- foreach ($rows as $row)
- {
- $item->set($row->cfg_name, $row->cfg_value);
- }
-
- $this->preprocessData('com_messages.config', $item);
-
- return $item;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_messages.config', 'config', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $db = $this->getDatabase();
-
- if ($userId = (int) $this->getState('user.id'))
- {
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__messages_cfg'))
- ->where($db->quoteName('user_id') . ' = :userid')
- ->bind(':userid', $userId, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (count($data))
- {
- $query = $db->getQuery(true)
- ->insert($db->quoteName('#__messages_cfg'))
- ->columns(
- [
- $db->quoteName('user_id'),
- $db->quoteName('cfg_name'),
- $db->quoteName('cfg_value'),
- ]
- );
-
- foreach ($data as $k => $v)
- {
- $query->values(
- implode(
- ',',
- $query->bindArray(
- [$userId , $k, $v],
- [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
- )
- )
- );
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- return true;
- }
- else
- {
- $this->setError('COM_MESSAGES_ERR_INVALID_USER');
-
- return false;
- }
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $user = Factory::getUser();
+
+ $this->setState('user.id', $user->get('id'));
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_messages');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function &getItem()
+ {
+ $item = new CMSObject();
+ $userid = (int) $this->getState('user.id');
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('cfg_name'),
+ $db->quoteName('cfg_value'),
+ ]
+ )
+ ->from($db->quoteName('#__messages_cfg'))
+ ->where($db->quoteName('user_id') . ' = :userid')
+ ->bind(':userid', $userid, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $rows = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ foreach ($rows as $row) {
+ $item->set($row->cfg_name, $row->cfg_value);
+ }
+
+ $this->preprocessData('com_messages.config', $item);
+
+ return $item;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_messages.config', 'config', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $db = $this->getDatabase();
+
+ if ($userId = (int) $this->getState('user.id')) {
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__messages_cfg'))
+ ->where($db->quoteName('user_id') . ' = :userid')
+ ->bind(':userid', $userId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (count($data)) {
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__messages_cfg'))
+ ->columns(
+ [
+ $db->quoteName('user_id'),
+ $db->quoteName('cfg_name'),
+ $db->quoteName('cfg_value'),
+ ]
+ );
+
+ foreach ($data as $k => $v) {
+ $query->values(
+ implode(
+ ',',
+ $query->bindArray(
+ [$userId , $k, $v],
+ [ParameterType::INTEGER, ParameterType::STRING, ParameterType::STRING]
+ )
+ )
+ );
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ $this->setError('COM_MESSAGES_ERR_INVALID_USER');
+
+ return false;
+ }
+ }
}
diff --git a/administrator/components/com_messages/src/Model/MessageModel.php b/administrator/components/com_messages/src/Model/MessageModel.php
index 3864871baeff5..bc480fda65e7c 100644
--- a/administrator/components/com_messages/src/Model/MessageModel.php
+++ b/administrator/components/com_messages/src/Model/MessageModel.php
@@ -1,4 +1,5 @@
input;
-
- $user = Factory::getUser();
- $this->setState('user.id', $user->get('id'));
-
- $messageId = (int) $input->getInt('message_id');
- $this->setState('message.id', $messageId);
-
- $replyId = (int) $input->getInt('reply_id');
- $this->setState('reply.id', $replyId);
- }
-
- /**
- * Check that recipient user is the one trying to delete and then call parent delete method
- *
- * @param array &$pks An array of record primary keys.
- *
- * @return boolean True if successful, false if an error occurs.
- *
- * @since 3.1
- */
- public function delete(&$pks)
- {
- $pks = (array) $pks;
- $table = $this->getTable();
- $user = Factory::getUser();
-
- // Iterate the items to delete each one.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- if ($table->user_id_to != $user->id)
- {
- // Prune items that you can't change.
- unset($pks[$i]);
-
- try
- {
- Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'warning');
- }
-
- return false;
- }
- }
- else
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- return parent::delete($pks);
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- if (!isset($this->item))
- {
- if ($this->item = parent::getItem($pk))
- {
- // Invalid message_id returns 0
- if ($this->item->user_id_to === '0')
- {
- $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
-
- return false;
- }
-
- // Prime required properties.
- if (empty($this->item->message_id))
- {
- // Prepare data for a new record.
- if ($replyId = (int) $this->getState('reply.id'))
- {
- // If replying to a message, preload some data.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['subject', 'user_id_from', 'user_id_to']))
- ->from($db->quoteName('#__messages'))
- ->where($db->quoteName('message_id') . ' = :messageid')
- ->bind(':messageid', $replyId, ParameterType::INTEGER);
-
- try
- {
- $message = $db->setQuery($query)->loadObject();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (!$message || $message->user_id_to != Factory::getUser()->id)
- {
- $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
-
- return false;
- }
-
- $this->item->set('user_id_to', $message->user_id_from);
- $re = Text::_('COM_MESSAGES_RE');
-
- if (stripos($message->subject, $re) !== 0)
- {
- $this->item->set('subject', $re . ' ' . $message->subject);
- }
- }
- }
- elseif ($this->item->user_id_to != Factory::getUser()->id)
- {
- $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
-
- return false;
- }
- else
- {
- // Mark message read
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__messages'))
- ->set($db->quoteName('state') . ' = 1')
- ->where($db->quoteName('message_id') . ' = :messageid')
- ->bind(':messageid', $this->item->message_id, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
- }
- }
-
- // Get the user name for an existing message.
- if ($this->item->user_id_from && $fromUser = new User($this->item->user_id_from))
- {
- $this->item->set('from_user_name', $fromUser->name);
- }
- }
-
- return $this->item;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_messages.message', 'message', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_messages.edit.message.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_messages.message', $data);
-
- return $data;
- }
-
- /**
- * Checks that the current user matches the message recipient and calls the parent publish method
- *
- * @param array &$pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 3.1
- */
- public function publish(&$pks, $value = 1)
- {
- $user = Factory::getUser();
- $table = $this->getTable();
- $pks = (array) $pks;
-
- // Check that the recipient matches the current user
- foreach ($pks as $i => $pk)
- {
- $table->reset();
-
- if ($table->load($pk))
- {
- if ($table->user_id_to != $user->id)
- {
- // Prune items that you can't change.
- unset($pks[$i]);
-
- try
- {
- Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'warning');
- }
-
- return false;
- }
- }
- }
-
- return parent::publish($pks, $value);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $table = $this->getTable();
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Assign empty values.
- if (empty($table->user_id_from))
- {
- $table->user_id_from = Factory::getUser()->get('id');
- }
-
- if ((int) $table->date_time == 0)
- {
- $table->date_time = Factory::getDate()->toSql();
- }
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Load the user details (already valid from table check).
- $toUser = User::getInstance($table->user_id_to);
-
- // Check if recipient can access com_messages.
- if (!$toUser->authorise('core.login.admin') || !$toUser->authorise('core.manage', 'com_messages'))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_RECIPIENT_NOT_AUTHORISED'));
-
- return false;
- }
-
- // Load the recipient user configuration.
- $model = $this->bootComponent('com_messages')
- ->getMVCFactory()->createModel('Config', 'Administrator', ['ignore_request' => true]);
- $model->setState('user.id', $table->user_id_to);
- $config = $model->getItem();
-
- if (empty($config))
- {
- $this->setError($model->getError());
-
- return false;
- }
-
- if ($config->get('lock', false))
- {
- $this->setError(Text::_('COM_MESSAGES_ERR_SEND_FAILED'));
-
- return false;
- }
-
- // Store the data.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $key = $table->getKeyName();
-
- if (isset($table->$key))
- {
- $this->setState($this->getName() . '.id', $table->$key);
- }
-
- if ($config->get('mail_on_new', true))
- {
- $fromUser = User::getInstance($table->user_id_from);
- $debug = Factory::getApplication()->get('debug_lang');
- $default_language = ComponentHelper::getParams('com_languages')->get('administrator');
- $lang = Language::getInstance($toUser->getParam('admin_language', $default_language), $debug);
- $lang->load('com_messages', JPATH_ADMINISTRATOR);
-
- // Build the email subject and message
- $app = Factory::getApplication();
- $linkMode = $app->get('force_ssl', 0) >= 1 ? Route::TLS_FORCE : Route::TLS_IGNORE;
- $sitename = $app->get('sitename');
- $fromName = $fromUser->get('name');
- $siteURL = Route::link(
- 'administrator',
- 'index.php?option=com_messages&view=message&message_id=' . $table->message_id,
- false,
- $linkMode,
- true
- );
- $subject = html_entity_decode($table->subject, ENT_COMPAT, 'UTF-8');
- $message = strip_tags(html_entity_decode($table->message, ENT_COMPAT, 'UTF-8'));
-
- // Send the email
- $mailer = new MailTemplate('com_messages.new_message', $lang->getTag());
- $data = [
- 'subject' => $subject,
- 'message' => $message,
- 'fromname' => $fromName,
- 'sitename' => $sitename,
- 'siteurl' => $siteURL,
- 'fromemail' => $fromUser->email,
- 'toname' => $toUser->name,
- 'toemail' => $toUser->email
- ];
- $mailer->addTemplateData($data);
- $mailer->setReplyTo($fromUser->email, $fromUser->name);
- $mailer->addRecipient($toUser->email, $toUser->name);
-
- try
- {
- $mailer->send();
- }
- catch (MailDisabledException | phpMailerException $exception)
- {
- try
- {
- Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
-
- $this->setError(Text::_('COM_MESSAGES_ERROR_MAIL_FAILED'));
-
- return false;
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
-
- $this->setError(Text::_('COM_MESSAGES_ERROR_MAIL_FAILED'));
-
- return false;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Sends a message to the site's super users
- *
- * @param string $subject The message subject
- * @param string $message The message
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function notifySuperUsers($subject, $message, $fromUser = null)
- {
- $db = $this->getDatabase();
-
- try
- {
- /** @var Asset $table */
- $table = Table::getInstance('Asset');
- $rootId = $table->getRootId();
-
- /** @var Rule[] $rules */
- $rules = Access::getAssetRules($rootId)->getData();
- $rawGroups = $rules['core.admin']->getData();
-
- if (empty($rawGroups))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_MISSING_ROOT_ASSET_GROUPS'));
-
- return false;
- }
-
- $groups = array();
-
- foreach ($rawGroups as $g => $enabled)
- {
- if ($enabled)
- {
- $groups[] = $g;
- }
- }
-
- if (empty($groups))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_NO_GROUPS_SET_AS_SUPER_USER'));
-
- return false;
- }
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('map.user_id'))
- ->from($db->quoteName('#__user_usergroup_map', 'map'))
- ->join('LEFT',
- $db->quoteName('#__users', 'u'),
- $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id')
- )
- ->whereIn($db->quoteName('map.group_id'), $groups)
- ->where($db->quoteName('u.block') . ' = 0')
- ->where($db->quoteName('u.sendEmail') . ' = 1');
-
- $userIDs = $db->setQuery($query)->loadColumn(0);
-
- if (empty($userIDs))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_NO_USERS_SET_AS_SUPER_USER'));
-
- return false;
- }
-
- foreach ($userIDs as $id)
- {
- /*
- * All messages must have a valid from user, we have use cases where an unauthenticated user may trigger this
- * so we will set the from user as the to user
- */
- $data = [
- 'user_id_from' => $id,
- 'user_id_to' => $id,
- 'subject' => $subject,
- 'message' => $message,
- ];
-
- if (!$this->save($data))
- {
- return false;
- }
- }
-
- return true;
- }
- catch (\Exception $exception)
- {
- $this->setError($exception->getMessage());
-
- return false;
- }
- }
+ /**
+ * Message
+ *
+ * @var \stdClass
+ */
+ protected $item;
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $input = Factory::getApplication()->input;
+
+ $user = Factory::getUser();
+ $this->setState('user.id', $user->get('id'));
+
+ $messageId = (int) $input->getInt('message_id');
+ $this->setState('message.id', $messageId);
+
+ $replyId = (int) $input->getInt('reply_id');
+ $this->setState('reply.id', $replyId);
+ }
+
+ /**
+ * Check that recipient user is the one trying to delete and then call parent delete method
+ *
+ * @param array &$pks An array of record primary keys.
+ *
+ * @return boolean True if successful, false if an error occurs.
+ *
+ * @since 3.1
+ */
+ public function delete(&$pks)
+ {
+ $pks = (array) $pks;
+ $table = $this->getTable();
+ $user = Factory::getUser();
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ if ($table->user_id_to != $user->id) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+
+ try {
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), 'warning');
+ }
+
+ return false;
+ }
+ } else {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ return parent::delete($pks);
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ if (!isset($this->item)) {
+ if ($this->item = parent::getItem($pk)) {
+ // Invalid message_id returns 0
+ if ($this->item->user_id_to === '0') {
+ $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
+
+ return false;
+ }
+
+ // Prime required properties.
+ if (empty($this->item->message_id)) {
+ // Prepare data for a new record.
+ if ($replyId = (int) $this->getState('reply.id')) {
+ // If replying to a message, preload some data.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['subject', 'user_id_from', 'user_id_to']))
+ ->from($db->quoteName('#__messages'))
+ ->where($db->quoteName('message_id') . ' = :messageid')
+ ->bind(':messageid', $replyId, ParameterType::INTEGER);
+
+ try {
+ $message = $db->setQuery($query)->loadObject();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (!$message || $message->user_id_to != Factory::getUser()->id) {
+ $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
+
+ return false;
+ }
+
+ $this->item->set('user_id_to', $message->user_id_from);
+ $re = Text::_('COM_MESSAGES_RE');
+
+ if (stripos($message->subject, $re) !== 0) {
+ $this->item->set('subject', $re . ' ' . $message->subject);
+ }
+ }
+ } elseif ($this->item->user_id_to != Factory::getUser()->id) {
+ $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
+
+ return false;
+ } else {
+ // Mark message read
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__messages'))
+ ->set($db->quoteName('state') . ' = 1')
+ ->where($db->quoteName('message_id') . ' = :messageid')
+ ->bind(':messageid', $this->item->message_id, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+ }
+ }
+
+ // Get the user name for an existing message.
+ if ($this->item->user_id_from && $fromUser = new User($this->item->user_id_from)) {
+ $this->item->set('from_user_name', $fromUser->name);
+ }
+ }
+
+ return $this->item;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_messages.message', 'message', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_messages.edit.message.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_messages.message', $data);
+
+ return $data;
+ }
+
+ /**
+ * Checks that the current user matches the message recipient and calls the parent publish method
+ *
+ * @param array &$pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.1
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ // Check that the recipient matches the current user
+ foreach ($pks as $i => $pk) {
+ $table->reset();
+
+ if ($table->load($pk)) {
+ if ($table->user_id_to != $user->id) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+
+ try {
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'warning');
+ }
+
+ return false;
+ }
+ }
+ }
+
+ return parent::publish($pks, $value);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Assign empty values.
+ if (empty($table->user_id_from)) {
+ $table->user_id_from = Factory::getUser()->get('id');
+ }
+
+ if ((int) $table->date_time == 0) {
+ $table->date_time = Factory::getDate()->toSql();
+ }
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Load the user details (already valid from table check).
+ $toUser = User::getInstance($table->user_id_to);
+
+ // Check if recipient can access com_messages.
+ if (!$toUser->authorise('core.login.admin') || !$toUser->authorise('core.manage', 'com_messages')) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_RECIPIENT_NOT_AUTHORISED'));
+
+ return false;
+ }
+
+ // Load the recipient user configuration.
+ $model = $this->bootComponent('com_messages')
+ ->getMVCFactory()->createModel('Config', 'Administrator', ['ignore_request' => true]);
+ $model->setState('user.id', $table->user_id_to);
+ $config = $model->getItem();
+
+ if (empty($config)) {
+ $this->setError($model->getError());
+
+ return false;
+ }
+
+ if ($config->get('lock', false)) {
+ $this->setError(Text::_('COM_MESSAGES_ERR_SEND_FAILED'));
+
+ return false;
+ }
+
+ // Store the data.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $key = $table->getKeyName();
+
+ if (isset($table->$key)) {
+ $this->setState($this->getName() . '.id', $table->$key);
+ }
+
+ if ($config->get('mail_on_new', true)) {
+ $fromUser = User::getInstance($table->user_id_from);
+ $debug = Factory::getApplication()->get('debug_lang');
+ $default_language = ComponentHelper::getParams('com_languages')->get('administrator');
+ $lang = Language::getInstance($toUser->getParam('admin_language', $default_language), $debug);
+ $lang->load('com_messages', JPATH_ADMINISTRATOR);
+
+ // Build the email subject and message
+ $app = Factory::getApplication();
+ $linkMode = $app->get('force_ssl', 0) >= 1 ? Route::TLS_FORCE : Route::TLS_IGNORE;
+ $sitename = $app->get('sitename');
+ $fromName = $fromUser->get('name');
+ $siteURL = Route::link(
+ 'administrator',
+ 'index.php?option=com_messages&view=message&message_id=' . $table->message_id,
+ false,
+ $linkMode,
+ true
+ );
+ $subject = html_entity_decode($table->subject, ENT_COMPAT, 'UTF-8');
+ $message = strip_tags(html_entity_decode($table->message, ENT_COMPAT, 'UTF-8'));
+
+ // Send the email
+ $mailer = new MailTemplate('com_messages.new_message', $lang->getTag());
+ $data = [
+ 'subject' => $subject,
+ 'message' => $message,
+ 'fromname' => $fromName,
+ 'sitename' => $sitename,
+ 'siteurl' => $siteURL,
+ 'fromemail' => $fromUser->email,
+ 'toname' => $toUser->name,
+ 'toemail' => $toUser->email
+ ];
+ $mailer->addTemplateData($data);
+ $mailer->setReplyTo($fromUser->email, $fromUser->name);
+ $mailer->addRecipient($toUser->email, $toUser->name);
+
+ try {
+ $mailer->send();
+ } catch (MailDisabledException | phpMailerException $exception) {
+ try {
+ Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
+
+ $this->setError(Text::_('COM_MESSAGES_ERROR_MAIL_FAILED'));
+
+ return false;
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
+
+ $this->setError(Text::_('COM_MESSAGES_ERROR_MAIL_FAILED'));
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Sends a message to the site's super users
+ *
+ * @param string $subject The message subject
+ * @param string $message The message
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function notifySuperUsers($subject, $message, $fromUser = null)
+ {
+ $db = $this->getDatabase();
+
+ try {
+ /** @var Asset $table */
+ $table = Table::getInstance('Asset');
+ $rootId = $table->getRootId();
+
+ /** @var Rule[] $rules */
+ $rules = Access::getAssetRules($rootId)->getData();
+ $rawGroups = $rules['core.admin']->getData();
+
+ if (empty($rawGroups)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_MISSING_ROOT_ASSET_GROUPS'));
+
+ return false;
+ }
+
+ $groups = array();
+
+ foreach ($rawGroups as $g => $enabled) {
+ if ($enabled) {
+ $groups[] = $g;
+ }
+ }
+
+ if (empty($groups)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_NO_GROUPS_SET_AS_SUPER_USER'));
+
+ return false;
+ }
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('map.user_id'))
+ ->from($db->quoteName('#__user_usergroup_map', 'map'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__users', 'u'),
+ $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id')
+ )
+ ->whereIn($db->quoteName('map.group_id'), $groups)
+ ->where($db->quoteName('u.block') . ' = 0')
+ ->where($db->quoteName('u.sendEmail') . ' = 1');
+
+ $userIDs = $db->setQuery($query)->loadColumn(0);
+
+ if (empty($userIDs)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_NO_USERS_SET_AS_SUPER_USER'));
+
+ return false;
+ }
+
+ foreach ($userIDs as $id) {
+ /*
+ * All messages must have a valid from user, we have use cases where an unauthenticated user may trigger this
+ * so we will set the from user as the to user
+ */
+ $data = [
+ 'user_id_from' => $id,
+ 'user_id_to' => $id,
+ 'subject' => $subject,
+ 'message' => $message,
+ ];
+
+ if (!$this->save($data)) {
+ return false;
+ }
+ }
+
+ return true;
+ } catch (\Exception $exception) {
+ $this->setError($exception->getMessage());
+
+ return false;
+ }
+ }
}
diff --git a/administrator/components/com_messages/src/Model/MessagesModel.php b/administrator/components/com_messages/src/Model/MessagesModel.php
index 0077db55d982d..181e2e711291a 100644
--- a/administrator/components/com_messages/src/Model/MessagesModel.php
+++ b/administrator/components/com_messages/src/Model/MessagesModel.php
@@ -1,4 +1,5 @@
getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
- $id = (int) $user->get('id');
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a') . '.*',
- $db->quoteName('u.name', 'user_from'),
- ]
- )
- );
- $query->from($db->quoteName('#__messages', 'a'));
-
- // Join over the users for message owner.
- $query->join('INNER',
- $db->quoteName('#__users', 'u'),
- $db->quoteName('u.id') . ' = ' . $db->quoteName('a.user_id_from')
- )
- ->where($db->quoteName('a.user_id_to') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
-
- // Filter by published state.
- $state = $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif ($state !== '*')
- {
- $query->whereIn($db->quoteName('a.state'), [0, 1]);
- }
-
- // Filter by search in subject or message.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.subject') . ' LIKE :subject',
- $db->quoteName('a.message') . ' LIKE :message',
- ],
- 'OR'
- )
- ->bind(':subject', $search)
- ->bind(':message', $search);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.date_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC')));
-
- return $query;
- }
-
- /**
- * Purge the messages table of old messages for the given user ID.
- *
- * @param int $userId The user id
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function purge(int $userId): void
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['cfg_name', 'cfg_value']))
- ->from($db->quoteName('#__messages_cfg'))
- ->where(
- [
- $db->quoteName('user_id') . ' = :userId',
- $db->quoteName('cfg_name') . ' = ' . $db->quote('auto_purge'),
- ]
- )
- ->bind(':userId', $userId, ParameterType::INTEGER);
-
- $db->setQuery($query);
- $config = $db->loadObject();
-
- // Default is 7 days
- $purge = 7;
-
- // Check if auto_purge value set
- if (\is_object($config) && $config->cfg_name === 'auto_purge')
- {
- $purge = $config->cfg_value;
- }
-
- // If purge value is not 0, then allow purging of old messages
- if ($purge > 0)
- {
- // Purge old messages at day set in message configuration
- $past = Factory::getDate(time() - $purge * 86400)->toSql();
-
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__messages'))
- ->where(
- [
- $db->quoteName('date_time') . ' < :past',
- $db->quoteName('user_id_to') . ' = :userId',
- ]
- )
- ->bind(':past', $past)
- ->bind(':userId', $userId, ParameterType::INTEGER);
-
- $db->setQuery($query);
- $db->execute();
- }
- }
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'message_id', 'a.id',
+ 'subject', 'a.subject',
+ 'state', 'a.state',
+ 'user_id_from', 'a.user_id_from',
+ 'user_id_to', 'a.user_id_to',
+ 'date_time', 'a.date_time',
+ 'priority', 'a.priority',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.date_time', $direction = 'desc')
+ {
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+ $id = (int) $user->get('id');
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a') . '.*',
+ $db->quoteName('u.name', 'user_from'),
+ ]
+ )
+ );
+ $query->from($db->quoteName('#__messages', 'a'));
+
+ // Join over the users for message owner.
+ $query->join(
+ 'INNER',
+ $db->quoteName('#__users', 'u'),
+ $db->quoteName('u.id') . ' = ' . $db->quoteName('a.user_id_from')
+ )
+ ->where($db->quoteName('a.user_id_to') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+
+ // Filter by published state.
+ $state = $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif ($state !== '*') {
+ $query->whereIn($db->quoteName('a.state'), [0, 1]);
+ }
+
+ // Filter by search in subject or message.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.subject') . ' LIKE :subject',
+ $db->quoteName('a.message') . ' LIKE :message',
+ ],
+ 'OR'
+ )
+ ->bind(':subject', $search)
+ ->bind(':message', $search);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.date_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC')));
+
+ return $query;
+ }
+
+ /**
+ * Purge the messages table of old messages for the given user ID.
+ *
+ * @param int $userId The user id
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function purge(int $userId): void
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['cfg_name', 'cfg_value']))
+ ->from($db->quoteName('#__messages_cfg'))
+ ->where(
+ [
+ $db->quoteName('user_id') . ' = :userId',
+ $db->quoteName('cfg_name') . ' = ' . $db->quote('auto_purge'),
+ ]
+ )
+ ->bind(':userId', $userId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $config = $db->loadObject();
+
+ // Default is 7 days
+ $purge = 7;
+
+ // Check if auto_purge value set
+ if (\is_object($config) && $config->cfg_name === 'auto_purge') {
+ $purge = $config->cfg_value;
+ }
+
+ // If purge value is not 0, then allow purging of old messages
+ if ($purge > 0) {
+ // Purge old messages at day set in message configuration
+ $past = Factory::getDate(time() - $purge * 86400)->toSql();
+
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__messages'))
+ ->where(
+ [
+ $db->quoteName('date_time') . ' < :past',
+ $db->quoteName('user_id_to') . ' = :userId',
+ ]
+ )
+ ->bind(':past', $past)
+ ->bind(':userId', $userId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $db->execute();
+ }
+ }
}
diff --git a/administrator/components/com_messages/src/Service/HTML/Messages.php b/administrator/components/com_messages/src/Service/HTML/Messages.php
index 17be8a3280f74..623157328ea36 100644
--- a/administrator/components/com_messages/src/Service/HTML/Messages.php
+++ b/administrator/components/com_messages/src/Service/HTML/Messages.php
@@ -1,4 +1,5 @@
array('trash', 'messages.unpublish', 'JTRASHED', 'COM_MESSAGES_MARK_AS_UNREAD'),
- 1 => array('publish', 'messages.unpublish', 'COM_MESSAGES_OPTION_READ', 'COM_MESSAGES_MARK_AS_UNREAD'),
- 0 => array('unpublish', 'messages.publish', 'COM_MESSAGES_OPTION_UNREAD', 'COM_MESSAGES_MARK_AS_READ'),
- );
+ /**
+ * Get the HTML code of the state switcher
+ *
+ * @param int $i Row number
+ * @param int $value The state value
+ * @param boolean $canChange Can the user change the state?
+ *
+ * @return string
+ *
+ * @since 3.4
+ */
+ public function status($i, $value = 0, $canChange = false)
+ {
+ // Array of image, task, title, action.
+ $states = array(
+ -2 => array('trash', 'messages.unpublish', 'JTRASHED', 'COM_MESSAGES_MARK_AS_UNREAD'),
+ 1 => array('publish', 'messages.unpublish', 'COM_MESSAGES_OPTION_READ', 'COM_MESSAGES_MARK_AS_UNREAD'),
+ 0 => array('unpublish', 'messages.publish', 'COM_MESSAGES_OPTION_UNREAD', 'COM_MESSAGES_MARK_AS_READ'),
+ );
- $state = ArrayHelper::getValue($states, (int) $value, $states[0]);
- $icon = $state[0];
+ $state = ArrayHelper::getValue($states, (int) $value, $states[0]);
+ $icon = $state[0];
- if ($canChange)
- {
- $html = '' . Text::_($state[3]) . '
';
- }
+ if ($canChange) {
+ $html = '' . Text::_($state[3]) . '
';
+ }
- return $html;
- }
+ return $html;
+ }
}
diff --git a/administrator/components/com_messages/src/Table/MessageTable.php b/administrator/components/com_messages/src/Table/MessageTable.php
index a750d7d7c17a3..045194f063f41 100644
--- a/administrator/components/com_messages/src/Table/MessageTable.php
+++ b/administrator/components/com_messages/src/Table/MessageTable.php
@@ -1,4 +1,5 @@
setColumnAlias('published', 'state');
- }
-
- /**
- * Validation and filtering.
- *
- * @return boolean
- *
- * @since 1.5
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Check the to and from users.
- $user = new User($this->user_id_from);
-
- if (empty($user->id))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_FROM_USER'));
-
- return false;
- }
-
- $user = new User($this->user_id_to);
-
- if (empty($user->id))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_TO_USER'));
-
- return false;
- }
-
- if (empty($this->subject))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_SUBJECT'));
-
- return false;
- }
-
- if (empty($this->message))
- {
- $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_MESSAGE'));
-
- return false;
- }
-
- return true;
- }
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 1.5
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ parent::__construct('#__messages', 'message_id', $db);
+
+ $this->setColumnAlias('published', 'state');
+ }
+
+ /**
+ * Validation and filtering.
+ *
+ * @return boolean
+ *
+ * @since 1.5
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Check the to and from users.
+ $user = new User($this->user_id_from);
+
+ if (empty($user->id)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_FROM_USER'));
+
+ return false;
+ }
+
+ $user = new User($this->user_id_to);
+
+ if (empty($user->id)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_TO_USER'));
+
+ return false;
+ }
+
+ if (empty($this->subject)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_SUBJECT'));
+
+ return false;
+ }
+
+ if (empty($this->message)) {
+ $this->setError(Text::_('COM_MESSAGES_ERROR_INVALID_MESSAGE'));
+
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_messages/src/View/Config/HtmlView.php b/administrator/components/com_messages/src/View/Config/HtmlView.php
index c4b9e79201721..508dbf7a5f2ca 100644
--- a/administrator/components/com_messages/src/View/Config/HtmlView.php
+++ b/administrator/components/com_messages/src/View/Config/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- // Bind the record to the form.
- $this->form->bind($this->item);
+ // Bind the record to the form.
+ $this->form->bind($this->item);
- $this->addToolbar();
+ $this->addToolbar();
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
- ToolbarHelper::title(Text::_('COM_MESSAGES_TOOLBAR_MY_SETTINGS'), 'envelope');
+ ToolbarHelper::title(Text::_('COM_MESSAGES_TOOLBAR_MY_SETTINGS'), 'envelope');
- ToolbarHelper::apply('config.save', 'JSAVE');
+ ToolbarHelper::apply('config.save', 'JSAVE');
- ToolbarHelper::cancel('config.cancel', 'JCANCEL');
- }
+ ToolbarHelper::cancel('config.cancel', 'JCANCEL');
+ }
}
diff --git a/administrator/components/com_messages/src/View/Message/HtmlView.php b/administrator/components/com_messages/src/View/Message/HtmlView.php
index 8d6a13cadfe03..44fadfb3c5831 100644
--- a/administrator/components/com_messages/src/View/Message/HtmlView.php
+++ b/administrator/components/com_messages/src/View/Message/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
- elseif ($this->getLayout() !== 'edit' && empty($this->item->message_id))
- {
- throw new GenericDataException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ } elseif ($this->getLayout() !== 'edit' && empty($this->item->message_id)) {
+ throw new GenericDataException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- parent::display($tpl);
- $this->addToolbar();
- }
+ parent::display($tpl);
+ $this->addToolbar();
+ }
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $app = Factory::getApplication();
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $app = Factory::getApplication();
- if ($this->getLayout() == 'edit')
- {
- $app->input->set('hidemainmenu', true);
- ToolbarHelper::title(Text::_('COM_MESSAGES_WRITE_PRIVATE_MESSAGE'), 'envelope-open-text new-privatemessage');
- ToolbarHelper::custom('message.save', 'envelope', '', 'COM_MESSAGES_TOOLBAR_SEND', false);
- ToolbarHelper::cancel('message.cancel');
- ToolbarHelper::help('Private_Messages:_Write');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_MESSAGES_VIEW_PRIVATE_MESSAGE'), 'envelope inbox');
- $sender = User::getInstance($this->item->user_id_from);
+ if ($this->getLayout() == 'edit') {
+ $app->input->set('hidemainmenu', true);
+ ToolbarHelper::title(Text::_('COM_MESSAGES_WRITE_PRIVATE_MESSAGE'), 'envelope-open-text new-privatemessage');
+ ToolbarHelper::custom('message.save', 'envelope', '', 'COM_MESSAGES_TOOLBAR_SEND', false);
+ ToolbarHelper::cancel('message.cancel');
+ ToolbarHelper::help('Private_Messages:_Write');
+ } else {
+ ToolbarHelper::title(Text::_('COM_MESSAGES_VIEW_PRIVATE_MESSAGE'), 'envelope inbox');
+ $sender = User::getInstance($this->item->user_id_from);
- if ($sender->id !== $app->getIdentity()->get('id') && ($sender->authorise('core.admin')
- || $sender->authorise('core.manage', 'com_messages') && $sender->authorise('core.login.admin'))
- && $app->getIdentity()->authorise('core.manage', 'com_users')
- )
- {
- ToolbarHelper::custom('message.reply', 'redo', '', 'COM_MESSAGES_TOOLBAR_REPLY', false);
- }
+ if (
+ $sender->id !== $app->getIdentity()->get('id') && ($sender->authorise('core.admin')
+ || $sender->authorise('core.manage', 'com_messages') && $sender->authorise('core.login.admin'))
+ && $app->getIdentity()->authorise('core.manage', 'com_users')
+ ) {
+ ToolbarHelper::custom('message.reply', 'redo', '', 'COM_MESSAGES_TOOLBAR_REPLY', false);
+ }
- ToolbarHelper::cancel('message.cancel');
- ToolbarHelper::help('Private_Messages:_Read');
- }
- }
+ ToolbarHelper::cancel('message.cancel');
+ ToolbarHelper::help('Private_Messages:_Read');
+ }
+ }
}
diff --git a/administrator/components/com_messages/src/View/Messages/HtmlView.php b/administrator/components/com_messages/src/View/Messages/HtmlView.php
index ec7d978dbd503..1b101fd64e988 100644
--- a/administrator/components/com_messages/src/View/Messages/HtmlView.php
+++ b/administrator/components/com_messages/src/View/Messages/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $state = $this->get('State');
- $canDo = ContentHelper::getActions('com_messages');
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_MESSAGES_MANAGER_MESSAGES'), 'envelope inbox');
-
- // Only display the New button if the user has the access level to create a message and if they have access to the list of users
- if ($canDo->get('core.create') && $user->authorise('core.manage', 'com_users'))
- {
- $toolbar->addNew('message.add');
- }
-
- if (!$this->isEmptyState && $canDo->get('core.edit.state'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('messages.publish')
- ->text('COM_MESSAGES_TOOLBAR_MARK_AS_READ')
- ->listCheck(true);
-
- $childBar->unpublish('messages.unpublish')
- ->text('COM_MESSAGES_TOOLBAR_MARK_AS_UNREAD')
- ->listCheck(true);
-
- if ($this->state->get('filter.state') != -2)
- {
- $childBar->trash('messages.trash')->listCheck(true);
- }
- }
-
- $toolbar->appendButton('Link', 'cog', 'COM_MESSAGES_TOOLBAR_MY_SETTINGS', 'index.php?option=com_messages&view=config');
- ToolbarHelper::divider();
-
- if (!$this->isEmptyState && $this->state->get('filter.state') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('messages.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin'))
- {
- $toolbar->preferences('com_messages');
- }
-
- $toolbar->help('Private_Messages');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $state = $this->get('State');
+ $canDo = ContentHelper::getActions('com_messages');
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_MESSAGES_MANAGER_MESSAGES'), 'envelope inbox');
+
+ // Only display the New button if the user has the access level to create a message and if they have access to the list of users
+ if ($canDo->get('core.create') && $user->authorise('core.manage', 'com_users')) {
+ $toolbar->addNew('message.add');
+ }
+
+ if (!$this->isEmptyState && $canDo->get('core.edit.state')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('messages.publish')
+ ->text('COM_MESSAGES_TOOLBAR_MARK_AS_READ')
+ ->listCheck(true);
+
+ $childBar->unpublish('messages.unpublish')
+ ->text('COM_MESSAGES_TOOLBAR_MARK_AS_UNREAD')
+ ->listCheck(true);
+
+ if ($this->state->get('filter.state') != -2) {
+ $childBar->trash('messages.trash')->listCheck(true);
+ }
+ }
+
+ $toolbar->appendButton('Link', 'cog', 'COM_MESSAGES_TOOLBAR_MY_SETTINGS', 'index.php?option=com_messages&view=config');
+ ToolbarHelper::divider();
+
+ if (!$this->isEmptyState && $this->state->get('filter.state') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('messages.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin')) {
+ $toolbar->preferences('com_messages');
+ }
+
+ $toolbar->help('Private_Messages');
+ }
}
diff --git a/administrator/components/com_messages/tmpl/config/default.php b/administrator/components/com_messages/tmpl/config/default.php
index 1c3d3dba57fa6..862c2a06f37d8 100644
--- a/administrator/components/com_messages/tmpl/config/default.php
+++ b/administrator/components/com_messages/tmpl/config/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_messages/tmpl/message/default.php b/administrator/components/com_messages/tmpl/message/default.php
index ce8277f94cf91..7cb2ee34822d2 100644
--- a/administrator/components/com_messages/tmpl/message/default.php
+++ b/administrator/components/com_messages/tmpl/message/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_messages/tmpl/message/edit.php b/administrator/components/com_messages/tmpl/message/edit.php
index 2ddcf004df2b0..00b33c96a3702 100644
--- a/administrator/components/com_messages/tmpl/message/edit.php
+++ b/administrator/components/com_messages/tmpl/message/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_messages/tmpl/messages/default.php b/administrator/components/com_messages/tmpl/messages/default.php
index 57ab907ee6a5f..0572dfdc0cf29 100644
--- a/administrator/components/com_messages/tmpl/messages/default.php
+++ b/administrator/components/com_messages/tmpl/messages/default.php
@@ -1,4 +1,5 @@
escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_messages/tmpl/messages/emptystate.php b/administrator/components/com_messages/tmpl/messages/emptystate.php
index bd872cff2d773..43a4b1a7a54c4 100644
--- a/administrator/components/com_messages/tmpl/messages/emptystate.php
+++ b/administrator/components/com_messages/tmpl/messages/emptystate.php
@@ -1,4 +1,5 @@
'COM_MESSAGES',
- 'formURL' => 'index.php?option=com_messages&view=messages',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Private_Messages',
- 'icon' => 'icon-envelope inbox',
+ 'textPrefix' => 'COM_MESSAGES',
+ 'formURL' => 'index.php?option=com_messages&view=messages',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Private_Messages',
+ 'icon' => 'icon-envelope inbox',
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_messages')
- && Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_users'))
-{
- $displayData['createURL'] = 'index.php?option=com_messages&task=message.add';
+if (
+ Factory::getApplication()->getIdentity()->authorise('core.create', 'com_messages')
+ && Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_users')
+) {
+ $displayData['createURL'] = 'index.php?option=com_messages&task=message.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_modules/helpers/modules.php b/administrator/components/com_modules/helpers/modules.php
index a2419e69b6609..7a2058e8a32b2 100644
--- a/administrator/components/com_modules/helpers/modules.php
+++ b/administrator/components/com_modules/helpers/modules.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Modules component helper.
@@ -18,5 +21,4 @@
*/
abstract class ModulesHelper extends \Joomla\Component\Modules\Administrator\Helper\ModulesHelper
{
-
}
diff --git a/administrator/components/com_modules/layouts/joomla/form/field/modulespositionedit.php b/administrator/components/com_modules/layouts/joomla/form/field/modulespositionedit.php
index 2af5c7d562a7b..671d77c70a44b 100644
--- a/administrator/components/com_modules/layouts/joomla/form/field/modulespositionedit.php
+++ b/administrator/components/com_modules/layouts/joomla/form/field/modulespositionedit.php
@@ -1,4 +1,5 @@
escape(Text::_('JGLOBAL_TYPE_OR_SELECT_SOME_OPTIONS')) . '" ',
+ 'class="' . $class . '"',
+ ' allow-custom',
+ ' search-placeholder="' . $this->escape(Text::_('JGLOBAL_TYPE_OR_SELECT_SOME_OPTIONS')) . '" ',
);
$selectAttr = array(
- $disabled ? 'disabled' : '',
- $readonly ? 'readonly' : '',
- strlen($hint) ? 'placeholder="' . $this->escape($hint) . '"' : '',
- $onchange ? ' onchange="' . $onchange . '"' : '',
- $autofocus ? ' autofocus' : '',
+ $disabled ? 'disabled' : '',
+ $readonly ? 'readonly' : '',
+ strlen($hint) ? 'placeholder="' . $this->escape($hint) . '"' : '',
+ $onchange ? ' onchange="' . $onchange . '"' : '',
+ $autofocus ? ' autofocus' : '',
);
-if ($required)
-{
- $selectAttr[] = ' required class="required"';
- $attributes[] = ' required';
+if ($required) {
+ $selectAttr[] = ' required class="required"';
+ $attributes[] = ' required';
}
Text::script('JGLOBAL_SELECT_NO_RESULTS_MATCH');
Text::script('JGLOBAL_SELECT_PRESS_TO_SELECT');
Factory::getDocument()->getWebAssetManager()
- ->usePreset('choicesjs')
- ->useScript('webcomponent.field-fancy-select');
+ ->usePreset('choicesjs')
+ ->useScript('webcomponent.field-fancy-select');
?>
> $id,
- 'list.select' => $value,
- 'list.attr' => implode(' ', $selectAttr),
- )
- );
-?>
+ echo HTMLHelper::_('select.groupedlist', $positions, $name, array(
+ 'id' => $id,
+ 'list.select' => $value,
+ 'list.attr' => implode(' ', $selectAttr),
+ ));
+ ?>
diff --git a/administrator/components/com_modules/layouts/toolbar/cancelselect.php b/administrator/components/com_modules/layouts/toolbar/cancelselect.php
index b94d855fd051f..7637060854cd8 100644
--- a/administrator/components/com_modules/layouts/toolbar/cancelselect.php
+++ b/administrator/components/com_modules/layouts/toolbar/cancelselect.php
@@ -1,4 +1,5 @@
-
-
-
+
+
+
diff --git a/administrator/components/com_modules/services/provider.php b/administrator/components/com_modules/services/provider.php
index 4998d4d42da02..e624191955b75 100644
--- a/administrator/components/com_modules/services/provider.php
+++ b/administrator/components/com_modules/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Modules'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Modules'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Modules'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Modules'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new ModulesComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new ModulesComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_modules/src/Controller/DisplayController.php b/administrator/components/com_modules/src/Controller/DisplayController.php
index 6751f988c7a0d..e4a8e7b303f90 100644
--- a/administrator/components/com_modules/src/Controller/DisplayController.php
+++ b/administrator/components/com_modules/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('layout', 'edit');
- $id = $this->input->getInt('id');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array|boolean $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $layout = $this->input->get('layout', 'edit');
+ $id = $this->input->getInt('id');
- // Verify client
- $clientId = $this->input->post->getInt('client_id');
+ // Verify client
+ $clientId = $this->input->post->getInt('client_id');
- if (!is_null($clientId))
- {
- $uri = Uri::getInstance();
+ if (!is_null($clientId)) {
+ $uri = Uri::getInstance();
- if ((int) $uri->getVar('client_id') !== (int) $clientId)
- {
- $this->setRedirect(Route::_('index.php?option=com_modules&view=modules&client_id=' . $clientId, false));
+ if ((int) $uri->getVar('client_id') !== (int) $clientId) {
+ $this->setRedirect(Route::_('index.php?option=com_modules&view=modules&client_id=' . $clientId, false));
- return false;
- }
- }
+ return false;
+ }
+ }
- // Check for edit form.
- if ($layout == 'edit' && !$this->checkEditId('com_modules.edit.module', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($layout == 'edit' && !$this->checkEditId('com_modules.edit.module', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_modules&view=modules&client_id=' . $this->input->getInt('client_id'), false));
+ $this->setRedirect(Route::_('index.php?option=com_modules&view=modules&client_id=' . $this->input->getInt('client_id'), false));
- return false;
- }
+ return false;
+ }
- // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
- $factory = $this->app->bootComponent('menus')->getMVCFactory();
+ // Check if we have a mod_menu module set to All languages or a mod_menu module for each admin language.
+ $factory = $this->app->bootComponent('menus')->getMVCFactory();
- if ($langMissing = $factory->createModel('Menus', 'Administrator')->getMissingModuleLanguages())
- {
- $this->app->enqueueMessage(Text::sprintf('JMENU_MULTILANG_WARNING_MISSING_MODULES', implode(', ', $langMissing)), 'warning');
- }
+ if ($langMissing = $factory->createModel('Menus', 'Administrator')->getMissingModuleLanguages()) {
+ $this->app->enqueueMessage(Text::sprintf('JMENU_MULTILANG_WARNING_MISSING_MODULES', implode(', ', $langMissing)), 'warning');
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_modules/src/Controller/ModuleController.php b/administrator/components/com_modules/src/Controller/ModuleController.php
index ef53b80adf3fe..d695c394a63a0 100644
--- a/administrator/components/com_modules/src/Controller/ModuleController.php
+++ b/administrator/components/com_modules/src/Controller/ModuleController.php
@@ -1,4 +1,5 @@
app;
-
- // Get the result of the parent method. If an error, just return it.
- $result = parent::add();
-
- if ($result instanceof \Exception)
- {
- return $result;
- }
-
- // Look for the Extension ID.
- $extensionId = $this->input->get('eid', 0, 'int');
-
- if (empty($extensionId))
- {
- $redirectUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . '&layout=edit';
-
- $this->setRedirect(Route::_($redirectUrl, false));
-
- $app->enqueueMessage(Text::_('COM_MODULES_ERROR_INVALID_EXTENSION'), 'warning');
- }
-
- $app->setUserState('com_modules.add.module.extension_id', $extensionId);
- $app->setUserState('com_modules.add.module.params', null);
-
- // Parameters could be coming in for a new item, so let's set them.
- $params = $this->input->get('params', array(), 'array');
- $app->setUserState('com_modules.add.module.params', $params);
- }
-
- /**
- * Override parent cancel method to reset the add module state.
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return boolean True if access level checks pass, false otherwise.
- *
- * @since 1.6
- */
- public function cancel($key = null)
- {
- $result = parent::cancel();
-
- $this->app->setUserState('com_modules.add.module.extension_id', null);
- $this->app->setUserState('com_modules.add.module.params', null);
-
- if ($return = $this->input->get('return', '', 'BASE64'))
- {
- $return = base64_decode($return);
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($return))
- {
- $return = Uri::base();
- }
-
- $this->app->redirect($return);
- }
-
- return $result;
- }
-
- /**
- * Override parent allowSave method.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowSave($data, $key = 'id')
- {
- // Use custom position if selected
- if (isset($data['custom_position']))
- {
- if (empty($data['position']))
- {
- $data['position'] = $data['custom_position'];
- }
-
- unset($data['custom_position']);
- }
-
- return parent::allowSave($data, $key);
- }
-
- /**
- * Method override to check if you can edit an existing record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 3.2
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- // Initialise variables.
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
-
- // Zero record (id:0), return component edit permission by calling parent controller method
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Check edit on the record asset (explicit or inherited)
- if ($this->app->getIdentity()->authorise('core.edit', 'com_modules.module.' . $recordId))
- {
- return true;
- }
-
- return false;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param string $model The model
- *
- * @return boolean True on success.
- *
- * @since 1.7
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('Module', 'Administrator', array());
-
- // Preset the redirect
- $redirectUrl = 'index.php?option=com_modules&view=modules' . $this->getRedirectToListAppend();
-
- $this->setRedirect(Route::_($redirectUrl, false));
-
- return parent::batch($model);
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- $task = $this->getTask();
-
- switch ($task)
- {
- case 'save2new':
- $this->app->setUserState('com_modules.add.module.extension_id', $model->getState('module.extension_id'));
- break;
-
- default:
- $this->app->setUserState('com_modules.add.module.extension_id', null);
- break;
- }
-
- $this->app->setUserState('com_modules.add.module.params', null);
- }
-
- /**
- * Method to save a record.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key
- *
- * @return boolean True if successful, false otherwise.
- */
- public function save($key = null, $urlVar = null)
- {
- $this->checkToken();
-
- if ($this->app->getDocument()->getType() == 'json')
- {
- $model = $this->getModel();
- $data = $this->input->post->get('jform', array(), 'array');
- $item = $model->getItem($this->input->get('id'));
- $properties = $item->getProperties();
-
- if (isset($data['params']))
- {
- unset($properties['params']);
- }
-
- // Replace changed properties
- $data = array_replace_recursive($properties, $data);
-
- if (!empty($data['assigned']))
- {
- $data['assigned'] = array_map('abs', $data['assigned']);
- }
-
- // Add new data to input before process by parent save()
- $this->input->post->set('jform', $data);
-
- // Add path of forms directory
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/models/forms');
- }
-
- return parent::save($key, $urlVar);
-
- }
-
- /**
- * Method to get the other modules in the same position
- *
- * @return string The data for the Ajax request.
- *
- * @since 3.6.3
- */
- public function orderPosition()
- {
- $app = $this->app;
-
- // Send json mime type.
- $app->mimeType = 'application/json';
- $app->setHeader('Content-Type', $app->mimeType . '; charset=' . $app->charSet);
- $app->sendHeaders();
-
- // Check if user token is valid.
- if (!Session::checkToken('get'))
- {
- $app->enqueueMessage(Text::_('JINVALID_TOKEN_NOTICE'), 'error');
- echo new JsonResponse;
- $app->close();
- }
-
- $clientId = $this->input->getValue('client_id');
- $position = $this->input->getValue('position');
- $moduleId = $this->input->getValue('module_id');
-
- // Access check.
- if (!$this->app->getIdentity()->authorise('core.create', 'com_modules')
- && !$this->app->getIdentity()->authorise('core.edit.state', 'com_modules')
- && ($moduleId && !$this->app->getIdentity()->authorise('core.edit.state', 'com_modules.module.' . $moduleId)))
- {
- $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error');
- echo new JsonResponse;
- $app->close();
- }
-
- $db = Factory::getDbo();
- $clientId = (int) $clientId;
- $query = $db->getQuery(true)
- ->select($db->quoteName(['position', 'ordering', 'title']))
- ->from($db->quoteName('#__modules'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->where($db->quoteName('position') . ' = :position')
- ->order($db->quoteName('ordering'))
- ->bind(':clientid', $clientId, ParameterType::INTEGER)
- ->bind(':position', $position);
-
- $db->setQuery($query);
-
- try
- {
- $orders = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
-
- return '';
- }
-
- $orders2 = array();
- $n = count($orders);
-
- if ($n > 0)
- {
- for ($i = 0; $i < $n; $i++)
- {
- if (!isset($orders2[$orders[$i]->position]))
- {
- $orders2[$orders[$i]->position] = 0;
- }
-
- $orders2[$orders[$i]->position]++;
- $ord = $orders2[$orders[$i]->position];
- $title = Text::sprintf('COM_MODULES_OPTION_ORDER_POSITION', $ord, htmlspecialchars($orders[$i]->title, ENT_QUOTES, 'UTF-8'));
-
- $html[] = $orders[$i]->position . ',' . $ord . ',' . $title;
- }
- }
- else
- {
- $html[] = $position . ',' . 1 . ',' . Text::_('JNONE');
- }
-
- echo new JsonResponse($html);
- $app->close();
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
- $append .= '&client_id=' . $this->input->getInt('client_id');
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&client_id=' . $this->input->getInt('client_id');
-
- return $append;
- }
+ /**
+ * Override parent add method.
+ *
+ * @return \Exception|void True if the record can be added, a \Exception object if not.
+ *
+ * @since 1.6
+ */
+ public function add()
+ {
+ $app = $this->app;
+
+ // Get the result of the parent method. If an error, just return it.
+ $result = parent::add();
+
+ if ($result instanceof \Exception) {
+ return $result;
+ }
+
+ // Look for the Extension ID.
+ $extensionId = $this->input->get('eid', 0, 'int');
+
+ if (empty($extensionId)) {
+ $redirectUrl = 'index.php?option=' . $this->option . '&view=' . $this->view_item . '&layout=edit';
+
+ $this->setRedirect(Route::_($redirectUrl, false));
+
+ $app->enqueueMessage(Text::_('COM_MODULES_ERROR_INVALID_EXTENSION'), 'warning');
+ }
+
+ $app->setUserState('com_modules.add.module.extension_id', $extensionId);
+ $app->setUserState('com_modules.add.module.params', null);
+
+ // Parameters could be coming in for a new item, so let's set them.
+ $params = $this->input->get('params', array(), 'array');
+ $app->setUserState('com_modules.add.module.params', $params);
+ }
+
+ /**
+ * Override parent cancel method to reset the add module state.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return boolean True if access level checks pass, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function cancel($key = null)
+ {
+ $result = parent::cancel();
+
+ $this->app->setUserState('com_modules.add.module.extension_id', null);
+ $this->app->setUserState('com_modules.add.module.params', null);
+
+ if ($return = $this->input->get('return', '', 'BASE64')) {
+ $return = base64_decode($return);
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($return)) {
+ $return = Uri::base();
+ }
+
+ $this->app->redirect($return);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Override parent allowSave method.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowSave($data, $key = 'id')
+ {
+ // Use custom position if selected
+ if (isset($data['custom_position'])) {
+ if (empty($data['position'])) {
+ $data['position'] = $data['custom_position'];
+ }
+
+ unset($data['custom_position']);
+ }
+
+ return parent::allowSave($data, $key);
+ }
+
+ /**
+ * Method override to check if you can edit an existing record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ // Initialise variables.
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+
+ // Zero record (id:0), return component edit permission by calling parent controller method
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Check edit on the record asset (explicit or inherited)
+ if ($this->app->getIdentity()->authorise('core.edit', 'com_modules.module.' . $recordId)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param string $model The model
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('Module', 'Administrator', array());
+
+ // Preset the redirect
+ $redirectUrl = 'index.php?option=com_modules&view=modules' . $this->getRedirectToListAppend();
+
+ $this->setRedirect(Route::_($redirectUrl, false));
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ $task = $this->getTask();
+
+ switch ($task) {
+ case 'save2new':
+ $this->app->setUserState('com_modules.add.module.extension_id', $model->getState('module.extension_id'));
+ break;
+
+ default:
+ $this->app->setUserState('com_modules.add.module.extension_id', null);
+ break;
+ }
+
+ $this->app->setUserState('com_modules.add.module.params', null);
+ }
+
+ /**
+ * Method to save a record.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key
+ *
+ * @return boolean True if successful, false otherwise.
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ $this->checkToken();
+
+ if ($this->app->getDocument()->getType() == 'json') {
+ $model = $this->getModel();
+ $data = $this->input->post->get('jform', array(), 'array');
+ $item = $model->getItem($this->input->get('id'));
+ $properties = $item->getProperties();
+
+ if (isset($data['params'])) {
+ unset($properties['params']);
+ }
+
+ // Replace changed properties
+ $data = array_replace_recursive($properties, $data);
+
+ if (!empty($data['assigned'])) {
+ $data['assigned'] = array_map('abs', $data['assigned']);
+ }
+
+ // Add new data to input before process by parent save()
+ $this->input->post->set('jform', $data);
+
+ // Add path of forms directory
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/models/forms');
+ }
+
+ return parent::save($key, $urlVar);
+ }
+
+ /**
+ * Method to get the other modules in the same position
+ *
+ * @return string The data for the Ajax request.
+ *
+ * @since 3.6.3
+ */
+ public function orderPosition()
+ {
+ $app = $this->app;
+
+ // Send json mime type.
+ $app->mimeType = 'application/json';
+ $app->setHeader('Content-Type', $app->mimeType . '; charset=' . $app->charSet);
+ $app->sendHeaders();
+
+ // Check if user token is valid.
+ if (!Session::checkToken('get')) {
+ $app->enqueueMessage(Text::_('JINVALID_TOKEN_NOTICE'), 'error');
+ echo new JsonResponse();
+ $app->close();
+ }
+
+ $clientId = $this->input->getValue('client_id');
+ $position = $this->input->getValue('position');
+ $moduleId = $this->input->getValue('module_id');
+
+ // Access check.
+ if (
+ !$this->app->getIdentity()->authorise('core.create', 'com_modules')
+ && !$this->app->getIdentity()->authorise('core.edit.state', 'com_modules')
+ && ($moduleId && !$this->app->getIdentity()->authorise('core.edit.state', 'com_modules.module.' . $moduleId))
+ ) {
+ $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 'error');
+ echo new JsonResponse();
+ $app->close();
+ }
+
+ $db = Factory::getDbo();
+ $clientId = (int) $clientId;
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['position', 'ordering', 'title']))
+ ->from($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->where($db->quoteName('position') . ' = :position')
+ ->order($db->quoteName('ordering'))
+ ->bind(':clientid', $clientId, ParameterType::INTEGER)
+ ->bind(':position', $position);
+
+ $db->setQuery($query);
+
+ try {
+ $orders = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+
+ return '';
+ }
+
+ $orders2 = array();
+ $n = count($orders);
+
+ if ($n > 0) {
+ for ($i = 0; $i < $n; $i++) {
+ if (!isset($orders2[$orders[$i]->position])) {
+ $orders2[$orders[$i]->position] = 0;
+ }
+
+ $orders2[$orders[$i]->position]++;
+ $ord = $orders2[$orders[$i]->position];
+ $title = Text::sprintf('COM_MODULES_OPTION_ORDER_POSITION', $ord, htmlspecialchars($orders[$i]->title, ENT_QUOTES, 'UTF-8'));
+
+ $html[] = $orders[$i]->position . ',' . $ord . ',' . $title;
+ }
+ } else {
+ $html[] = $position . ',' . 1 . ',' . Text::_('JNONE');
+ }
+
+ echo new JsonResponse($html);
+ $app->close();
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+ $append .= '&client_id=' . $this->input->getInt('client_id');
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&client_id=' . $this->input->getInt('client_id');
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_modules/src/Controller/ModulesController.php b/administrator/components/com_modules/src/Controller/ModulesController.php
index 005e5e8ffa0f3..bf4dd445f89cb 100644
--- a/administrator/components/com_modules/src/Controller/ModulesController.php
+++ b/administrator/components/com_modules/src/Controller/ModulesController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $pks = (array) $this->input->post->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $pks = array_filter($pks);
-
- try
- {
- if (empty($pks))
- {
- throw new \Exception(Text::_('COM_MODULES_ERROR_NO_MODULES_SELECTED'));
- }
-
- $model = $this->getModel();
- $model->duplicate($pks);
- $this->setMessage(Text::plural('COM_MODULES_N_MODULES_DUPLICATED', count($pks)));
- }
- catch (\Exception $e)
- {
- $this->app->enqueueMessage($e->getMessage(), 'warning');
- }
-
- $this->setRedirect('index.php?option=com_modules&view=modules' . $this->getRedirectToListAppend());
- }
-
- /**
- * Method to get a model object, loading it if required.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return object The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Module', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to get the number of frontend modules
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('Modules');
-
- $model->setState('filter.state', 1);
- $model->setState('filter.client_id', 0);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_MODULES_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_MODULES_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&client_id=' . $this->input->getInt('client_id');
-
- return $append;
- }
+ /**
+ * Method to clone an existing module.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function duplicate()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $pks = (array) $this->input->post->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $pks = array_filter($pks);
+
+ try {
+ if (empty($pks)) {
+ throw new \Exception(Text::_('COM_MODULES_ERROR_NO_MODULES_SELECTED'));
+ }
+
+ $model = $this->getModel();
+ $model->duplicate($pks);
+ $this->setMessage(Text::plural('COM_MODULES_N_MODULES_DUPLICATED', count($pks)));
+ } catch (\Exception $e) {
+ $this->app->enqueueMessage($e->getMessage(), 'warning');
+ }
+
+ $this->setRedirect('index.php?option=com_modules&view=modules' . $this->getRedirectToListAppend());
+ }
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Module', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to get the number of frontend modules
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Modules');
+
+ $model->setState('filter.state', 1);
+ $model->setState('filter.client_id', 0);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_MODULES_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_MODULES_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&client_id=' . $this->input->getInt('client_id');
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_modules/src/Extension/ModulesComponent.php b/administrator/components/com_modules/src/Extension/ModulesComponent.php
index 167c8b8ff60fd..1d90e6d75b772 100644
--- a/administrator/components/com_modules/src/Extension/ModulesComponent.php
+++ b/administrator/components/com_modules/src/Extension/ModulesComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('modules', new Modules);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('modules', new Modules());
+ }
}
diff --git a/administrator/components/com_modules/src/Field/ModulesModuleField.php b/administrator/components/com_modules/src/Field/ModulesModuleField.php
index f748ff6f55af0..ae307df61c29b 100644
--- a/administrator/components/com_modules/src/Field/ModulesModuleField.php
+++ b/administrator/components/com_modules/src/Field/ModulesModuleField.php
@@ -1,4 +1,5 @@
$name;
- }
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 4.0.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'client':
+ return $this->$name;
+ }
- return parent::__get($name);
- }
+ return parent::__get($name);
+ }
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'client':
- $this->$name = (string) $value;
- break;
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'client':
+ $this->$name = (string) $value;
+ break;
- default:
- parent::__set($name, $value);
- }
- }
+ default:
+ parent::__set($name, $value);
+ }
+ }
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
- if ($result === true)
- {
- $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
- }
+ if ($result === true) {
+ $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
+ }
- return $result;
- }
+ return $result;
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.4.2
- */
- public function getOptions()
- {
- $clientId = $this->client === 'administrator' ? 1 : 0;
- $options = ModulesHelper::getModules($clientId);
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.4.2
+ */
+ public function getOptions()
+ {
+ $clientId = $this->client === 'administrator' ? 1 : 0;
+ $options = ModulesHelper::getModules($clientId);
- return array_merge(parent::getOptions(), $options);
- }
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_modules/src/Field/ModulesPositionField.php b/administrator/components/com_modules/src/Field/ModulesPositionField.php
index e7e73d25b377d..b6a82d5d4116a 100644
--- a/administrator/components/com_modules/src/Field/ModulesPositionField.php
+++ b/administrator/components/com_modules/src/Field/ModulesPositionField.php
@@ -1,4 +1,5 @@
$name;
- }
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 4.0.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'client':
+ return $this->$name;
+ }
- return parent::__get($name);
- }
+ return parent::__get($name);
+ }
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'client':
- $this->$name = (string) $value;
- break;
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'client':
+ $this->$name = (string) $value;
+ break;
- default:
- parent::__set($name, $value);
- }
- }
+ default:
+ parent::__set($name, $value);
+ }
+ }
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
- if ($result === true)
- {
- $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
- }
+ if ($result === true) {
+ $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
+ }
- return $result;
- }
+ return $result;
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.4.2
- */
- public function getOptions()
- {
- $clientId = $this->client === 'administrator' ? 1 : 0;
- $options = ModulesHelper::getPositions($clientId);
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.4.2
+ */
+ public function getOptions()
+ {
+ $clientId = $this->client === 'administrator' ? 1 : 0;
+ $options = ModulesHelper::getPositions($clientId);
- return array_merge(parent::getOptions(), $options);
- }
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_modules/src/Field/ModulesPositioneditField.php b/administrator/components/com_modules/src/Field/ModulesPositioneditField.php
index 040f5f3d98eff..71163417131d3 100644
--- a/administrator/components/com_modules/src/Field/ModulesPositioneditField.php
+++ b/administrator/components/com_modules/src/Field/ModulesPositioneditField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'client':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 4.0.0
- */
- protected function getInput()
- {
- $data = $this->getLayoutData();
-
- $clientId = $this->client === 'administrator' ? 1 : 0;
- $positions = HTMLHelper::_('modules.positions', $clientId, 1, $this->value);
-
- $data['client'] = $clientId;
- $data['positions'] = $positions;
-
- $renderer = $this->getRenderer($this->layout);
- $renderer->setComponent('com_modules');
- $renderer->setClient(1);
-
- return $renderer->render($data);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $type = 'ModulesPositionedit';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.modulespositionedit';
+
+ /**
+ * Client name.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $client;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 4.0.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'client':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'client':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ $this->client = $this->element['client'] ? (string) $this->element['client'] : 'site';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 4.0.0
+ */
+ protected function getInput()
+ {
+ $data = $this->getLayoutData();
+
+ $clientId = $this->client === 'administrator' ? 1 : 0;
+ $positions = HTMLHelper::_('modules.positions', $clientId, 1, $this->value);
+
+ $data['client'] = $clientId;
+ $data['positions'] = $positions;
+
+ $renderer = $this->getRenderer($this->layout);
+ $renderer->setComponent('com_modules');
+ $renderer->setClient(1);
+
+ return $renderer->render($data);
+ }
}
diff --git a/administrator/components/com_modules/src/Helper/ModulesHelper.php b/administrator/components/com_modules/src/Helper/ModulesHelper.php
index 6657d2f1b988a..e2aefdc58a359 100644
--- a/administrator/components/com_modules/src/Helper/ModulesHelper.php
+++ b/administrator/components/com_modules/src/Helper/ModulesHelper.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('position'))
- ->from($db->quoteName('#__modules'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->order($db->quoteName('position'))
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $positions = $db->loadColumn();
- $positions = is_array($positions) ? $positions : array();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return;
- }
-
- // Build the list
- $options = array();
-
- foreach ($positions as $position)
- {
- if (!$position && !$editPositions)
- {
- $options[] = HTMLHelper::_('select.option', 'none', Text::_('COM_MODULES_NONE'));
- }
- elseif (!$position)
- {
- $options[] = HTMLHelper::_('select.option', '', Text::_('COM_MODULES_NONE'));
- }
- else
- {
- $options[] = HTMLHelper::_('select.option', $position, $position);
- }
- }
-
- return $options;
- }
-
- /**
- * Return a list of templates
- *
- * @param integer $clientId Client ID
- * @param string $state State
- * @param string $template Template name
- *
- * @return array List of templates
- */
- public static function getTemplates($clientId = 0, $state = '', $template = '')
- {
- $db = Factory::getDbo();
- $clientId = (int) $clientId;
-
- // Get the database object and a new query object.
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select($db->quoteName(['element', 'name', 'enabled']))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->where($db->quoteName('type') . ' = ' . $db->quote('template'));
-
- if ($state != '')
- {
- $query->where($db->quoteName('enabled') . ' = :state')
- ->bind(':state', $state);
- }
-
- if ($template != '')
- {
- $query->where($db->quoteName('element') . ' = :element')
- ->bind(':element', $template);
- }
-
- $query->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- // Set the query and load the templates.
- $db->setQuery($query);
- $templates = $db->loadObjectList('element');
-
- return $templates;
- }
-
- /**
- * Get a list of the unique modules installed in the client application.
- *
- * @param int $clientId The client id.
- *
- * @return array Array of unique modules
- */
- public static function getModules($clientId)
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select('element AS value, name AS text')
- ->from('#__extensions as e')
- ->where('e.client_id = ' . (int) $clientId)
- ->where('type = ' . $db->quote('module'))
- ->join('LEFT', '#__modules as m ON m.module=e.element AND m.client_id=e.client_id')
- ->where('m.module IS NOT NULL')
- ->group('element,name');
-
- $db->setQuery($query);
- $modules = $db->loadObjectList();
- $lang = Factory::getLanguage();
-
- foreach ($modules as $i => $module)
- {
- $extension = $module->value;
- $path = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
- $source = $path . "/modules/$extension";
- $lang->load("$extension.sys", $path)
- || $lang->load("$extension.sys", $source);
- $modules[$i]->text = Text::_($module->text);
- }
-
- $modules = ArrayHelper::sortObjects($modules, 'text', 1, true, true);
-
- return $modules;
- }
-
- /**
- * Get a list of the assignment options for modules to menus.
- *
- * @param int $clientId The client id.
- *
- * @return array
- */
- public static function getAssignmentOptions($clientId)
- {
- $options = array();
- $options[] = HTMLHelper::_('select.option', '0', 'COM_MODULES_OPTION_MENU_ALL');
- $options[] = HTMLHelper::_('select.option', '-', 'COM_MODULES_OPTION_MENU_NONE');
-
- if ($clientId == 0)
- {
- $options[] = HTMLHelper::_('select.option', '1', 'COM_MODULES_OPTION_MENU_INCLUDE');
- $options[] = HTMLHelper::_('select.option', '-1', 'COM_MODULES_OPTION_MENU_EXCLUDE');
- }
-
- return $options;
- }
-
- /**
- * Return a translated module position name
- *
- * @param integer $clientId Application client id 0: site | 1: admin
- * @param string $template Template name
- * @param string $position Position name
- *
- * @return string Return a translated position name
- *
- * @since 3.0
- */
- public static function getTranslatedModulePosition($clientId, $template, $position)
- {
- // Template translation
- $lang = Factory::getLanguage();
- $path = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
-
- $loaded = $lang->getPaths('tpl_' . $template . '.sys');
-
- // Only load the template's language file if it hasn't been already
- if (!$loaded)
- {
- $lang->load('tpl_' . $template . '.sys', $path, null, false, false)
- || $lang->load('tpl_' . $template . '.sys', $path . '/templates/' . $template, null, false, false)
- || $lang->load('tpl_' . $template . '.sys', $path, $lang->getDefault(), false, false)
- || $lang->load('tpl_' . $template . '.sys', $path . '/templates/' . $template, $lang->getDefault(), false, false);
- }
-
- $langKey = strtoupper('TPL_' . $template . '_POSITION_' . $position);
- $text = Text::_($langKey);
-
- // Avoid untranslated strings
- if (!self::isTranslatedText($langKey, $text))
- {
- // Modules component translation
- $langKey = strtoupper('COM_MODULES_POSITION_' . $position);
- $text = Text::_($langKey);
-
- // Avoid untranslated strings
- if (!self::isTranslatedText($langKey, $text))
- {
- // Try to humanize the position name
- $text = ucfirst(preg_replace('/^' . $template . '\-/', '', $position));
- $text = ucwords(str_replace(array('-', '_'), ' ', $text));
- }
- }
-
- return $text;
- }
-
- /**
- * Check if the string was translated
- *
- * @param string $langKey Language file text key
- * @param string $text The "translated" text to be checked
- *
- * @return boolean Return true for translated text
- *
- * @since 3.0
- */
- public static function isTranslatedText($langKey, $text)
- {
- return $text !== $langKey;
- }
-
- /**
- * Create and return a new Option
- *
- * @param string $value The option value [optional]
- * @param string $text The option text [optional]
- *
- * @return object The option as an object (\stdClass instance)
- *
- * @since 3.0
- */
- public static function createOption($value = '', $text = '')
- {
- if (empty($text))
- {
- $text = $value;
- }
-
- $option = new \stdClass;
- $option->value = $value;
- $option->text = $text;
-
- return $option;
- }
-
- /**
- * Create and return a new Option Group
- *
- * @param string $label Value and label for group [optional]
- * @param array $options Array of options to insert into group [optional]
- *
- * @return array Return the new group as an array
- *
- * @since 3.0
- */
- public static function createOptionGroup($label = '', $options = array())
- {
- $group = array();
- $group['value'] = $label;
- $group['text'] = $label;
- $group['items'] = $options;
-
- return $group;
- }
+ /**
+ * Get a list of filter options for the state of a module.
+ *
+ * @return array An array of \JHtmlOption elements.
+ */
+ public static function getStateOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('JPUBLISHED'));
+ $options[] = HTMLHelper::_('select.option', '0', Text::_('JUNPUBLISHED'));
+ $options[] = HTMLHelper::_('select.option', '-2', Text::_('JTRASHED'));
+ $options[] = HTMLHelper::_('select.option', '*', Text::_('JALL'));
+
+ return $options;
+ }
+
+ /**
+ * Get a list of filter options for the application clients.
+ *
+ * @return array An array of \JHtmlOption elements.
+ */
+ public static function getClientOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '0', Text::_('JSITE'));
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('JADMINISTRATOR'));
+
+ return $options;
+ }
+
+ /**
+ * Get a list of modules positions
+ *
+ * @param integer $clientId Client ID
+ * @param boolean $editPositions Allow to edit the positions
+ *
+ * @return array A list of positions
+ */
+ public static function getPositions($clientId, $editPositions = false)
+ {
+ $db = Factory::getDbo();
+ $clientId = (int) $clientId;
+ $query = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('position'))
+ ->from($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->order($db->quoteName('position'))
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $positions = $db->loadColumn();
+ $positions = is_array($positions) ? $positions : array();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return;
+ }
+
+ // Build the list
+ $options = array();
+
+ foreach ($positions as $position) {
+ if (!$position && !$editPositions) {
+ $options[] = HTMLHelper::_('select.option', 'none', Text::_('COM_MODULES_NONE'));
+ } elseif (!$position) {
+ $options[] = HTMLHelper::_('select.option', '', Text::_('COM_MODULES_NONE'));
+ } else {
+ $options[] = HTMLHelper::_('select.option', $position, $position);
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Return a list of templates
+ *
+ * @param integer $clientId Client ID
+ * @param string $state State
+ * @param string $template Template name
+ *
+ * @return array List of templates
+ */
+ public static function getTemplates($clientId = 0, $state = '', $template = '')
+ {
+ $db = Factory::getDbo();
+ $clientId = (int) $clientId;
+
+ // Get the database object and a new query object.
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select($db->quoteName(['element', 'name', 'enabled']))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('template'));
+
+ if ($state != '') {
+ $query->where($db->quoteName('enabled') . ' = :state')
+ ->bind(':state', $state);
+ }
+
+ if ($template != '') {
+ $query->where($db->quoteName('element') . ' = :element')
+ ->bind(':element', $template);
+ }
+
+ $query->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ // Set the query and load the templates.
+ $db->setQuery($query);
+ $templates = $db->loadObjectList('element');
+
+ return $templates;
+ }
+
+ /**
+ * Get a list of the unique modules installed in the client application.
+ *
+ * @param int $clientId The client id.
+ *
+ * @return array Array of unique modules
+ */
+ public static function getModules($clientId)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('element AS value, name AS text')
+ ->from('#__extensions as e')
+ ->where('e.client_id = ' . (int) $clientId)
+ ->where('type = ' . $db->quote('module'))
+ ->join('LEFT', '#__modules as m ON m.module=e.element AND m.client_id=e.client_id')
+ ->where('m.module IS NOT NULL')
+ ->group('element,name');
+
+ $db->setQuery($query);
+ $modules = $db->loadObjectList();
+ $lang = Factory::getLanguage();
+
+ foreach ($modules as $i => $module) {
+ $extension = $module->value;
+ $path = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
+ $source = $path . "/modules/$extension";
+ $lang->load("$extension.sys", $path)
+ || $lang->load("$extension.sys", $source);
+ $modules[$i]->text = Text::_($module->text);
+ }
+
+ $modules = ArrayHelper::sortObjects($modules, 'text', 1, true, true);
+
+ return $modules;
+ }
+
+ /**
+ * Get a list of the assignment options for modules to menus.
+ *
+ * @param int $clientId The client id.
+ *
+ * @return array
+ */
+ public static function getAssignmentOptions($clientId)
+ {
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '0', 'COM_MODULES_OPTION_MENU_ALL');
+ $options[] = HTMLHelper::_('select.option', '-', 'COM_MODULES_OPTION_MENU_NONE');
+
+ if ($clientId == 0) {
+ $options[] = HTMLHelper::_('select.option', '1', 'COM_MODULES_OPTION_MENU_INCLUDE');
+ $options[] = HTMLHelper::_('select.option', '-1', 'COM_MODULES_OPTION_MENU_EXCLUDE');
+ }
+
+ return $options;
+ }
+
+ /**
+ * Return a translated module position name
+ *
+ * @param integer $clientId Application client id 0: site | 1: admin
+ * @param string $template Template name
+ * @param string $position Position name
+ *
+ * @return string Return a translated position name
+ *
+ * @since 3.0
+ */
+ public static function getTranslatedModulePosition($clientId, $template, $position)
+ {
+ // Template translation
+ $lang = Factory::getLanguage();
+ $path = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
+
+ $loaded = $lang->getPaths('tpl_' . $template . '.sys');
+
+ // Only load the template's language file if it hasn't been already
+ if (!$loaded) {
+ $lang->load('tpl_' . $template . '.sys', $path, null, false, false)
+ || $lang->load('tpl_' . $template . '.sys', $path . '/templates/' . $template, null, false, false)
+ || $lang->load('tpl_' . $template . '.sys', $path, $lang->getDefault(), false, false)
+ || $lang->load('tpl_' . $template . '.sys', $path . '/templates/' . $template, $lang->getDefault(), false, false);
+ }
+
+ $langKey = strtoupper('TPL_' . $template . '_POSITION_' . $position);
+ $text = Text::_($langKey);
+
+ // Avoid untranslated strings
+ if (!self::isTranslatedText($langKey, $text)) {
+ // Modules component translation
+ $langKey = strtoupper('COM_MODULES_POSITION_' . $position);
+ $text = Text::_($langKey);
+
+ // Avoid untranslated strings
+ if (!self::isTranslatedText($langKey, $text)) {
+ // Try to humanize the position name
+ $text = ucfirst(preg_replace('/^' . $template . '\-/', '', $position));
+ $text = ucwords(str_replace(array('-', '_'), ' ', $text));
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Check if the string was translated
+ *
+ * @param string $langKey Language file text key
+ * @param string $text The "translated" text to be checked
+ *
+ * @return boolean Return true for translated text
+ *
+ * @since 3.0
+ */
+ public static function isTranslatedText($langKey, $text)
+ {
+ return $text !== $langKey;
+ }
+
+ /**
+ * Create and return a new Option
+ *
+ * @param string $value The option value [optional]
+ * @param string $text The option text [optional]
+ *
+ * @return object The option as an object (\stdClass instance)
+ *
+ * @since 3.0
+ */
+ public static function createOption($value = '', $text = '')
+ {
+ if (empty($text)) {
+ $text = $value;
+ }
+
+ $option = new \stdClass();
+ $option->value = $value;
+ $option->text = $text;
+
+ return $option;
+ }
+
+ /**
+ * Create and return a new Option Group
+ *
+ * @param string $label Value and label for group [optional]
+ * @param array $options Array of options to insert into group [optional]
+ *
+ * @return array Return the new group as an array
+ *
+ * @since 3.0
+ */
+ public static function createOptionGroup($label = '', $options = array())
+ {
+ $group = array();
+ $group['value'] = $label;
+ $group['text'] = $label;
+ $group['items'] = $options;
+
+ return $group;
+ }
}
diff --git a/administrator/components/com_modules/src/Model/ModuleModel.php b/administrator/components/com_modules/src/Model/ModuleModel.php
index fc5c49137da52..ab36763da9ab9 100644
--- a/administrator/components/com_modules/src/Model/ModuleModel.php
+++ b/administrator/components/com_modules/src/Model/ModuleModel.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage',
- );
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- */
- public function __construct($config = array())
- {
- $config = array_merge(
- array(
- 'event_after_delete' => 'onExtensionAfterDelete',
- 'event_after_save' => 'onExtensionAfterSave',
- 'event_before_delete' => 'onExtensionBeforeDelete',
- 'event_before_save' => 'onExtensionBeforeSave',
- 'events_map' => array(
- 'save' => 'extension',
- 'delete' => 'extension'
- )
- ), $config
- );
-
- parent::__construct($config);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('id');
-
- if (!$pk)
- {
- if ($extensionId = (int) $app->getUserState('com_modules.add.module.extension_id'))
- {
- $this->setState('extension.id', $extensionId);
- }
- }
-
- $this->setState('module.id', $pk);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_modules');
- $this->setState('params', $params);
- }
-
- /**
- * Batch copy modules to a new position or current.
- *
- * @param integer $value The new value matching a module position.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- protected function batchCopy($value, $pks, $contexts)
- {
- // Set the variables
- $user = Factory::getUser();
- $table = $this->getTable();
- $newIds = array();
-
- foreach ($pks as $pk)
- {
- if ($user->authorise('core.create', 'com_modules'))
- {
- $table->reset();
- $table->load($pk);
-
- // Set the new position
- if ($value == 'noposition')
- {
- $position = '';
- }
- elseif ($value == 'nochange')
- {
- $position = $table->position;
- }
- else
- {
- $position = $value;
- }
-
- $table->position = $position;
-
- // Copy of the Asset ID
- $oldAssetId = $table->asset_id;
-
- // Alter the title if necessary
- $data = $this->generateNewTitle(0, $table->title, $table->position);
- $table->title = $data['0'];
-
- // Reset the ID because we are making a copy
- $table->id = 0;
-
- // Unpublish the new module
- $table->published = 0;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Get the new item ID
- $newId = $table->get('id');
-
- // Add the new ID to the array
- $newIds[$pk] = $newId;
-
- // Now we need to handle the module assignments
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('menuid'))
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = :moduleid')
- ->bind(':moduleid', $pk, ParameterType::INTEGER);
- $db->setQuery($query);
- $menus = $db->loadColumn();
-
- // Insert the new records into the table
- foreach ($menus as $i => $menu)
- {
- $query->clear()
- ->insert($db->quoteName('#__modules_menu'))
- ->columns($db->quoteName(['moduleid', 'menuid']))
- ->values(implode(', ', [':newid' . $i, ':menu' . $i]))
- ->bind(':newid' . $i, $newId, ParameterType::INTEGER)
- ->bind(':menu' . $i, $menu, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- }
-
- // Copy rules
- $query->clear()
- ->update($db->quoteName('#__assets', 't'))
- ->join('INNER', $db->quoteName('#__assets', 's') .
- ' ON ' . $db->quoteName('s.id') . ' = ' . $oldAssetId
- )
- ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules'))
- ->where($db->quoteName('t.id') . ' = ' . $table->asset_id);
-
- $db->setQuery($query)->execute();
- }
- else
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return $newIds;
- }
-
- /**
- * Batch move modules to a new position or current.
- *
- * @param integer $value The new value matching a module position.
- * @param array $pks An array of row IDs.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- protected function batchMove($value, $pks, $contexts)
- {
- // Set the variables
- $user = Factory::getUser();
- $table = $this->getTable();
-
- foreach ($pks as $pk)
- {
- if ($user->authorise('core.edit', 'com_modules'))
- {
- $table->reset();
- $table->load($pk);
-
- // Set the new position
- if ($value == 'noposition')
- {
- $position = '';
- }
- elseif ($value == 'nochange')
- {
- $position = $table->position;
- }
- else
- {
- $position = $value;
- }
-
- $table->position = $position;
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- else
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
-
- return false;
- }
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to test whether a record can have its state edited.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 3.2
- */
- protected function canEditState($record)
- {
- // Check for existing module.
- if (!empty($record->id))
- {
- return Factory::getUser()->authorise('core.edit.state', 'com_modules.module.' . (int) $record->id);
- }
-
- // Default to component settings if module not known.
- return parent::canEditState($record);
- }
-
- /**
- * Method to delete rows.
- *
- * @param array &$pks An array of item ids.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function delete(&$pks)
- {
- $app = Factory::getApplication();
- $pks = (array) $pks;
- $user = Factory::getUser();
- $table = $this->getTable();
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the on delete events.
- PluginHelper::importPlugin($this->events_map['delete']);
-
- // Iterate the items to delete each one.
- foreach ($pks as $pk)
- {
- if ($table->load($pk))
- {
- // Access checks.
- if (!$user->authorise('core.delete', 'com_modules.module.' . (int) $pk) || $table->published != -2)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- // Trigger the before delete event.
- $result = $app->triggerEvent($this->event_before_delete, array($context, $table));
-
- if (in_array(false, $result, true) || !$table->delete($pk))
- {
- throw new \Exception($table->getError());
- }
- else
- {
- // Delete the menu assignments
- $pk = (int) $pk;
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = :moduleid')
- ->bind(':moduleid', $pk, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- // Trigger the after delete event.
- $app->triggerEvent($this->event_after_delete, array($context, $table));
- }
-
- // Clear module cache
- parent::cleanCache($table->module);
- }
- else
- {
- throw new \Exception($table->getError());
- }
- }
-
- // Clear modules cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to duplicate modules.
- *
- * @param array &$pks An array of primary key IDs.
- *
- * @return boolean Boolean true on success
- *
- * @since 1.6
- * @throws \Exception
- */
- public function duplicate(&$pks)
- {
- $user = Factory::getUser();
- $db = $this->getDatabase();
-
- // Access checks.
- if (!$user->authorise('core.create', 'com_modules'))
- {
- throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
- }
-
- $table = $this->getTable();
-
- foreach ($pks as $pk)
- {
- if ($table->load($pk, true))
- {
- // Reset the id to create a new record.
- $table->id = 0;
-
- // Alter the title.
- $m = null;
-
- if (preg_match('#\((\d+)\)$#', $table->title, $m))
- {
- $table->title = preg_replace('#\(\d+\)$#', '(' . ($m[1] + 1) . ')', $table->title);
- }
-
- $data = $this->generateNewTitle(0, $table->title, $table->position);
- $table->title = $data[0];
-
- // Unpublish duplicate module
- $table->published = 0;
-
- if (!$table->check() || !$table->store())
- {
- throw new \Exception($table->getError());
- }
-
- $pk = (int) $pk;
- $query = $db->getQuery(true)
- ->select($db->quoteName('menuid'))
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = :moduleid')
- ->bind(':moduleid', $pk, ParameterType::INTEGER);
-
- $db->setQuery($query);
- $rows = $db->loadColumn();
-
- foreach ($rows as $menuid)
- {
- $tuples[] = (int) $table->id . ',' . (int) $menuid;
- }
- }
- else
- {
- throw new \Exception($table->getError());
- }
- }
-
- if (!empty($tuples))
- {
- // Module-Menu Mapping: Do it in one query
- $query = $db->getQuery(true)
- ->insert($db->quoteName('#__modules_menu'))
- ->columns($db->quoteName(array('moduleid', 'menuid')))
- ->values($tuples);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
- }
-
- // Clear modules cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the title.
- *
- * @param integer $categoryId The id of the category. Not used here.
- * @param string $title The title.
- * @param string $position The position.
- *
- * @return array Contains the modified title.
- *
- * @since 2.5
- */
- protected function generateNewTitle($categoryId, $title, $position)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('position' => $position, 'title' => $title)))
- {
- $title = StringHelper::increment($title);
- }
-
- return array($title);
- }
-
- /**
- * Method to get the client object
- *
- * @return void
- *
- * @since 1.6
- */
- public function &getClient()
- {
- return $this->_client;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // The folder and element vars are passed when saving the form.
- if (empty($data))
- {
- $item = $this->getItem();
- $clientId = $item->client_id;
- $module = $item->module;
- $id = $item->id;
- }
- else
- {
- $clientId = ArrayHelper::getValue($data, 'client_id');
- $module = ArrayHelper::getValue($data, 'module');
- $id = ArrayHelper::getValue($data, 'id');
- }
-
- // Add the default fields directory
- $baseFolder = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
- Form::addFieldPath($baseFolder . '/modules/' . $module . '/field');
-
- // These variables are used to add data from the plugin XML files.
- $this->setState('item.client_id', $clientId);
- $this->setState('item.module', $module);
-
- // Get the form.
- if ($clientId == 1)
- {
- $form = $this->loadForm('com_modules.module.admin', 'moduleadmin', array('control' => 'jform', 'load_data' => $loadData), true);
-
- // Display language field to filter admin custom menus per language
- if (!ModuleHelper::isAdminMultilang())
- {
- $form->setFieldAttribute('language', 'type', 'hidden');
- }
- }
- else
- {
- $form = $this->loadForm('com_modules.module', 'module', array('control' => 'jform', 'load_data' => $loadData), true);
- }
-
- if (empty($form))
- {
- return false;
- }
-
- $user = Factory::getUser();
-
- /**
- * Check for existing module
- * Modify the form based on Edit State access controls.
- */
- if ($id != 0 && (!$user->authorise('core.edit.state', 'com_modules.module.' . (int) $id))
- || ($id == 0 && !$user->authorise('core.edit.state', 'com_modules')) )
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
- $form->setFieldAttribute('publish_up', 'disabled', 'true');
- $form->setFieldAttribute('publish_down', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- $form->setFieldAttribute('publish_up', 'filter', 'unset');
- $form->setFieldAttribute('publish_down', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- $app = Factory::getApplication();
-
- // Check the session for previously entered form data.
- $data = $app->getUserState('com_modules.edit.module.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Pre-select some filters (Status, Module Position, Language, Access Level) in edit form if those have been selected in Module Manager
- if (!$data->id)
- {
- $clientId = $app->input->getInt('client_id', 0);
- $filters = (array) $app->getUserState('com_modules.modules.' . $clientId . '.filter');
- $data->set('published', $app->input->getInt('published', ((isset($filters['state']) && $filters['state'] !== '') ? $filters['state'] : null)));
- $data->set('position', $app->input->getInt('position', (!empty($filters['position']) ? $filters['position'] : null)));
- $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
- $data->set('access', $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access'))));
- }
-
- // Avoid to delete params of a second module opened in a new browser tab while new one is not saved yet.
- if (empty($data->params))
- {
- // This allows us to inject parameter settings into a new module.
- $params = $app->getUserState('com_modules.add.module.params');
-
- if (is_array($params))
- {
- $data->set('params', $params);
- }
- }
- }
-
- $this->preprocessData('com_modules.module', $data);
-
- return $data;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- $pk = (!empty($pk)) ? (int) $pk : (int) $this->getState('module.id');
- $db = $this->getDatabase();
-
- if (!isset($this->_cache[$pk]))
- {
- // Get a row instance.
- $table = $this->getTable();
-
- // Attempt to load the row.
- $return = $table->load($pk);
-
- // Check for a table object error.
- if ($return === false && $error = $table->getError())
- {
- $this->setError($error);
-
- return false;
- }
-
- // Check if we are creating a new extension.
- if (empty($pk))
- {
- if ($extensionId = (int) $this->getState('extension.id'))
- {
- $query = $db->getQuery(true)
- ->select($db->quoteName(['element', 'client_id']))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('extension_id') . ' = :extensionid')
- ->where($db->quoteName('type') . ' = ' . $db->quote('module'))
- ->bind(':extensionid', $extensionId, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $extension = $db->loadObject();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (empty($extension))
- {
- $this->setError('COM_MODULES_ERROR_CANNOT_FIND_MODULE');
-
- return false;
- }
-
- // Extension found, prime some module values.
- $table->module = $extension->element;
- $table->client_id = $extension->client_id;
- }
- else
- {
- Factory::getApplication()->redirect(Route::_('index.php?option=com_modules&view=modules', false));
-
- return false;
- }
- }
-
- // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
- $properties = $table->getProperties(1);
- $this->_cache[$pk] = ArrayHelper::toObject($properties, CMSObject::class);
-
- // Convert the params field to an array.
- $registry = new Registry($table->params);
- $this->_cache[$pk]->params = $registry->toArray();
-
- // Determine the page assignment mode.
- $query = $db->getQuery(true)
- ->select($db->quoteName('menuid'))
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = :moduleid')
- ->bind(':moduleid', $pk, ParameterType::INTEGER);
- $db->setQuery($query);
- $assigned = $db->loadColumn();
-
- if (empty($pk))
- {
- // If this is a new module, assign to all pages.
- $assignment = 0;
- }
- elseif (empty($assigned))
- {
- // For an existing module it is assigned to none.
- $assignment = '-';
- }
- else
- {
- if ($assigned[0] > 0)
- {
- $assignment = 1;
- }
- elseif ($assigned[0] < 0)
- {
- $assignment = -1;
- }
- else
- {
- $assignment = 0;
- }
- }
-
- $this->_cache[$pk]->assigned = $assigned;
- $this->_cache[$pk]->assignment = $assignment;
-
- // Get the module XML.
- $client = ApplicationHelper::getClientInfo($table->client_id);
- $path = Path::clean($client->path . '/modules/' . $table->module . '/' . $table->module . '.xml');
-
- if (file_exists($path))
- {
- $this->_cache[$pk]->xml = simplexml_load_file($path);
- }
- else
- {
- $this->_cache[$pk]->xml = null;
- }
- }
-
- return $this->_cache[$pk];
- }
-
- /**
- * Get the necessary data to load an item help screen.
- *
- * @return object An object with key, url, and local properties for loading the item help screen.
- *
- * @since 1.6
- */
- public function getHelp()
- {
- return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
- }
-
- /**
- * Returns a reference to the a Table object, always creating it.
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- *
- * @since 1.6
- */
- public function getTable($type = 'Module', $prefix = 'JTable', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
-
- /**
- * Prepare and sanitise the table prior to saving.
- *
- * @param Table $table The database object
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- $table->title = htmlspecialchars_decode($table->title, ENT_QUOTES);
- $table->position = trim($table->position);
- }
-
- /**
- * Method to preprocess the form
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error loading the form.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $lang = Factory::getLanguage();
- $clientId = $this->getState('item.client_id');
- $module = $this->getState('item.module');
-
- $client = ApplicationHelper::getClientInfo($clientId);
- $formFile = Path::clean($client->path . '/modules/' . $module . '/' . $module . '.xml');
-
- // Load the core and/or local language file(s).
- $lang->load($module, $client->path)
- || $lang->load($module, $client->path . '/modules/' . $module);
-
- if (file_exists($formFile))
- {
- // Get the module form.
- if (!$form->loadFile($formFile, false, '//config'))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Get the help data from the XML file if present.
- $help = $xml->xpath('/extension/help');
-
- if (!empty($help))
- {
- $helpKey = trim((string) $help[0]['key']);
- $helpURL = trim((string) $help[0]['url']);
-
- $this->helpKey = $helpKey ?: $this->helpKey;
- $this->helpURL = $helpURL ?: $this->helpURL;
- }
- }
-
- // Load the default advanced params
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/models/forms');
- $form->loadFile('advanced', false);
-
- // Load chrome specific params for global files
- $chromePath = JPATH_SITE . '/layouts/chromes';
- $chromeFormFiles = Folder::files($chromePath, '.*\.xml');
-
- if ($chromeFormFiles)
- {
- Form::addFormPath($chromePath);
-
- foreach ($chromeFormFiles as $formFile)
- {
- $form->loadFile(basename($formFile, '.xml'), false);
- }
- }
-
- // Load chrome specific params for template files
- $templates = ModulesHelper::getTemplates($clientId);
-
- foreach ($templates as $template)
- {
- $chromePath = $client->path . '/templates/' . $template->element . '/html/layouts/chromes';
-
- // Skip if there is no chrome folder in that template.
- if (!is_dir($chromePath))
- {
- continue;
- }
-
- $chromeFormFiles = Folder::files($chromePath, '.*\.xml');
-
- if ($chromeFormFiles)
- {
- Form::addFormPath($chromePath);
-
- foreach ($chromeFormFiles as $formFile)
- {
- $form->loadFile(basename($formFile, '.xml'), false);
- }
- }
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Loads ContentHelper for filters before validating data.
- *
- * @param object $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the group(defaults to null).
- *
- * @return mixed Array of filtered data if valid, false otherwise.
- *
- * @since 1.1
- */
- public function validate($form, $data, $group = null)
- {
- if (!Factory::getUser()->authorise('core.admin', 'com_modules'))
- {
- if (isset($data['rules']))
- {
- unset($data['rules']);
- }
- }
-
- return parent::validate($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- $input = Factory::getApplication()->input;
- $table = $this->getTable();
- $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('module.id');
- $isNew = true;
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the save event.
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Load the row if saving an existing record.
- if ($pk > 0)
- {
- $table->load($pk);
- $isNew = false;
- }
-
- // Alter the title and published state for Save as Copy
- if ($input->get('task') == 'save2copy')
- {
- $orig_table = clone $this->getTable();
- $orig_table->load((int) $input->getInt('id'));
- $data['published'] = 0;
-
- if ($data['title'] == $orig_table->title)
- {
- $data['title'] = StringHelper::increment($data['title']);
- }
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Prepare the row for saving
- $this->prepareTable($table);
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew));
-
- if (in_array(false, $result, true))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Store the data.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Process the menu link mappings.
- $assignment = $data['assignment'] ?? 0;
-
- $table->id = (int) $table->id;
-
- // Delete old module to menu item associations
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = :moduleid')
- ->bind(':moduleid', $table->id, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // If the assignment is numeric, then something is selected (otherwise it's none).
- if (is_numeric($assignment))
- {
- // Variable is numeric, but could be a string.
- $assignment = (int) $assignment;
-
- // Logic check: if no module excluded then convert to display on all.
- if ($assignment == -1 && empty($data['assigned']))
- {
- $assignment = 0;
- }
-
- // Check needed to stop a module being assigned to `All`
- // and other menu items resulting in a module being displayed twice.
- if ($assignment === 0)
- {
- // Assign new module to `all` menu item associations.
- $query->clear()
- ->insert($db->quoteName('#__modules_menu'))
- ->columns($db->quoteName(['moduleid', 'menuid']))
- ->values(implode(', ', [':moduleid', 0]))
- ->bind(':moduleid', $table->id, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- elseif (!empty($data['assigned']))
- {
- // Get the sign of the number.
- $sign = $assignment < 0 ? -1 : 1;
-
- $query->clear()
- ->insert($db->quoteName('#__modules_menu'))
- ->columns($db->quoteName(array('moduleid', 'menuid')));
-
- foreach ($data['assigned'] as &$pk)
- {
- $query->values((int) $table->id . ',' . (int) $pk * $sign);
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew));
-
- // Compute the extension id of this module in case the controller wants it.
- $query->clear()
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions', 'e'))
- ->join(
- 'LEFT',
- $db->quoteName('#__modules', 'm') . ' ON ' . $db->quoteName('e.client_id') . ' = ' . (int) $table->client_id .
- ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('m.module')
- )
- ->where($db->quoteName('m.id') . ' = :id')
- ->bind(':id', $table->id, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $extensionId = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- $this->setState('module.extension_id', $extensionId);
- $this->setState('module.id', $table->id);
-
- // Clear modules cache
- $this->cleanCache();
-
- // Clean module cache
- parent::cleanCache($table->module);
-
- return true;
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('client_id') . ' = ' . (int) $table->client_id,
- $db->quoteName('position') . ' = ' . $db->quote($table->position),
- ];
- }
-
- /**
- * Custom clean cache method for different clients
- *
- * @param string $group The name of the plugin group to import (defaults to null).
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_modules');
- }
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.4
+ */
+ public $typeAlias = 'com_modules.module';
+
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_MODULES';
+
+ /**
+ * @var string The help screen key for the module.
+ * @since 1.6
+ */
+ protected $helpKey = '';
+
+ /**
+ * @var string The help screen base URL for the module.
+ * @since 1.6
+ */
+ protected $helpURL;
+
+ /**
+ * Batch copy/move command. If set to false,
+ * the batch copy/move command is not supported
+ *
+ * @var string
+ */
+ protected $batch_copymove = 'position_id';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ */
+ public function __construct($config = array())
+ {
+ $config = array_merge(
+ array(
+ 'event_after_delete' => 'onExtensionAfterDelete',
+ 'event_after_save' => 'onExtensionAfterSave',
+ 'event_before_delete' => 'onExtensionBeforeDelete',
+ 'event_before_save' => 'onExtensionBeforeSave',
+ 'events_map' => array(
+ 'save' => 'extension',
+ 'delete' => 'extension'
+ )
+ ),
+ $config
+ );
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+
+ if (!$pk) {
+ if ($extensionId = (int) $app->getUserState('com_modules.add.module.extension_id')) {
+ $this->setState('extension.id', $extensionId);
+ }
+ }
+
+ $this->setState('module.id', $pk);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_modules');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Batch copy modules to a new position or current.
+ *
+ * @param integer $value The new value matching a module position.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ protected function batchCopy($value, $pks, $contexts)
+ {
+ // Set the variables
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $newIds = array();
+
+ foreach ($pks as $pk) {
+ if ($user->authorise('core.create', 'com_modules')) {
+ $table->reset();
+ $table->load($pk);
+
+ // Set the new position
+ if ($value == 'noposition') {
+ $position = '';
+ } elseif ($value == 'nochange') {
+ $position = $table->position;
+ } else {
+ $position = $value;
+ }
+
+ $table->position = $position;
+
+ // Copy of the Asset ID
+ $oldAssetId = $table->asset_id;
+
+ // Alter the title if necessary
+ $data = $this->generateNewTitle(0, $table->title, $table->position);
+ $table->title = $data['0'];
+
+ // Reset the ID because we are making a copy
+ $table->id = 0;
+
+ // Unpublish the new module
+ $table->published = 0;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Get the new item ID
+ $newId = $table->get('id');
+
+ // Add the new ID to the array
+ $newIds[$pk] = $newId;
+
+ // Now we need to handle the module assignments
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('menuid'))
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = :moduleid')
+ ->bind(':moduleid', $pk, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $menus = $db->loadColumn();
+
+ // Insert the new records into the table
+ foreach ($menus as $i => $menu) {
+ $query->clear()
+ ->insert($db->quoteName('#__modules_menu'))
+ ->columns($db->quoteName(['moduleid', 'menuid']))
+ ->values(implode(', ', [':newid' . $i, ':menu' . $i]))
+ ->bind(':newid' . $i, $newId, ParameterType::INTEGER)
+ ->bind(':menu' . $i, $menu, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ }
+
+ // Copy rules
+ $query->clear()
+ ->update($db->quoteName('#__assets', 't'))
+ ->join('INNER', $db->quoteName('#__assets', 's') .
+ ' ON ' . $db->quoteName('s.id') . ' = ' . $oldAssetId)
+ ->set($db->quoteName('t.rules') . ' = ' . $db->quoteName('s.rules'))
+ ->where($db->quoteName('t.id') . ' = ' . $table->asset_id);
+
+ $db->setQuery($query)->execute();
+ } else {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return $newIds;
+ }
+
+ /**
+ * Batch move modules to a new position or current.
+ *
+ * @param integer $value The new value matching a module position.
+ * @param array $pks An array of row IDs.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ protected function batchMove($value, $pks, $contexts)
+ {
+ // Set the variables
+ $user = Factory::getUser();
+ $table = $this->getTable();
+
+ foreach ($pks as $pk) {
+ if ($user->authorise('core.edit', 'com_modules')) {
+ $table->reset();
+ $table->load($pk);
+
+ // Set the new position
+ if ($value == 'noposition') {
+ $position = '';
+ } elseif ($value == 'nochange') {
+ $position = $table->position;
+ } else {
+ $position = $value;
+ }
+
+ $table->position = $position;
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ } else {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
+
+ return false;
+ }
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can have its state edited.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 3.2
+ */
+ protected function canEditState($record)
+ {
+ // Check for existing module.
+ if (!empty($record->id)) {
+ return Factory::getUser()->authorise('core.edit.state', 'com_modules.module.' . (int) $record->id);
+ }
+
+ // Default to component settings if module not known.
+ return parent::canEditState($record);
+ }
+
+ /**
+ * Method to delete rows.
+ *
+ * @param array &$pks An array of item ids.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function delete(&$pks)
+ {
+ $app = Factory::getApplication();
+ $pks = (array) $pks;
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the on delete events.
+ PluginHelper::importPlugin($this->events_map['delete']);
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $pk) {
+ if ($table->load($pk)) {
+ // Access checks.
+ if (!$user->authorise('core.delete', 'com_modules.module.' . (int) $pk) || $table->published != -2) {
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ // Trigger the before delete event.
+ $result = $app->triggerEvent($this->event_before_delete, array($context, $table));
+
+ if (in_array(false, $result, true) || !$table->delete($pk)) {
+ throw new \Exception($table->getError());
+ } else {
+ // Delete the menu assignments
+ $pk = (int) $pk;
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = :moduleid')
+ ->bind(':moduleid', $pk, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Trigger the after delete event.
+ $app->triggerEvent($this->event_after_delete, array($context, $table));
+ }
+
+ // Clear module cache
+ parent::cleanCache($table->module);
+ } else {
+ throw new \Exception($table->getError());
+ }
+ }
+
+ // Clear modules cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to duplicate modules.
+ *
+ * @param array &$pks An array of primary key IDs.
+ *
+ * @return boolean Boolean true on success
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function duplicate(&$pks)
+ {
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+
+ // Access checks.
+ if (!$user->authorise('core.create', 'com_modules')) {
+ throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
+ }
+
+ $table = $this->getTable();
+
+ foreach ($pks as $pk) {
+ if ($table->load($pk, true)) {
+ // Reset the id to create a new record.
+ $table->id = 0;
+
+ // Alter the title.
+ $m = null;
+
+ if (preg_match('#\((\d+)\)$#', $table->title, $m)) {
+ $table->title = preg_replace('#\(\d+\)$#', '(' . ($m[1] + 1) . ')', $table->title);
+ }
+
+ $data = $this->generateNewTitle(0, $table->title, $table->position);
+ $table->title = $data[0];
+
+ // Unpublish duplicate module
+ $table->published = 0;
+
+ if (!$table->check() || !$table->store()) {
+ throw new \Exception($table->getError());
+ }
+
+ $pk = (int) $pk;
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('menuid'))
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = :moduleid')
+ ->bind(':moduleid', $pk, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+ $rows = $db->loadColumn();
+
+ foreach ($rows as $menuid) {
+ $tuples[] = (int) $table->id . ',' . (int) $menuid;
+ }
+ } else {
+ throw new \Exception($table->getError());
+ }
+ }
+
+ if (!empty($tuples)) {
+ // Module-Menu Mapping: Do it in one query
+ $query = $db->getQuery(true)
+ ->insert($db->quoteName('#__modules_menu'))
+ ->columns($db->quoteName(array('moduleid', 'menuid')))
+ ->values($tuples);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+ }
+
+ // Clear modules cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the title.
+ *
+ * @param integer $categoryId The id of the category. Not used here.
+ * @param string $title The title.
+ * @param string $position The position.
+ *
+ * @return array Contains the modified title.
+ *
+ * @since 2.5
+ */
+ protected function generateNewTitle($categoryId, $title, $position)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('position' => $position, 'title' => $title))) {
+ $title = StringHelper::increment($title);
+ }
+
+ return array($title);
+ }
+
+ /**
+ * Method to get the client object
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function &getClient()
+ {
+ return $this->_client;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // The folder and element vars are passed when saving the form.
+ if (empty($data)) {
+ $item = $this->getItem();
+ $clientId = $item->client_id;
+ $module = $item->module;
+ $id = $item->id;
+ } else {
+ $clientId = ArrayHelper::getValue($data, 'client_id');
+ $module = ArrayHelper::getValue($data, 'module');
+ $id = ArrayHelper::getValue($data, 'id');
+ }
+
+ // Add the default fields directory
+ $baseFolder = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
+ Form::addFieldPath($baseFolder . '/modules/' . $module . '/field');
+
+ // These variables are used to add data from the plugin XML files.
+ $this->setState('item.client_id', $clientId);
+ $this->setState('item.module', $module);
+
+ // Get the form.
+ if ($clientId == 1) {
+ $form = $this->loadForm('com_modules.module.admin', 'moduleadmin', array('control' => 'jform', 'load_data' => $loadData), true);
+
+ // Display language field to filter admin custom menus per language
+ if (!ModuleHelper::isAdminMultilang()) {
+ $form->setFieldAttribute('language', 'type', 'hidden');
+ }
+ } else {
+ $form = $this->loadForm('com_modules.module', 'module', array('control' => 'jform', 'load_data' => $loadData), true);
+ }
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ /**
+ * Check for existing module
+ * Modify the form based on Edit State access controls.
+ */
+ if (
+ $id != 0 && (!$user->authorise('core.edit.state', 'com_modules.module.' . (int) $id))
+ || ($id == 0 && !$user->authorise('core.edit.state', 'com_modules'))
+ ) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+ $form->setFieldAttribute('publish_up', 'disabled', 'true');
+ $form->setFieldAttribute('publish_down', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ $form->setFieldAttribute('publish_up', 'filter', 'unset');
+ $form->setFieldAttribute('publish_down', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ $app = Factory::getApplication();
+
+ // Check the session for previously entered form data.
+ $data = $app->getUserState('com_modules.edit.module.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Pre-select some filters (Status, Module Position, Language, Access Level) in edit form if those have been selected in Module Manager
+ if (!$data->id) {
+ $clientId = $app->input->getInt('client_id', 0);
+ $filters = (array) $app->getUserState('com_modules.modules.' . $clientId . '.filter');
+ $data->set('published', $app->input->getInt('published', ((isset($filters['state']) && $filters['state'] !== '') ? $filters['state'] : null)));
+ $data->set('position', $app->input->getInt('position', (!empty($filters['position']) ? $filters['position'] : null)));
+ $data->set('language', $app->input->getString('language', (!empty($filters['language']) ? $filters['language'] : null)));
+ $data->set('access', $app->input->getInt('access', (!empty($filters['access']) ? $filters['access'] : $app->get('access'))));
+ }
+
+ // Avoid to delete params of a second module opened in a new browser tab while new one is not saved yet.
+ if (empty($data->params)) {
+ // This allows us to inject parameter settings into a new module.
+ $params = $app->getUserState('com_modules.add.module.params');
+
+ if (is_array($params)) {
+ $data->set('params', $params);
+ }
+ }
+ }
+
+ $this->preprocessData('com_modules.module', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ $pk = (!empty($pk)) ? (int) $pk : (int) $this->getState('module.id');
+ $db = $this->getDatabase();
+
+ if (!isset($this->_cache[$pk])) {
+ // Get a row instance.
+ $table = $this->getTable();
+
+ // Attempt to load the row.
+ $return = $table->load($pk);
+
+ // Check for a table object error.
+ if ($return === false && $error = $table->getError()) {
+ $this->setError($error);
+
+ return false;
+ }
+
+ // Check if we are creating a new extension.
+ if (empty($pk)) {
+ if ($extensionId = (int) $this->getState('extension.id')) {
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['element', 'client_id']))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('extension_id') . ' = :extensionid')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('module'))
+ ->bind(':extensionid', $extensionId, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $extension = $db->loadObject();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (empty($extension)) {
+ $this->setError('COM_MODULES_ERROR_CANNOT_FIND_MODULE');
+
+ return false;
+ }
+
+ // Extension found, prime some module values.
+ $table->module = $extension->element;
+ $table->client_id = $extension->client_id;
+ } else {
+ Factory::getApplication()->redirect(Route::_('index.php?option=com_modules&view=modules', false));
+
+ return false;
+ }
+ }
+
+ // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
+ $properties = $table->getProperties(1);
+ $this->_cache[$pk] = ArrayHelper::toObject($properties, CMSObject::class);
+
+ // Convert the params field to an array.
+ $registry = new Registry($table->params);
+ $this->_cache[$pk]->params = $registry->toArray();
+
+ // Determine the page assignment mode.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('menuid'))
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = :moduleid')
+ ->bind(':moduleid', $pk, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $assigned = $db->loadColumn();
+
+ if (empty($pk)) {
+ // If this is a new module, assign to all pages.
+ $assignment = 0;
+ } elseif (empty($assigned)) {
+ // For an existing module it is assigned to none.
+ $assignment = '-';
+ } else {
+ if ($assigned[0] > 0) {
+ $assignment = 1;
+ } elseif ($assigned[0] < 0) {
+ $assignment = -1;
+ } else {
+ $assignment = 0;
+ }
+ }
+
+ $this->_cache[$pk]->assigned = $assigned;
+ $this->_cache[$pk]->assignment = $assignment;
+
+ // Get the module XML.
+ $client = ApplicationHelper::getClientInfo($table->client_id);
+ $path = Path::clean($client->path . '/modules/' . $table->module . '/' . $table->module . '.xml');
+
+ if (file_exists($path)) {
+ $this->_cache[$pk]->xml = simplexml_load_file($path);
+ } else {
+ $this->_cache[$pk]->xml = null;
+ }
+ }
+
+ return $this->_cache[$pk];
+ }
+
+ /**
+ * Get the necessary data to load an item help screen.
+ *
+ * @return object An object with key, url, and local properties for loading the item help screen.
+ *
+ * @since 1.6
+ */
+ public function getHelp()
+ {
+ return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
+ }
+
+ /**
+ * Returns a reference to the a Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'Module', $prefix = 'JTable', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+
+ /**
+ * Prepare and sanitise the table prior to saving.
+ *
+ * @param Table $table The database object
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ $table->title = htmlspecialchars_decode($table->title, ENT_QUOTES);
+ $table->position = trim($table->position);
+ }
+
+ /**
+ * Method to preprocess the form
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error loading the form.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $lang = Factory::getLanguage();
+ $clientId = $this->getState('item.client_id');
+ $module = $this->getState('item.module');
+
+ $client = ApplicationHelper::getClientInfo($clientId);
+ $formFile = Path::clean($client->path . '/modules/' . $module . '/' . $module . '.xml');
+
+ // Load the core and/or local language file(s).
+ $lang->load($module, $client->path)
+ || $lang->load($module, $client->path . '/modules/' . $module);
+
+ if (file_exists($formFile)) {
+ // Get the module form.
+ if (!$form->loadFile($formFile, false, '//config')) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Get the help data from the XML file if present.
+ $help = $xml->xpath('/extension/help');
+
+ if (!empty($help)) {
+ $helpKey = trim((string) $help[0]['key']);
+ $helpURL = trim((string) $help[0]['url']);
+
+ $this->helpKey = $helpKey ?: $this->helpKey;
+ $this->helpURL = $helpURL ?: $this->helpURL;
+ }
+ }
+
+ // Load the default advanced params
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/models/forms');
+ $form->loadFile('advanced', false);
+
+ // Load chrome specific params for global files
+ $chromePath = JPATH_SITE . '/layouts/chromes';
+ $chromeFormFiles = Folder::files($chromePath, '.*\.xml');
+
+ if ($chromeFormFiles) {
+ Form::addFormPath($chromePath);
+
+ foreach ($chromeFormFiles as $formFile) {
+ $form->loadFile(basename($formFile, '.xml'), false);
+ }
+ }
+
+ // Load chrome specific params for template files
+ $templates = ModulesHelper::getTemplates($clientId);
+
+ foreach ($templates as $template) {
+ $chromePath = $client->path . '/templates/' . $template->element . '/html/layouts/chromes';
+
+ // Skip if there is no chrome folder in that template.
+ if (!is_dir($chromePath)) {
+ continue;
+ }
+
+ $chromeFormFiles = Folder::files($chromePath, '.*\.xml');
+
+ if ($chromeFormFiles) {
+ Form::addFormPath($chromePath);
+
+ foreach ($chromeFormFiles as $formFile) {
+ $form->loadFile(basename($formFile, '.xml'), false);
+ }
+ }
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Loads ContentHelper for filters before validating data.
+ *
+ * @param object $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the group(defaults to null).
+ *
+ * @return mixed Array of filtered data if valid, false otherwise.
+ *
+ * @since 1.1
+ */
+ public function validate($form, $data, $group = null)
+ {
+ if (!Factory::getUser()->authorise('core.admin', 'com_modules')) {
+ if (isset($data['rules'])) {
+ unset($data['rules']);
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ $input = Factory::getApplication()->input;
+ $table = $this->getTable();
+ $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('module.id');
+ $isNew = true;
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the save event.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Load the row if saving an existing record.
+ if ($pk > 0) {
+ $table->load($pk);
+ $isNew = false;
+ }
+
+ // Alter the title and published state for Save as Copy
+ if ($input->get('task') == 'save2copy') {
+ $orig_table = clone $this->getTable();
+ $orig_table->load((int) $input->getInt('id'));
+ $data['published'] = 0;
+
+ if ($data['title'] == $orig_table->title) {
+ $data['title'] = StringHelper::increment($data['title']);
+ }
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Prepare the row for saving
+ $this->prepareTable($table);
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, $isNew));
+
+ if (in_array(false, $result, true)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Store the data.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Process the menu link mappings.
+ $assignment = $data['assignment'] ?? 0;
+
+ $table->id = (int) $table->id;
+
+ // Delete old module to menu item associations
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = :moduleid')
+ ->bind(':moduleid', $table->id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // If the assignment is numeric, then something is selected (otherwise it's none).
+ if (is_numeric($assignment)) {
+ // Variable is numeric, but could be a string.
+ $assignment = (int) $assignment;
+
+ // Logic check: if no module excluded then convert to display on all.
+ if ($assignment == -1 && empty($data['assigned'])) {
+ $assignment = 0;
+ }
+
+ // Check needed to stop a module being assigned to `All`
+ // and other menu items resulting in a module being displayed twice.
+ if ($assignment === 0) {
+ // Assign new module to `all` menu item associations.
+ $query->clear()
+ ->insert($db->quoteName('#__modules_menu'))
+ ->columns($db->quoteName(['moduleid', 'menuid']))
+ ->values(implode(', ', [':moduleid', 0]))
+ ->bind(':moduleid', $table->id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ } elseif (!empty($data['assigned'])) {
+ // Get the sign of the number.
+ $sign = $assignment < 0 ? -1 : 1;
+
+ $query->clear()
+ ->insert($db->quoteName('#__modules_menu'))
+ ->columns($db->quoteName(array('moduleid', 'menuid')));
+
+ foreach ($data['assigned'] as &$pk) {
+ $query->values((int) $table->id . ',' . (int) $pk * $sign);
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, $isNew));
+
+ // Compute the extension id of this module in case the controller wants it.
+ $query->clear()
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions', 'e'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__modules', 'm') . ' ON ' . $db->quoteName('e.client_id') . ' = ' . (int) $table->client_id .
+ ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('m.module')
+ )
+ ->where($db->quoteName('m.id') . ' = :id')
+ ->bind(':id', $table->id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $extensionId = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ $this->setState('module.extension_id', $extensionId);
+ $this->setState('module.id', $table->id);
+
+ // Clear modules cache
+ $this->cleanCache();
+
+ // Clean module cache
+ parent::cleanCache($table->module);
+
+ return true;
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('client_id') . ' = ' . (int) $table->client_id,
+ $db->quoteName('position') . ' = ' . $db->quote($table->position),
+ ];
+ }
+
+ /**
+ * Custom clean cache method for different clients
+ *
+ * @param string $group The name of the plugin group to import (defaults to null).
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_modules');
+ }
}
diff --git a/administrator/components/com_modules/src/Model/ModulesModel.php b/administrator/components/com_modules/src/Model/ModulesModel.php
index 5abc720bda5c5..48579f3f02359 100644
--- a/administrator/components/com_modules/src/Model/ModulesModel.php
+++ b/administrator/components/com_modules/src/Model/ModulesModel.php
@@ -1,4 +1,5 @@
input->get('layout', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout)
- {
- $this->context .= '.' . $layout;
- }
-
- // Make context client aware
- $this->context .= '.' . $app->input->get->getInt('client_id', 0);
-
- // Load the filter state.
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
- $this->setState('filter.position', $this->getUserStateFromRequest($this->context . '.filter.position', 'filter_position', '', 'string'));
- $this->setState('filter.module', $this->getUserStateFromRequest($this->context . '.filter.module', 'filter_module', '', 'string'));
- $this->setState('filter.menuitem', $this->getUserStateFromRequest($this->context . '.filter.menuitem', 'filter_menuitem', '', 'cmd'));
- $this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'cmd'));
-
- // If in modal layout on the frontend, state and language are always forced.
- if ($app->isClient('site') && $layout === 'modal')
- {
- $this->setState('filter.language', 'current');
- $this->setState('filter.state', 1);
- }
- // If in backend (modal or not) we get the same fields from the user request.
- else
- {
- $this->setState('filter.language', $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '', 'string'));
- $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'string'));
- }
-
- // Special case for the client id.
- if ($app->isClient('site') || $layout === 'modal')
- {
- $this->setState('client_id', 0);
- $clientId = 0;
- }
- else
- {
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $clientId = (!in_array($clientId, array(0, 1))) ? 0 : $clientId;
- $this->setState('client_id', $clientId);
- }
-
- // Use a different filter file when client is administrator
- if ($clientId == 1)
- {
- $this->filterFormName = 'filter_modulesadmin';
- }
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_modules');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('client_id');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.position');
- $id .= ':' . $this->getState('filter.module');
- $id .= ':' . $this->getState('filter.menuitem');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.language');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Returns an object list
- *
- * @param DatabaseQuery $query The query
- * @param int $limitstart Offset
- * @param int $limit The number of records
- *
- * @return array
- */
- protected function _getList($query, $limitstart = 0, $limit = 0)
- {
- $listOrder = $this->getState('list.ordering', 'a.position');
- $listDirn = $this->getState('list.direction', 'asc');
-
- $db = $this->getDatabase();
-
- // If ordering by fields that need translate we need to sort the array of objects after translating them.
- if (in_array($listOrder, array('pages', 'name')))
- {
- // Fetch the results.
- $db->setQuery($query);
- $result = $db->loadObjectList();
-
- // Translate the results.
- $this->translate($result);
-
- // Sort the array of translated objects.
- $result = ArrayHelper::sortObjects($result, $listOrder, strtolower($listDirn) == 'desc' ? -1 : 1, true, true);
-
- // Process pagination.
- $total = count($result);
- $this->cache[$this->getStoreId('getTotal')] = $total;
-
- if ($total < $limitstart)
- {
- $limitstart = 0;
- $this->setState('list.start', 0);
- }
-
- return array_slice($result, $limitstart, $limit ?: null);
- }
-
- // If ordering by fields that doesn't need translate just order the query.
- if ($listOrder === 'a.ordering')
- {
- $query->order($db->quoteName('a.position') . ' ASC')
- ->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
- }
- elseif ($listOrder === 'a.position')
- {
- $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn))
- ->order($db->quoteName('a.ordering') . ' ASC');
- }
- else
- {
- $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
- }
-
- // Process pagination.
- $result = parent::_getList($query, $limitstart, $limit);
-
- // Translate the results.
- $this->translate($result);
-
- return $result;
- }
-
- /**
- * Translate a list of objects
- *
- * @param array &$items The array of objects
- *
- * @return array The array of translated objects
- */
- protected function translate(&$items)
- {
- $lang = Factory::getLanguage();
- $clientPath = $this->getState('client_id') ? JPATH_ADMINISTRATOR : JPATH_SITE;
-
- foreach ($items as $item)
- {
- $extension = $item->module;
- $source = $clientPath . "/modules/$extension";
- $lang->load("$extension.sys", $clientPath)
- || $lang->load("$extension.sys", $source);
- $item->name = Text::_($item->name);
-
- if (is_null($item->pages))
- {
- $item->pages = Text::_('JNONE');
- }
- elseif ($item->pages < 0)
- {
- $item->pages = Text::_('COM_MODULES_ASSIGNED_VARIES_EXCEPT');
- }
- elseif ($item->pages > 0)
- {
- $item->pages = Text::_('COM_MODULES_ASSIGNED_VARIES_ONLY');
- }
- else
- {
- $item->pages = Text::_('JALL');
- }
- }
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.id, a.title, a.note, a.position, a.module, a.language,' .
- 'a.checked_out, a.checked_out_time, a.published AS published, e.enabled AS enabled, a.access, a.ordering, a.publish_up, a.publish_down'
- )
- );
-
- // From modules table.
- $query->from($db->quoteName('#__modules', 'a'));
-
- // Join over the language
- $query->select($db->quoteName('l.title', 'language_title'))
- ->select($db->quoteName('l.image', 'language_image'))
- ->join('LEFT', $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
-
- // Join over the users for the checked out user.
- $query->select($db->quoteName('uc.name', 'editor'))
- ->join('LEFT', $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
-
- // Join over the asset groups.
- $query->select($db->quoteName('ag.title', 'access_level'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'));
-
- // Join over the module menus
- $query->select('MIN(mm.menuid) AS pages')
- ->join('LEFT', $db->quoteName('#__modules_menu', 'mm') . ' ON ' . $db->quoteName('mm.moduleid') . ' = ' . $db->quoteName('a.id'));
-
- // Join over the extensions
- $query->select($db->quoteName('e.name', 'name'))
- ->join('LEFT', $db->quoteName('#__extensions', 'e') . ' ON ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('a.module'));
-
- // Group (careful with PostgreSQL)
- $query->group(
- 'a.id, a.title, a.note, a.position, a.module, a.language, a.checked_out, '
- . 'a.checked_out_time, a.published, a.access, a.ordering, l.title, l.image, uc.name, ag.title, e.name, '
- . 'l.lang_code, uc.id, ag.id, mm.moduleid, e.element, a.publish_up, a.publish_down, e.enabled'
- );
-
- // Filter by client.
- $clientId = (int) $this->getState('client_id');
- $query->where($db->quoteName('a.client_id') . ' = :aclientid')
- ->where($db->quoteName('e.client_id') . ' = :eclientid')
- ->bind(':aclientid', $clientId, ParameterType::INTEGER)
- ->bind(':eclientid', $clientId, ParameterType::INTEGER);
-
- // Filter by current user access level.
- $user = Factory::getUser();
-
- // Get the current user for authorisation checks
- if ($user->authorise('core.admin') !== true)
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- $access = (int) $access;
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Filter by published state.
- $state = $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.published') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif ($state === '')
- {
- $query->whereIn($db->quoteName('a.published'), [0, 1]);
- }
-
- // Filter by position.
- if ($position = $this->getState('filter.position'))
- {
- $position = ($position === 'none') ? '' : $position;
- $query->where($db->quoteName('a.position') . ' = :position')
- ->bind(':position', $position);
- }
-
- // Filter by module.
- if ($module = $this->getState('filter.module'))
- {
- $query->where($db->quoteName('a.module') . ' = :module')
- ->bind(':module', $module);
- }
-
- // Filter by menuitem id (only for site client).
- if ((int) $clientId === 0 && $menuItemId = $this->getState('filter.menuitem'))
- {
- // If user selected the modules not assigned to any page (menu item).
- if ((int) $menuItemId === -1)
- {
- $query->having('MIN(' . $db->quoteName('mm.menuid') . ') IS NULL');
- }
- // If user selected the modules assigned to some particular page (menu item).
- else
- {
- // Modules in "All" pages.
- $subQuery1 = $db->getQuery(true);
- $subQuery1->select('MIN(' . $db->quoteName('menuid') . ')')
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'));
-
- // Modules in "Selected" pages that have the chosen menu item id.
- $menuItemId = (int) $menuItemId;
- $minusMenuItemId = $menuItemId * -1;
- $subQuery2 = $db->getQuery(true);
- $subQuery2->select($db->quoteName('moduleid'))
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('menuid') . ' = :menuitemid2');
-
- // Modules in "All except selected" pages that doesn't have the chosen menu item id.
- $subQuery3 = $db->getQuery(true);
- $subQuery3->select($db->quoteName('moduleid'))
- ->from($db->quoteName('#__modules_menu'))
- ->where($db->quoteName('menuid') . ' = :menuitemid3');
-
- // Filter by modules assigned to the selected menu item.
- $query->where('(
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @see \JController
+ * @since 1.6
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'published', 'a.published', 'state',
+ 'access', 'a.access',
+ 'ag.title', 'access_level',
+ 'ordering', 'a.ordering',
+ 'module', 'a.module',
+ 'language', 'a.language',
+ 'l.title', 'language_title',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'client_id', 'a.client_id',
+ 'position', 'a.position',
+ 'pages',
+ 'name', 'e.name',
+ 'menuitem',
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.position', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $layout = $app->input->get('layout', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Make context client aware
+ $this->context .= '.' . $app->input->get->getInt('client_id', 0);
+
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+ $this->setState('filter.position', $this->getUserStateFromRequest($this->context . '.filter.position', 'filter_position', '', 'string'));
+ $this->setState('filter.module', $this->getUserStateFromRequest($this->context . '.filter.module', 'filter_module', '', 'string'));
+ $this->setState('filter.menuitem', $this->getUserStateFromRequest($this->context . '.filter.menuitem', 'filter_menuitem', '', 'cmd'));
+ $this->setState('filter.access', $this->getUserStateFromRequest($this->context . '.filter.access', 'filter_access', '', 'cmd'));
+
+ // If in modal layout on the frontend, state and language are always forced.
+ if ($app->isClient('site') && $layout === 'modal') {
+ $this->setState('filter.language', 'current');
+ $this->setState('filter.state', 1);
+ } else {
+ // If in backend (modal or not) we get the same fields from the user request.
+ $this->setState('filter.language', $this->getUserStateFromRequest($this->context . '.filter.language', 'filter_language', '', 'string'));
+ $this->setState('filter.state', $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'string'));
+ }
+
+ // Special case for the client id.
+ if ($app->isClient('site') || $layout === 'modal') {
+ $this->setState('client_id', 0);
+ $clientId = 0;
+ } else {
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $clientId = (!in_array($clientId, array(0, 1))) ? 0 : $clientId;
+ $this->setState('client_id', $clientId);
+ }
+
+ // Use a different filter file when client is administrator
+ if ($clientId == 1) {
+ $this->filterFormName = 'filter_modulesadmin';
+ }
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_modules');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('client_id');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.position');
+ $id .= ':' . $this->getState('filter.module');
+ $id .= ':' . $this->getState('filter.menuitem');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.language');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Returns an object list
+ *
+ * @param DatabaseQuery $query The query
+ * @param int $limitstart Offset
+ * @param int $limit The number of records
+ *
+ * @return array
+ */
+ protected function _getList($query, $limitstart = 0, $limit = 0)
+ {
+ $listOrder = $this->getState('list.ordering', 'a.position');
+ $listDirn = $this->getState('list.direction', 'asc');
+
+ $db = $this->getDatabase();
+
+ // If ordering by fields that need translate we need to sort the array of objects after translating them.
+ if (in_array($listOrder, array('pages', 'name'))) {
+ // Fetch the results.
+ $db->setQuery($query);
+ $result = $db->loadObjectList();
+
+ // Translate the results.
+ $this->translate($result);
+
+ // Sort the array of translated objects.
+ $result = ArrayHelper::sortObjects($result, $listOrder, strtolower($listDirn) == 'desc' ? -1 : 1, true, true);
+
+ // Process pagination.
+ $total = count($result);
+ $this->cache[$this->getStoreId('getTotal')] = $total;
+
+ if ($total < $limitstart) {
+ $limitstart = 0;
+ $this->setState('list.start', 0);
+ }
+
+ return array_slice($result, $limitstart, $limit ?: null);
+ }
+
+ // If ordering by fields that doesn't need translate just order the query.
+ if ($listOrder === 'a.ordering') {
+ $query->order($db->quoteName('a.position') . ' ASC')
+ ->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
+ } elseif ($listOrder === 'a.position') {
+ $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn))
+ ->order($db->quoteName('a.ordering') . ' ASC');
+ } else {
+ $query->order($db->quoteName($listOrder) . ' ' . $db->escape($listDirn));
+ }
+
+ // Process pagination.
+ $result = parent::_getList($query, $limitstart, $limit);
+
+ // Translate the results.
+ $this->translate($result);
+
+ return $result;
+ }
+
+ /**
+ * Translate a list of objects
+ *
+ * @param array &$items The array of objects
+ *
+ * @return array The array of translated objects
+ */
+ protected function translate(&$items)
+ {
+ $lang = Factory::getLanguage();
+ $clientPath = $this->getState('client_id') ? JPATH_ADMINISTRATOR : JPATH_SITE;
+
+ foreach ($items as $item) {
+ $extension = $item->module;
+ $source = $clientPath . "/modules/$extension";
+ $lang->load("$extension.sys", $clientPath)
+ || $lang->load("$extension.sys", $source);
+ $item->name = Text::_($item->name);
+
+ if (is_null($item->pages)) {
+ $item->pages = Text::_('JNONE');
+ } elseif ($item->pages < 0) {
+ $item->pages = Text::_('COM_MODULES_ASSIGNED_VARIES_EXCEPT');
+ } elseif ($item->pages > 0) {
+ $item->pages = Text::_('COM_MODULES_ASSIGNED_VARIES_ONLY');
+ } else {
+ $item->pages = Text::_('JALL');
+ }
+ }
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.title, a.note, a.position, a.module, a.language,' .
+ 'a.checked_out, a.checked_out_time, a.published AS published, e.enabled AS enabled, a.access, a.ordering, a.publish_up, a.publish_down'
+ )
+ );
+
+ // From modules table.
+ $query->from($db->quoteName('#__modules', 'a'));
+
+ // Join over the language
+ $query->select($db->quoteName('l.title', 'language_title'))
+ ->select($db->quoteName('l.image', 'language_image'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l') . ' ON ' . $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
+
+ // Join over the users for the checked out user.
+ $query->select($db->quoteName('uc.name', 'editor'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
+
+ // Join over the asset groups.
+ $query->select($db->quoteName('ag.title', 'access_level'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ag') . ' ON ' . $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'));
+
+ // Join over the module menus
+ $query->select('MIN(mm.menuid) AS pages')
+ ->join('LEFT', $db->quoteName('#__modules_menu', 'mm') . ' ON ' . $db->quoteName('mm.moduleid') . ' = ' . $db->quoteName('a.id'));
+
+ // Join over the extensions
+ $query->select($db->quoteName('e.name', 'name'))
+ ->join('LEFT', $db->quoteName('#__extensions', 'e') . ' ON ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('a.module'));
+
+ // Group (careful with PostgreSQL)
+ $query->group(
+ 'a.id, a.title, a.note, a.position, a.module, a.language, a.checked_out, '
+ . 'a.checked_out_time, a.published, a.access, a.ordering, l.title, l.image, uc.name, ag.title, e.name, '
+ . 'l.lang_code, uc.id, ag.id, mm.moduleid, e.element, a.publish_up, a.publish_down, e.enabled'
+ );
+
+ // Filter by client.
+ $clientId = (int) $this->getState('client_id');
+ $query->where($db->quoteName('a.client_id') . ' = :aclientid')
+ ->where($db->quoteName('e.client_id') . ' = :eclientid')
+ ->bind(':aclientid', $clientId, ParameterType::INTEGER)
+ ->bind(':eclientid', $clientId, ParameterType::INTEGER);
+
+ // Filter by current user access level.
+ $user = Factory::getUser();
+
+ // Get the current user for authorisation checks
+ if ($user->authorise('core.admin') !== true) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ $access = (int) $access;
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Filter by published state.
+ $state = $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.published') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif ($state === '') {
+ $query->whereIn($db->quoteName('a.published'), [0, 1]);
+ }
+
+ // Filter by position.
+ if ($position = $this->getState('filter.position')) {
+ $position = ($position === 'none') ? '' : $position;
+ $query->where($db->quoteName('a.position') . ' = :position')
+ ->bind(':position', $position);
+ }
+
+ // Filter by module.
+ if ($module = $this->getState('filter.module')) {
+ $query->where($db->quoteName('a.module') . ' = :module')
+ ->bind(':module', $module);
+ }
+
+ // Filter by menuitem id (only for site client).
+ if ((int) $clientId === 0 && $menuItemId = $this->getState('filter.menuitem')) {
+ // If user selected the modules not assigned to any page (menu item).
+ if ((int) $menuItemId === -1) {
+ $query->having('MIN(' . $db->quoteName('mm.menuid') . ') IS NULL');
+ } else {
+ // If user selected the modules assigned to some particular page (menu item).
+ // Modules in "All" pages.
+ $subQuery1 = $db->getQuery(true);
+ $subQuery1->select('MIN(' . $db->quoteName('menuid') . ')')
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('moduleid') . ' = ' . $db->quoteName('a.id'));
+
+ // Modules in "Selected" pages that have the chosen menu item id.
+ $menuItemId = (int) $menuItemId;
+ $minusMenuItemId = $menuItemId * -1;
+ $subQuery2 = $db->getQuery(true);
+ $subQuery2->select($db->quoteName('moduleid'))
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('menuid') . ' = :menuitemid2');
+
+ // Modules in "All except selected" pages that doesn't have the chosen menu item id.
+ $subQuery3 = $db->getQuery(true);
+ $subQuery3->select($db->quoteName('moduleid'))
+ ->from($db->quoteName('#__modules_menu'))
+ ->where($db->quoteName('menuid') . ' = :menuitemid3');
+
+ // Filter by modules assigned to the selected menu item.
+ $query->where('(
(' . $subQuery1 . ') = 0
OR ((' . $subQuery1 . ') > 0 AND ' . $db->quoteName('a.id') . ' IN (' . $subQuery2 . '))
OR ((' . $subQuery1 . ') < 0 AND ' . $db->quoteName('a.id') . ' NOT IN (' . $subQuery3 . '))
- )'
- );
- $query->bind(':menuitemid2', $menuItemId, ParameterType::INTEGER);
- $query->bind(':menuitemid3', $minusMenuItemId, ParameterType::INTEGER);
- }
- }
-
- // Filter by search in title or note or id:.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . StringHelper::strtolower($search) . '%';
- $query->extendWhere(
- 'AND',
- [
- 'LOWER(' . $db->quoteName('a.title') . ') LIKE :title',
- 'LOWER(' . $db->quoteName('a.note') . ') LIKE :note',
- ],
- 'OR'
- )
- ->bind(':title', $search)
- ->bind(':note', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- if ($language === 'current')
- {
- $language = [Factory::getLanguage()->getTag(), '*'];
- $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
- }
- else
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
- }
-
- return $query;
- }
-
- /**
- * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
- *
- * @return DatabaseQuery
- *
- * @since 4.0.0
- */
- protected function getEmptyStateQuery()
- {
- $query = parent::getEmptyStateQuery();
-
- $clientId = (int) $this->getState('client_id');
-
- $query->where($this->getDatabase()->quoteName('a.client_id') . ' = :client_id')
- ->bind(':client_id', $clientId, ParameterType::INTEGER);
-
- return $query;
- }
+ )');
+ $query->bind(':menuitemid2', $menuItemId, ParameterType::INTEGER);
+ $query->bind(':menuitemid3', $minusMenuItemId, ParameterType::INTEGER);
+ }
+ }
+
+ // Filter by search in title or note or id:.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . StringHelper::strtolower($search) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ 'LOWER(' . $db->quoteName('a.title') . ') LIKE :title',
+ 'LOWER(' . $db->quoteName('a.note') . ') LIKE :note',
+ ],
+ 'OR'
+ )
+ ->bind(':title', $search)
+ ->bind(':note', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ if ($language === 'current') {
+ $language = [Factory::getLanguage()->getTag(), '*'];
+ $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
+ } else {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 4.0.0
+ */
+ protected function getEmptyStateQuery()
+ {
+ $query = parent::getEmptyStateQuery();
+
+ $clientId = (int) $this->getState('client_id');
+
+ $query->where($this->getDatabase()->quoteName('a.client_id') . ' = :client_id')
+ ->bind(':client_id', $clientId, ParameterType::INTEGER);
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_modules/src/Model/PositionsModel.php b/administrator/components/com_modules/src/Model/PositionsModel.php
index 0213400e5f5d2..4d4c1b2a7ed88 100644
--- a/administrator/components/com_modules/src/Model/PositionsModel.php
+++ b/administrator/components/com_modules/src/Model/PositionsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
- $this->setState('filter.search', $search);
-
- $state = $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'string');
- $this->setState('filter.state', $state);
-
- $template = $this->getUserStateFromRequest($this->context . '.filter.template', 'filter_template', '', 'string');
- $this->setState('filter.template', $template);
-
- $type = $this->getUserStateFromRequest($this->context . '.filter.type', 'filter_type', '', 'string');
- $this->setState('filter.type', $type);
-
- // Special case for the client id.
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $clientId = (!in_array((int) $clientId, array (0, 1))) ? 0 : (int) $clientId;
- $this->setState('client_id', $clientId);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_modules');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 1.6
- */
- public function getItems()
- {
- if (!isset($this->items))
- {
- $lang = Factory::getLanguage();
- $search = $this->getState('filter.search');
- $state = $this->getState('filter.state');
- $clientId = $this->getState('client_id');
- $filter_template = $this->getState('filter.template');
- $type = $this->getState('filter.type');
- $ordering = $this->getState('list.ordering');
- $direction = $this->getState('list.direction');
- $limitstart = $this->getState('list.start');
- $limit = $this->getState('list.limit');
- $client = ApplicationHelper::getClientInfo($clientId);
-
- if ($type != 'template')
- {
- $clientId = (int) $clientId;
-
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('position', 'value'))
- ->from($db->quoteName('#__modules'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- if ($search)
- {
- $search = '%' . str_replace(' ', '%', trim($search), true) . '%';
- $query->where($db->quoteName('position') . ' LIKE :position')
- ->bind(':position', $search);
- }
-
- $db->setQuery($query);
-
- try
- {
- $positions = $db->loadObjectList('value');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- foreach ($positions as $value => $position)
- {
- $positions[$value] = array();
- }
- }
- else
- {
- $positions = array();
- }
-
- // Load the positions from the installed templates.
- foreach (ModulesHelper::getTemplates($clientId) as $template)
- {
- $path = Path::clean($client->path . '/templates/' . $template->element . '/templateDetails.xml');
-
- if (file_exists($path))
- {
- $xml = simplexml_load_file($path);
-
- if (isset($xml->positions[0]))
- {
- $lang->load('tpl_' . $template->element . '.sys', $client->path)
- || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
-
- foreach ($xml->positions[0] as $position)
- {
- $value = (string) $position['value'];
- $label = (string) $position;
-
- if (!$value)
- {
- $value = $label;
- $label = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'TPL_' . $template->element . '_POSITION_' . $value);
- $altlabel = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'COM_MODULES_POSITION_' . $value);
-
- if (!$lang->hasKey($label) && $lang->hasKey($altlabel))
- {
- $label = $altlabel;
- }
- }
-
- if ($type == 'user' || ($state != '' && $state != $template->enabled))
- {
- unset($positions[$value]);
- }
- elseif (preg_match(chr(1) . $search . chr(1) . 'i', $value) && ($filter_template == '' || $filter_template == $template->element))
- {
- if (!isset($positions[$value]))
- {
- $positions[$value] = array();
- }
-
- $positions[$value][$template->name] = $label;
- }
- }
- }
- }
- }
-
- $this->total = count($positions);
-
- if ($limitstart >= $this->total)
- {
- $limitstart = $limitstart < $limit ? 0 : $limitstart - $limit;
- $this->setState('list.start', $limitstart);
- }
-
- if ($ordering == 'value')
- {
- if ($direction == 'asc')
- {
- ksort($positions);
- }
- else
- {
- krsort($positions);
- }
- }
- else
- {
- if ($direction == 'asc')
- {
- asort($positions);
- }
- else
- {
- arsort($positions);
- }
- }
-
- $this->items = array_slice($positions, $limitstart, $limit ?: null);
- }
-
- return $this->items;
- }
-
- /**
- * Method to get the total number of items.
- *
- * @return integer The total number of items.
- *
- * @since 1.6
- */
- public function getTotal()
- {
- if (!isset($this->total))
- {
- $this->getItems();
- }
-
- return $this->total;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @see \JController
+ * @since 1.6
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'value',
+ 'templates',
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'ordering', $direction = 'asc')
+ {
+ // Load the filter state.
+ $search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search');
+ $this->setState('filter.search', $search);
+
+ $state = $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state', '', 'string');
+ $this->setState('filter.state', $state);
+
+ $template = $this->getUserStateFromRequest($this->context . '.filter.template', 'filter_template', '', 'string');
+ $this->setState('filter.template', $template);
+
+ $type = $this->getUserStateFromRequest($this->context . '.filter.type', 'filter_type', '', 'string');
+ $this->setState('filter.type', $type);
+
+ // Special case for the client id.
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $clientId = (!in_array((int) $clientId, array (0, 1))) ? 0 : (int) $clientId;
+ $this->setState('client_id', $clientId);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_modules');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ if (!isset($this->items)) {
+ $lang = Factory::getLanguage();
+ $search = $this->getState('filter.search');
+ $state = $this->getState('filter.state');
+ $clientId = $this->getState('client_id');
+ $filter_template = $this->getState('filter.template');
+ $type = $this->getState('filter.type');
+ $ordering = $this->getState('list.ordering');
+ $direction = $this->getState('list.direction');
+ $limitstart = $this->getState('list.start');
+ $limit = $this->getState('list.limit');
+ $client = ApplicationHelper::getClientInfo($clientId);
+
+ if ($type != 'template') {
+ $clientId = (int) $clientId;
+
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('position', 'value'))
+ ->from($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ if ($search) {
+ $search = '%' . str_replace(' ', '%', trim($search), true) . '%';
+ $query->where($db->quoteName('position') . ' LIKE :position')
+ ->bind(':position', $search);
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $positions = $db->loadObjectList('value');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ foreach ($positions as $value => $position) {
+ $positions[$value] = array();
+ }
+ } else {
+ $positions = array();
+ }
+
+ // Load the positions from the installed templates.
+ foreach (ModulesHelper::getTemplates($clientId) as $template) {
+ $path = Path::clean($client->path . '/templates/' . $template->element . '/templateDetails.xml');
+
+ if (file_exists($path)) {
+ $xml = simplexml_load_file($path);
+
+ if (isset($xml->positions[0])) {
+ $lang->load('tpl_' . $template->element . '.sys', $client->path)
+ || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
+
+ foreach ($xml->positions[0] as $position) {
+ $value = (string) $position['value'];
+ $label = (string) $position;
+
+ if (!$value) {
+ $value = $label;
+ $label = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'TPL_' . $template->element . '_POSITION_' . $value);
+ $altlabel = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'COM_MODULES_POSITION_' . $value);
+
+ if (!$lang->hasKey($label) && $lang->hasKey($altlabel)) {
+ $label = $altlabel;
+ }
+ }
+
+ if ($type == 'user' || ($state != '' && $state != $template->enabled)) {
+ unset($positions[$value]);
+ } elseif (preg_match(chr(1) . $search . chr(1) . 'i', $value) && ($filter_template == '' || $filter_template == $template->element)) {
+ if (!isset($positions[$value])) {
+ $positions[$value] = array();
+ }
+
+ $positions[$value][$template->name] = $label;
+ }
+ }
+ }
+ }
+ }
+
+ $this->total = count($positions);
+
+ if ($limitstart >= $this->total) {
+ $limitstart = $limitstart < $limit ? 0 : $limitstart - $limit;
+ $this->setState('list.start', $limitstart);
+ }
+
+ if ($ordering == 'value') {
+ if ($direction == 'asc') {
+ ksort($positions);
+ } else {
+ krsort($positions);
+ }
+ } else {
+ if ($direction == 'asc') {
+ asort($positions);
+ } else {
+ arsort($positions);
+ }
+ }
+
+ $this->items = array_slice($positions, $limitstart, $limit ?: null);
+ }
+
+ return $this->items;
+ }
+
+ /**
+ * Method to get the total number of items.
+ *
+ * @return integer The total number of items.
+ *
+ * @since 1.6
+ */
+ public function getTotal()
+ {
+ if (!isset($this->total)) {
+ $this->getItems();
+ }
+
+ return $this->total;
+ }
}
diff --git a/administrator/components/com_modules/src/Model/SelectModel.php b/administrator/components/com_modules/src/Model/SelectModel.php
index eb901af523edc..36fbc6b3df615 100644
--- a/administrator/components/com_modules/src/Model/SelectModel.php
+++ b/administrator/components/com_modules/src/Model/SelectModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest('com_modules.modules.client_id', 'client_id', 0);
- $this->setState('client_id', (int) $clientId);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_modules');
- $this->setState('params', $params);
-
- // Manually set limits to get all modules.
- $this->setState('list.limit', 0);
- $this->setState('list.start', 0);
- $this->setState('list.ordering', 'a.name');
- $this->setState('list.direction', 'ASC');
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('client_id');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.extension_id, a.name, a.element AS module'
- )
- );
- $query->from($db->quoteName('#__extensions', 'a'));
-
- // Filter by module
- $query->where($db->quoteName('a.type') . ' = ' . $db->quote('module'));
-
- // Filter by client.
- $clientId = (int) $this->getState('client_id');
- $query->where($db->quoteName('a.client_id') . ' = :clientid')
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- // Filter by enabled
- $query->where($db->quoteName('a.enabled') . ' = 1');
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Method to get a list of items.
- *
- * @return mixed An array of objects on success, false on failure.
- */
- public function getItems()
- {
- // Get the list of items from the database.
- $items = parent::getItems();
-
- $client = ApplicationHelper::getClientInfo($this->getState('client_id', 0));
- $lang = Factory::getLanguage();
-
- // Loop through the results to add the XML metadata,
- // and load language support.
- foreach ($items as &$item)
- {
- $path = Path::clean($client->path . '/modules/' . $item->module . '/' . $item->module . '.xml');
-
- if (file_exists($path))
- {
- $item->xml = simplexml_load_file($path);
- }
- else
- {
- $item->xml = null;
- }
-
- // 1.5 Format; Core files or language packs then
- // 1.6 3PD Extension Support
- $lang->load($item->module . '.sys', $client->path)
- || $lang->load($item->module . '.sys', $client->path . '/modules/' . $item->module);
- $item->name = Text::_($item->name);
-
- if (isset($item->xml) && $text = trim($item->xml->description))
- {
- $item->desc = Text::_($text);
- }
- else
- {
- $item->desc = Text::_('COM_MODULES_NODESCRIPTION');
- }
- }
-
- $items = ArrayHelper::sortObjects($items, 'name', 1, true, true);
-
- // @todo: Use the cached XML from the extensions table?
-
- return $items;
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = null, $direction = null)
+ {
+ $app = Factory::getApplication();
+
+ // Load the filter state.
+ $clientId = $app->getUserStateFromRequest('com_modules.modules.client_id', 'client_id', 0);
+ $this->setState('client_id', (int) $clientId);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_modules');
+ $this->setState('params', $params);
+
+ // Manually set limits to get all modules.
+ $this->setState('list.limit', 0);
+ $this->setState('list.start', 0);
+ $this->setState('list.ordering', 'a.name');
+ $this->setState('list.direction', 'ASC');
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('client_id');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.extension_id, a.name, a.element AS module'
+ )
+ );
+ $query->from($db->quoteName('#__extensions', 'a'));
+
+ // Filter by module
+ $query->where($db->quoteName('a.type') . ' = ' . $db->quote('module'));
+
+ // Filter by client.
+ $clientId = (int) $this->getState('client_id');
+ $query->where($db->quoteName('a.client_id') . ' = :clientid')
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ // Filter by enabled
+ $query->where($db->quoteName('a.enabled') . ' = 1');
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to get a list of items.
+ *
+ * @return mixed An array of objects on success, false on failure.
+ */
+ public function getItems()
+ {
+ // Get the list of items from the database.
+ $items = parent::getItems();
+
+ $client = ApplicationHelper::getClientInfo($this->getState('client_id', 0));
+ $lang = Factory::getLanguage();
+
+ // Loop through the results to add the XML metadata,
+ // and load language support.
+ foreach ($items as &$item) {
+ $path = Path::clean($client->path . '/modules/' . $item->module . '/' . $item->module . '.xml');
+
+ if (file_exists($path)) {
+ $item->xml = simplexml_load_file($path);
+ } else {
+ $item->xml = null;
+ }
+
+ // 1.5 Format; Core files or language packs then
+ // 1.6 3PD Extension Support
+ $lang->load($item->module . '.sys', $client->path)
+ || $lang->load($item->module . '.sys', $client->path . '/modules/' . $item->module);
+ $item->name = Text::_($item->name);
+
+ if (isset($item->xml) && $text = trim($item->xml->description)) {
+ $item->desc = Text::_($text);
+ } else {
+ $item->desc = Text::_('COM_MODULES_NODESCRIPTION');
+ }
+ }
+
+ $items = ArrayHelper::sortObjects($items, 'name', 1, true, true);
+
+ // @todo: Use the cached XML from the extensions table?
+
+ return $items;
+ }
}
diff --git a/administrator/components/com_modules/src/Service/HTML/Modules.php b/administrator/components/com_modules/src/Service/HTML/Modules.php
index 2e850cda51920..f934fd3d9d2a8 100644
--- a/administrator/components/com_modules/src/Service/HTML/Modules.php
+++ b/administrator/components/com_modules/src/Service/HTML/Modules.php
@@ -1,4 +1,5 @@
element, $template->name);
- }
-
- return $options;
- }
-
- /**
- * Builds an array of template type options
- *
- * @return array
- */
- public function types()
- {
- $options = array();
- $options[] = HTMLHelper::_('select.option', 'user', 'COM_MODULES_OPTION_POSITION_USER_DEFINED');
- $options[] = HTMLHelper::_('select.option', 'template', 'COM_MODULES_OPTION_POSITION_TEMPLATE_DEFINED');
-
- return $options;
- }
-
- /**
- * Builds an array of template state options
- *
- * @return array
- */
- public function templateStates()
- {
- $options = array();
- $options[] = HTMLHelper::_('select.option', '1', 'JENABLED');
- $options[] = HTMLHelper::_('select.option', '0', 'JDISABLED');
-
- return $options;
- }
-
- /**
- * Returns a published state on a grid
- *
- * @param integer $value The state value.
- * @param integer $i The row index
- * @param boolean $enabled An optional setting for access control on the action.
- * @param string $checkbox An optional prefix for checkboxes.
- *
- * @return string The Html code
- *
- * @see HTMLHelperJGrid::state
- * @since 1.7.1
- */
- public function state($value, $i, $enabled = true, $checkbox = 'cb')
- {
- $states = array(
- 1 => array(
- 'unpublish',
- 'COM_MODULES_EXTENSION_PUBLISHED_ENABLED',
- 'COM_MODULES_HTML_UNPUBLISH_ENABLED',
- 'COM_MODULES_EXTENSION_PUBLISHED_ENABLED',
- true,
- 'publish',
- 'publish',
- ),
- 0 => array(
- 'publish',
- 'COM_MODULES_EXTENSION_UNPUBLISHED_ENABLED',
- 'COM_MODULES_HTML_PUBLISH_ENABLED',
- 'COM_MODULES_EXTENSION_UNPUBLISHED_ENABLED',
- true,
- 'unpublish',
- 'unpublish',
- ),
- -1 => array(
- 'unpublish',
- 'COM_MODULES_EXTENSION_PUBLISHED_DISABLED',
- 'COM_MODULES_HTML_UNPUBLISH_DISABLED',
- 'COM_MODULES_EXTENSION_PUBLISHED_DISABLED',
- true,
- 'warning',
- 'warning',
- ),
- -2 => array(
- 'publish',
- 'COM_MODULES_EXTENSION_UNPUBLISHED_DISABLED',
- 'COM_MODULES_HTML_PUBLISH_DISABLED',
- 'COM_MODULES_EXTENSION_UNPUBLISHED_DISABLED',
- true,
- 'unpublish',
- 'unpublish',
- ),
- );
-
- return HTMLHelper::_('jgrid.state', $states, $value, $i, 'modules.', $enabled, true, $checkbox);
- }
-
- /**
- * Display a batch widget for the module position selector.
- *
- * @param integer $clientId The client ID.
- * @param integer $state The state of the module (enabled, unenabled, trashed).
- * @param string $selectedPosition The currently selected position for the module.
- *
- * @return string The necessary positions for the widget.
- *
- * @since 2.5
- */
- public function positions($clientId, $state = 1, $selectedPosition = '')
- {
- $templates = array_keys(ModulesHelper::getTemplates($clientId, $state));
- $templateGroups = array();
-
- // Add an empty value to be able to deselect a module position
- $option = ModulesHelper::createOption('', Text::_('COM_MODULES_NONE'));
- $templateGroups[''] = ModulesHelper::createOptionGroup('', array($option));
-
- // Add positions from templates
- $isTemplatePosition = false;
-
- foreach ($templates as $template)
- {
- $options = array();
-
- $positions = TemplatesHelper::getPositions($clientId, $template);
-
- if (is_array($positions))
- {
- foreach ($positions as $position)
- {
- $text = ModulesHelper::getTranslatedModulePosition($clientId, $template, $position) . ' [' . $position . ']';
- $options[] = ModulesHelper::createOption($position, $text);
-
- if (!$isTemplatePosition && $selectedPosition === $position)
- {
- $isTemplatePosition = true;
- }
- }
-
- $options = ArrayHelper::sortObjects($options, 'text');
- }
-
- $templateGroups[$template] = ModulesHelper::createOptionGroup(ucfirst($template), $options);
- }
-
- // Add custom position to options
- $customGroupText = Text::_('COM_MODULES_CUSTOM_POSITION');
- $editPositions = true;
- $customPositions = ModulesHelper::getPositions($clientId, $editPositions);
-
- $app = Factory::getApplication();
-
- $position = $app->getUserState('com_modules.modules.' . $clientId . '.filter.position');
-
- if ($position)
- {
- $customPositions[] = HTMLHelper::_('select.option', $position);
-
- $customPositions = array_unique($customPositions, SORT_REGULAR);
- }
-
- $templateGroups[$customGroupText] = ModulesHelper::createOptionGroup($customGroupText, $customPositions);
-
- return $templateGroups;
- }
-
- /**
- * Get a select with the batch action options
- *
- * @return void
- */
- public function batchOptions()
- {
- // Create the copy/move options.
- $options = array(
- HTMLHelper::_('select.option', 'c', Text::_('JLIB_HTML_BATCH_COPY')),
- HTMLHelper::_('select.option', 'm', Text::_('JLIB_HTML_BATCH_MOVE'))
- );
-
- echo HTMLHelper::_('select.radiolist', $options, 'batch[move_copy]', '', 'value', 'text', 'm');
- }
-
- /**
- * Method to get the field options.
- *
- * @param integer $clientId The client ID
- *
- * @return array The field option objects.
- *
- * @since 2.5
- *
- * @deprecated 5.0 Will be removed with no replacement
- */
- public function positionList($clientId = 0)
- {
- $clientId = (int) $clientId;
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('position', 'value'))
- ->select($db->quoteName('position', 'text'))
- ->from($db->quoteName('#__modules'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->order($db->quoteName('position'))
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- // Pop the first item off the array if it's blank
- if (count($options))
- {
- if (strlen($options[0]->text) < 1)
- {
- array_shift($options);
- }
- }
-
- return $options;
- }
+ /**
+ * Builds an array of template options
+ *
+ * @param integer $clientId The client id.
+ * @param string $state The state of the template.
+ *
+ * @return array
+ */
+ public function templates($clientId = 0, $state = '')
+ {
+ $options = array();
+ $templates = ModulesHelper::getTemplates($clientId, $state);
+
+ foreach ($templates as $template) {
+ $options[] = HTMLHelper::_('select.option', $template->element, $template->name);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Builds an array of template type options
+ *
+ * @return array
+ */
+ public function types()
+ {
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', 'user', 'COM_MODULES_OPTION_POSITION_USER_DEFINED');
+ $options[] = HTMLHelper::_('select.option', 'template', 'COM_MODULES_OPTION_POSITION_TEMPLATE_DEFINED');
+
+ return $options;
+ }
+
+ /**
+ * Builds an array of template state options
+ *
+ * @return array
+ */
+ public function templateStates()
+ {
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '1', 'JENABLED');
+ $options[] = HTMLHelper::_('select.option', '0', 'JDISABLED');
+
+ return $options;
+ }
+
+ /**
+ * Returns a published state on a grid
+ *
+ * @param integer $value The state value.
+ * @param integer $i The row index
+ * @param boolean $enabled An optional setting for access control on the action.
+ * @param string $checkbox An optional prefix for checkboxes.
+ *
+ * @return string The Html code
+ *
+ * @see HTMLHelperJGrid::state
+ * @since 1.7.1
+ */
+ public function state($value, $i, $enabled = true, $checkbox = 'cb')
+ {
+ $states = array(
+ 1 => array(
+ 'unpublish',
+ 'COM_MODULES_EXTENSION_PUBLISHED_ENABLED',
+ 'COM_MODULES_HTML_UNPUBLISH_ENABLED',
+ 'COM_MODULES_EXTENSION_PUBLISHED_ENABLED',
+ true,
+ 'publish',
+ 'publish',
+ ),
+ 0 => array(
+ 'publish',
+ 'COM_MODULES_EXTENSION_UNPUBLISHED_ENABLED',
+ 'COM_MODULES_HTML_PUBLISH_ENABLED',
+ 'COM_MODULES_EXTENSION_UNPUBLISHED_ENABLED',
+ true,
+ 'unpublish',
+ 'unpublish',
+ ),
+ -1 => array(
+ 'unpublish',
+ 'COM_MODULES_EXTENSION_PUBLISHED_DISABLED',
+ 'COM_MODULES_HTML_UNPUBLISH_DISABLED',
+ 'COM_MODULES_EXTENSION_PUBLISHED_DISABLED',
+ true,
+ 'warning',
+ 'warning',
+ ),
+ -2 => array(
+ 'publish',
+ 'COM_MODULES_EXTENSION_UNPUBLISHED_DISABLED',
+ 'COM_MODULES_HTML_PUBLISH_DISABLED',
+ 'COM_MODULES_EXTENSION_UNPUBLISHED_DISABLED',
+ true,
+ 'unpublish',
+ 'unpublish',
+ ),
+ );
+
+ return HTMLHelper::_('jgrid.state', $states, $value, $i, 'modules.', $enabled, true, $checkbox);
+ }
+
+ /**
+ * Display a batch widget for the module position selector.
+ *
+ * @param integer $clientId The client ID.
+ * @param integer $state The state of the module (enabled, unenabled, trashed).
+ * @param string $selectedPosition The currently selected position for the module.
+ *
+ * @return string The necessary positions for the widget.
+ *
+ * @since 2.5
+ */
+ public function positions($clientId, $state = 1, $selectedPosition = '')
+ {
+ $templates = array_keys(ModulesHelper::getTemplates($clientId, $state));
+ $templateGroups = array();
+
+ // Add an empty value to be able to deselect a module position
+ $option = ModulesHelper::createOption('', Text::_('COM_MODULES_NONE'));
+ $templateGroups[''] = ModulesHelper::createOptionGroup('', array($option));
+
+ // Add positions from templates
+ $isTemplatePosition = false;
+
+ foreach ($templates as $template) {
+ $options = array();
+
+ $positions = TemplatesHelper::getPositions($clientId, $template);
+
+ if (is_array($positions)) {
+ foreach ($positions as $position) {
+ $text = ModulesHelper::getTranslatedModulePosition($clientId, $template, $position) . ' [' . $position . ']';
+ $options[] = ModulesHelper::createOption($position, $text);
+
+ if (!$isTemplatePosition && $selectedPosition === $position) {
+ $isTemplatePosition = true;
+ }
+ }
+
+ $options = ArrayHelper::sortObjects($options, 'text');
+ }
+
+ $templateGroups[$template] = ModulesHelper::createOptionGroup(ucfirst($template), $options);
+ }
+
+ // Add custom position to options
+ $customGroupText = Text::_('COM_MODULES_CUSTOM_POSITION');
+ $editPositions = true;
+ $customPositions = ModulesHelper::getPositions($clientId, $editPositions);
+
+ $app = Factory::getApplication();
+
+ $position = $app->getUserState('com_modules.modules.' . $clientId . '.filter.position');
+
+ if ($position) {
+ $customPositions[] = HTMLHelper::_('select.option', $position);
+
+ $customPositions = array_unique($customPositions, SORT_REGULAR);
+ }
+
+ $templateGroups[$customGroupText] = ModulesHelper::createOptionGroup($customGroupText, $customPositions);
+
+ return $templateGroups;
+ }
+
+ /**
+ * Get a select with the batch action options
+ *
+ * @return void
+ */
+ public function batchOptions()
+ {
+ // Create the copy/move options.
+ $options = array(
+ HTMLHelper::_('select.option', 'c', Text::_('JLIB_HTML_BATCH_COPY')),
+ HTMLHelper::_('select.option', 'm', Text::_('JLIB_HTML_BATCH_MOVE'))
+ );
+
+ echo HTMLHelper::_('select.radiolist', $options, 'batch[move_copy]', '', 'value', 'text', 'm');
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @param integer $clientId The client ID
+ *
+ * @return array The field option objects.
+ *
+ * @since 2.5
+ *
+ * @deprecated 5.0 Will be removed with no replacement
+ */
+ public function positionList($clientId = 0)
+ {
+ $clientId = (int) $clientId;
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('position', 'value'))
+ ->select($db->quoteName('position', 'text'))
+ ->from($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->order($db->quoteName('position'))
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ // Pop the first item off the array if it's blank
+ if (count($options)) {
+ if (strlen($options[0]->text) < 1) {
+ array_shift($options);
+ }
+ }
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_modules/src/View/Module/HtmlView.php b/administrator/components/com_modules/src/View/Module/HtmlView.php
index 5a21ecb1ee68e..a2dfd77ea4e96 100644
--- a/administrator/components/com_modules/src/View/Module/HtmlView.php
+++ b/administrator/components/com_modules/src/View/Module/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
- $this->canDo = ContentHelper::getActions('com_modules', 'module', $this->item->id);
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
- $canDo = $this->canDo;
-
- ToolbarHelper::title(Text::sprintf('COM_MODULES_MANAGER_MODULE', Text::_($this->item->module)), 'cube module');
-
- // For new records, check the create permission.
- if ($isNew && $canDo->get('core.create'))
- {
- ToolbarHelper::apply('module.apply');
-
- ToolbarHelper::saveGroup(
- [
- ['save', 'module.save'],
- ['save2new', 'module.save2new']
- ],
- 'btn-success'
- );
-
- ToolbarHelper::cancel('module.cancel');
- }
- else
- {
- $toolbarButtons = [];
-
- // Can't save the record if it's checked out.
- if (!$checkedOut)
- {
- // Since it's an existing record, check the edit permission.
- if ($canDo->get('core.edit'))
- {
- ToolbarHelper::apply('module.apply');
-
- $toolbarButtons[] = ['save', 'module.save'];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'module.save2new'];
- }
- }
- }
-
- // If checked out, we can still save
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'module.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel('module.cancel', 'JTOOLBAR_CLOSE');
- }
-
- // Get the help information for the menu item.
- $lang = Factory::getLanguage();
-
- $help = $this->get('Help');
-
- if ($lang->hasKey($help->url))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($help->url);
- $lang->setDebug($debug);
- }
- else
- {
- $url = null;
- }
-
- ToolbarHelper::inlinehelp();
- ToolbarHelper::help($help->key, false, $url);
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 4.0.0
+ */
+ protected $canDo;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $this->canDo = ContentHelper::getActions('com_modules', 'module', $this->item->id);
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
+ $canDo = $this->canDo;
+
+ ToolbarHelper::title(Text::sprintf('COM_MODULES_MANAGER_MODULE', Text::_($this->item->module)), 'cube module');
+
+ // For new records, check the create permission.
+ if ($isNew && $canDo->get('core.create')) {
+ ToolbarHelper::apply('module.apply');
+
+ ToolbarHelper::saveGroup(
+ [
+ ['save', 'module.save'],
+ ['save2new', 'module.save2new']
+ ],
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('module.cancel');
+ } else {
+ $toolbarButtons = [];
+
+ // Can't save the record if it's checked out.
+ if (!$checkedOut) {
+ // Since it's an existing record, check the edit permission.
+ if ($canDo->get('core.edit')) {
+ ToolbarHelper::apply('module.apply');
+
+ $toolbarButtons[] = ['save', 'module.save'];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'module.save2new'];
+ }
+ }
+ }
+
+ // If checked out, we can still save
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'module.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('module.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ // Get the help information for the menu item.
+ $lang = Factory::getLanguage();
+
+ $help = $this->get('Help');
+
+ if ($lang->hasKey($help->url)) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($help->url);
+ $lang->setDebug($debug);
+ } else {
+ $url = null;
+ }
+
+ ToolbarHelper::inlinehelp();
+ ToolbarHelper::help($help->key, false, $url);
+ }
}
diff --git a/administrator/components/com_modules/src/View/Modules/HtmlView.php b/administrator/components/com_modules/src/View/Modules/HtmlView.php
index 853ae9b074b13..8f505a6f58ede 100644
--- a/administrator/components/com_modules/src/View/Modules/HtmlView.php
+++ b/administrator/components/com_modules/src/View/Modules/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->total = $this->get('Total');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->clientId = $this->state->get('client_id');
-
- if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- /**
- * The code below make sure the remembered position will be available from filter dropdown even if there are no
- * modules available for this position. This will make the UI less confusing for users in case there is only one
- * module in the selected position and user:
- * 1. Edit the module, change it to new position, save it and come back to Modules Management Screen
- * 2. Or move that module to new position using Batch action
- */
- if (count($this->items) === 0 && $this->state->get('filter.position'))
- {
- $selectedPosition = $this->state->get('filter.position');
- $positionField = $this->filterForm->getField('position', 'filter');
-
- $positionExists = false;
-
- foreach ($positionField->getOptions() as $option)
- {
- if ($option->value === $selectedPosition)
- {
- $positionExists = true;
- break;
- }
- }
-
- if ($positionExists === false)
- {
- $positionField->addOption($selectedPosition, ['value' => $selectedPosition]);
- }
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // We do not need the Language filter when modules are not filtered
- if ($this->clientId == 1 && !ModuleHelper::isAdminMultilang())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
-
- // We don't need the toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- // If in modal layout.
- else
- {
- // Client id selector should not exist.
- $this->filterForm->removeField('client_id', '');
-
- // If in the frontend state and language should not activate the search tools.
- if (Factory::getApplication()->isClient('site'))
- {
- unset($this->activeFilters['state']);
- unset($this->activeFilters['language']);
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $state = $this->get('State');
- $canDo = ContentHelper::getActions('com_modules');
- $user = $this->getCurrentUser();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- if ($state->get('client_id') == 1)
- {
- ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_ADMIN'), 'cube module');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_SITE'), 'cube module');
- }
-
- if ($canDo->get('core.create'))
- {
- $toolbar->standardButton('new', 'JTOOLBAR_NEW')
- ->onclick("location.href='index.php?option=com_modules&view=select&client_id=" . $this->state->get('client_id', 0) . "'");
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('modules.publish')->listCheck(true);
-
- $childBar->unpublish('modules.unpublish')->listCheck(true);
- }
-
- if ($this->getCurrentUser()->authorise('core.admin'))
- {
- $childBar->checkin('modules.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2)
- {
- $childBar->trash('modules.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_modules') && $user->authorise('core.edit', 'com_modules')
- && $user->authorise('core.edit.state', 'com_modules'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.create'))
- {
- $childBar->standardButton('copy')
- ->text('JTOOLBAR_DUPLICATE')
- ->task('modules.duplicate')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && ($state->get('filter.state') == -2 && $canDo->get('core.delete')))
- {
- $toolbar->delete('modules.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin'))
- {
- $toolbar->preferences('com_modules');
- }
-
- $toolbar->help('Modules');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->total = $this->get('Total');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->clientId = $this->state->get('client_id');
+
+ if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ /**
+ * The code below make sure the remembered position will be available from filter dropdown even if there are no
+ * modules available for this position. This will make the UI less confusing for users in case there is only one
+ * module in the selected position and user:
+ * 1. Edit the module, change it to new position, save it and come back to Modules Management Screen
+ * 2. Or move that module to new position using Batch action
+ */
+ if (count($this->items) === 0 && $this->state->get('filter.position')) {
+ $selectedPosition = $this->state->get('filter.position');
+ $positionField = $this->filterForm->getField('position', 'filter');
+
+ $positionExists = false;
+
+ foreach ($positionField->getOptions() as $option) {
+ if ($option->value === $selectedPosition) {
+ $positionExists = true;
+ break;
+ }
+ }
+
+ if ($positionExists === false) {
+ $positionField->addOption($selectedPosition, ['value' => $selectedPosition]);
+ }
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // We do not need the Language filter when modules are not filtered
+ if ($this->clientId == 1 && !ModuleHelper::isAdminMultilang()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+
+ // We don't need the toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // If in modal layout.
+ // Client id selector should not exist.
+ $this->filterForm->removeField('client_id', '');
+
+ // If in the frontend state and language should not activate the search tools.
+ if (Factory::getApplication()->isClient('site')) {
+ unset($this->activeFilters['state']);
+ unset($this->activeFilters['language']);
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $state = $this->get('State');
+ $canDo = ContentHelper::getActions('com_modules');
+ $user = $this->getCurrentUser();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ if ($state->get('client_id') == 1) {
+ ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_ADMIN'), 'cube module');
+ } else {
+ ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_SITE'), 'cube module');
+ }
+
+ if ($canDo->get('core.create')) {
+ $toolbar->standardButton('new', 'JTOOLBAR_NEW')
+ ->onclick("location.href='index.php?option=com_modules&view=select&client_id=" . $this->state->get('client_id', 0) . "'");
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $this->getCurrentUser()->authorise('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('modules.publish')->listCheck(true);
+
+ $childBar->unpublish('modules.unpublish')->listCheck(true);
+ }
+
+ if ($this->getCurrentUser()->authorise('core.admin')) {
+ $childBar->checkin('modules.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2) {
+ $childBar->trash('modules.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_modules') && $user->authorise('core.edit', 'com_modules')
+ && $user->authorise('core.edit.state', 'com_modules')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.create')) {
+ $childBar->standardButton('copy')
+ ->text('JTOOLBAR_DUPLICATE')
+ ->task('modules.duplicate')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && ($state->get('filter.state') == -2 && $canDo->get('core.delete'))) {
+ $toolbar->delete('modules.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin')) {
+ $toolbar->preferences('com_modules');
+ }
+
+ $toolbar->help('Modules');
+ }
}
diff --git a/administrator/components/com_modules/src/View/Select/HtmlView.php b/administrator/components/com_modules/src/View/Select/HtmlView.php
index 3084005611456..9684b10b5222e 100644
--- a/administrator/components/com_modules/src/View/Select/HtmlView.php
+++ b/administrator/components/com_modules/src/View/Select/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->items = $this->get('Items');
- $this->modalLink = '';
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->modalLink = '';
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- $this->addToolbar();
- parent::display($tpl);
- }
+ $this->addToolbar();
+ parent::display($tpl);
+ }
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $state = $this->get('State');
- $clientId = (int) $state->get('client_id', 0);
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $state = $this->get('State');
+ $clientId = (int) $state->get('client_id', 0);
- // Add page title
- ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_SITE'), 'cube module');
+ // Add page title
+ ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_SITE'), 'cube module');
- if ($clientId === 1)
- {
- ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_ADMIN'), 'cube module');
- }
+ if ($clientId === 1) {
+ ToolbarHelper::title(Text::_('COM_MODULES_MANAGER_MODULES_ADMIN'), 'cube module');
+ }
- // Get the toolbar object instance
- $bar = Toolbar::getInstance('toolbar');
+ // Get the toolbar object instance
+ $bar = Toolbar::getInstance('toolbar');
- // Instantiate a new FileLayout instance and render the layout
- $layout = new FileLayout('toolbar.cancelselect');
+ // Instantiate a new FileLayout instance and render the layout
+ $layout = new FileLayout('toolbar.cancelselect');
- $bar->appendButton('Custom', $layout->render(array('client_id' => $clientId)), 'new');
- }
+ $bar->appendButton('Custom', $layout->render(array('client_id' => $clientId)), 'new');
+ }
}
diff --git a/administrator/components/com_modules/tmpl/module/edit.php b/administrator/components/com_modules/tmpl/module/edit.php
index 982bbf002d4fc..0dbdb4680d460 100644
--- a/administrator/components/com_modules/tmpl/module/edit.php
+++ b/administrator/components/com_modules/tmpl/module/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_modules.admin-module-edit');
+ ->useScript('form.validate')
+ ->useScript('com_modules.admin-module-edit');
$input = Factory::getApplication()->input;
@@ -54,151 +54,144 @@
diff --git a/administrator/components/com_modules/tmpl/module/edit_assignment.php b/administrator/components/com_modules/tmpl/module/edit_assignment.php
index bcd98ef2b1056..311e19998e796 100644
--- a/administrator/components/com_modules/tmpl/module/edit_assignment.php
+++ b/administrator/components/com_modules/tmpl/module/edit_assignment.php
@@ -1,4 +1,5 @@
document->getWebAssetManager()
- ->useScript('joomla.treeselectmenu')
- ->useScript('com_modules.admin-module-edit-assignment');
+ ->useScript('joomla.treeselectmenu')
+ ->useScript('com_modules.admin-module-edit-assignment');
?>
-
-
+
+
diff --git a/administrator/components/com_modules/tmpl/module/modal.php b/administrator/components/com_modules/tmpl/module/modal.php
index bb8088a027279..b90680e779098 100644
--- a/administrator/components/com_modules/tmpl/module/modal.php
+++ b/administrator/components/com_modules/tmpl/module/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_modules/tmpl/modules/default.php b/administrator/components/com_modules/tmpl/modules/default.php
index c3bccaa60f501..d4ba1df7fdf59 100644
--- a/administrator/components/com_modules/tmpl/modules/default.php
+++ b/administrator/components/com_modules/tmpl/modules/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$clientId = (int) $this->state->get('client_id', 0);
$user = Factory::getUser();
@@ -29,191 +30,191 @@
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = ($listOrder == 'a.ordering');
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_modules&task=modules.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_modules&task=modules.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_modules/tmpl/modules/default_batch_body.php b/administrator/components/com_modules/tmpl/modules/default_batch_body.php
index 2a52e9c45f60c..4683baede1166 100644
--- a/administrator/components/com_modules/tmpl/modules/default_batch_body.php
+++ b/administrator/components/com_modules/tmpl/modules/default_batch_body.php
@@ -1,4 +1,5 @@
'batch-position-id',
+ 'id' => 'batch-position-id',
);
Text::script('JGLOBAL_SELECT_NO_RESULTS_MATCH');
Text::script('JGLOBAL_SELECT_PRESS_TO_SELECT');
$this->document->getWebAssetManager()
- ->usePreset('choicesjs')
- ->useScript('webcomponent.field-fancy-select')
- ->useScript('joomla.batch-copymove');
+ ->usePreset('choicesjs')
+ ->useScript('webcomponent.field-fancy-select')
+ ->useScript('joomla.batch-copymove');
?>
diff --git a/administrator/components/com_modules/tmpl/modules/default_batch_footer.php b/administrator/components/com_modules/tmpl/modules/default_batch_footer.php
index 44f8345ac2384..0a684547cb746 100644
--- a/administrator/components/com_modules/tmpl/modules/default_batch_footer.php
+++ b/administrator/components/com_modules/tmpl/modules/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_modules/tmpl/modules/emptystate.php b/administrator/components/com_modules/tmpl/modules/emptystate.php
index 323715f6070cd..f0b2b02ed1d2a 100644
--- a/administrator/components/com_modules/tmpl/modules/emptystate.php
+++ b/administrator/components/com_modules/tmpl/modules/emptystate.php
@@ -1,4 +1,5 @@
'COM_MODULES',
- 'formURL' => 'index.php?option=com_modules&view=select&client_id=' . $this->clientId,
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Module',
- 'icon' => 'icon-cube module',
- // Although it is (almost) impossible to get to this page with no created Administrator Modules, we add this for completeness.
- 'title' => Text::_('COM_MODULES_EMPTYSTATE_TITLE_' . ($this->clientId ? 'ADMINISTRATOR' : 'SITE')),
+ 'textPrefix' => 'COM_MODULES',
+ 'formURL' => 'index.php?option=com_modules&view=select&client_id=' . $this->clientId,
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Module',
+ 'icon' => 'icon-cube module',
+ // Although it is (almost) impossible to get to this page with no created Administrator Modules, we add this for completeness.
+ 'title' => Text::_('COM_MODULES_EMPTYSTATE_TITLE_' . ($this->clientId ? 'ADMINISTRATOR' : 'SITE')),
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_modules'))
-{
- $displayData['createURL'] = 'index.php?option=com_modules&view=select&client_id=' . $this->clientId;
+if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_modules')) {
+ $displayData['createURL'] = 'index.php?option=com_modules&view=select&client_id=' . $this->clientId;
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_modules/tmpl/modules/modal.php b/administrator/components/com_modules/tmpl/modules/modal.php
index 972919a7dd79f..3c24c77f552c0 100644
--- a/administrator/components/com_modules/tmpl/modules/modal.php
+++ b/administrator/components/com_modules/tmpl/modules/modal.php
@@ -1,4 +1,5 @@
isClient('site'))
-{
- Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
+if (Factory::getApplication()->isClient('site')) {
+ Session::checkToken('get') or die(Text::_('JINVALID_TOKEN'));
}
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
@@ -30,109 +30,108 @@
$editor = Factory::getApplication()->input->get('editor', '', 'cmd');
$link = 'index.php?option=com_modules&view=modules&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
-if (!empty($editor))
-{
- $link .= '&editor=' . $editor;
+if (!empty($editor)) {
+ $link .= '&editor=' . $editor;
}
?>
diff --git a/administrator/components/com_modules/tmpl/select/default.php b/administrator/components/com_modules/tmpl/select/default.php
index 344db412c06b9..e335ed5b81026 100644
--- a/administrator/components/com_modules/tmpl/select/default.php
+++ b/administrator/components/com_modules/tmpl/select/default.php
@@ -1,4 +1,5 @@
useScript('com_modules.admin-module-search');
if ($function) :
- $wa->useScript('com_modules.admin-select-modal');
+ $wa->useScript('com_modules.admin-select-modal');
endif;
?>
-
-
-
-
-
-
-
- items as &$item) : ?>
-
- state->get('client_id', 0) . $this->modalLink . '&eid=' . $item->extension_id; ?>
- escape($item->name); ?>
- escape(strip_tags($item->desc)), 200); ?>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ items as &$item) : ?>
+
+ state->get('client_id', 0) . $this->modalLink . '&eid=' . $item->extension_id; ?>
+ escape($item->name); ?>
+ escape(strip_tags($item->desc)), 200); ?>
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_modules/tmpl/select/modal.php b/administrator/components/com_modules/tmpl/select/modal.php
index 2e6998d652bb3..25ac8d0afdee1 100644
--- a/administrator/components/com_modules/tmpl/select/modal.php
+++ b/administrator/components/com_modules/tmpl/select/modal.php
@@ -1,4 +1,5 @@
modalLink = '&tmpl=component&view=module&layout=modal';
?>
diff --git a/administrator/components/com_newsfeeds/helpers/newsfeeds.php b/administrator/components/com_newsfeeds/helpers/newsfeeds.php
index 608aeabc716dc..3347beaead9af 100644
--- a/administrator/components/com_newsfeeds/helpers/newsfeeds.php
+++ b/administrator/components/com_newsfeeds/helpers/newsfeeds.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Newsfeeds component helper.
diff --git a/administrator/components/com_newsfeeds/services/provider.php b/administrator/components/com_newsfeeds/services/provider.php
index 3374c7259492a..0382efe8a6595 100644
--- a/administrator/components/com_newsfeeds/services/provider.php
+++ b/administrator/components/com_newsfeeds/services/provider.php
@@ -1,4 +1,5 @@
set(AssociationExtensionInterface::class, new AssociationsHelper);
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(AssociationExtensionInterface::class, new AssociationsHelper());
- $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Newsfeeds'));
- $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Newsfeeds'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Newsfeeds'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Newsfeeds'));
+ $container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Newsfeeds'));
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Newsfeeds'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Newsfeeds'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Newsfeeds'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new NewsfeedsComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new NewsfeedsComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
+ $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_newsfeeds/src/Controller/AjaxController.php b/administrator/components/com_newsfeeds/src/Controller/AjaxController.php
index 34487acbf8275..ce5d7b74a405b 100644
--- a/administrator/components/com_newsfeeds/src/Controller/AjaxController.php
+++ b/administrator/components/com_newsfeeds/src/Controller/AjaxController.php
@@ -1,4 +1,5 @@
input->getInt('assocId', 0);
+ /**
+ * Method to fetch associations of a newsfeed
+ *
+ * The method assumes that the following http parameters are passed in an Ajax Get request:
+ * token: the form token
+ * assocId: the id of the newsfeed whose associations are to be returned
+ * excludeLang: the association for this language is to be excluded
+ *
+ * @return null
+ *
+ * @since 3.9.0
+ */
+ public function fetchAssociations()
+ {
+ if (!Session::checkToken('get')) {
+ echo new JsonResponse(null, Text::_('JINVALID_TOKEN'), true);
+ } else {
+ $assocId = $this->input->getInt('assocId', 0);
- if ($assocId == 0)
- {
- echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
+ if ($assocId == 0) {
+ echo new JsonResponse(null, Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'assocId'), true);
- return;
- }
+ return;
+ }
- $excludeLang = $this->input->get('excludeLang', '', 'STRING');
+ $excludeLang = $this->input->get('excludeLang', '', 'STRING');
- $associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', (int) $assocId);
+ $associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', (int) $assocId);
- unset($associations[$excludeLang]);
+ unset($associations[$excludeLang]);
- // Add the title to each of the associated records
- $newsfeedsTable = $this->factory->createTable('Newsfeed', 'Administrator');
+ // Add the title to each of the associated records
+ $newsfeedsTable = $this->factory->createTable('Newsfeed', 'Administrator');
- foreach ($associations as $lang => $association)
- {
- $newsfeedsTable->load($association->id);
- $associations[$lang]->title = $newsfeedsTable->name;
- }
+ foreach ($associations as $lang => $association) {
+ $newsfeedsTable->load($association->id);
+ $associations[$lang]->title = $newsfeedsTable->name;
+ }
- $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
+ $countContentLanguages = count(LanguageHelper::getContentLanguages(array(0, 1), false));
- if (count($associations) == 0)
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
- }
- elseif ($countContentLanguages > count($associations) + 2)
- {
- $tags = implode(', ', array_keys($associations));
- $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
- }
- else
- {
- $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
- }
+ if (count($associations) == 0) {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_NONE');
+ } elseif ($countContentLanguages > count($associations) + 2) {
+ $tags = implode(', ', array_keys($associations));
+ $message = Text::sprintf('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_SOME', $tags);
+ } else {
+ $message = Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_MESSAGE_ALL');
+ }
- echo new JsonResponse($associations, $message);
- }
- }
+ echo new JsonResponse($associations, $message);
+ }
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Controller/DisplayController.php b/administrator/components/com_newsfeeds/src/Controller/DisplayController.php
index 7eeabbb78ade9..96908aa30c306 100644
--- a/administrator/components/com_newsfeeds/src/Controller/DisplayController.php
+++ b/administrator/components/com_newsfeeds/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'newsfeeds');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if ($view == 'newsfeed' && $layout == 'edit' && !$this->checkEditId('com_newsfeeds.edit.newsfeed', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_newsfeeds&view=newsfeeds', false));
-
- return false;
- }
-
- return parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'newsfeeds';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view', 'newsfeeds');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if ($view == 'newsfeed' && $layout == 'edit' && !$this->checkEditId('com_newsfeeds.edit.newsfeed', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_newsfeeds&view=newsfeeds', false));
+
+ return false;
+ }
+
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Controller/NewsfeedController.php b/administrator/components/com_newsfeeds/src/Controller/NewsfeedController.php
index 0ef1f40524d42..8c7db02614af9 100644
--- a/administrator/components/com_newsfeeds/src/Controller/NewsfeedController.php
+++ b/administrator/components/com_newsfeeds/src/Controller/NewsfeedController.php
@@ -1,4 +1,5 @@
input->getInt('filter_category_id'), 'int');
- $allow = null;
-
- if ($categoryId)
- {
- // If the category has been passed in the URL check it.
- $allow = $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
- }
-
- if ($allow === null)
- {
- // In the absence of better information, revert to the component permissions.
- return parent::allowAdd($data);
- }
- else
- {
- return $allow;
- }
- }
-
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
-
- // Since there is no asset tracking, fallback to the component permissions.
- if (!$recordId)
- {
- return parent::allowEdit($data, $key);
- }
-
- // Get the item.
- $item = $this->getModel()->getItem($recordId);
-
- // Since there is no item, return false.
- if (empty($item))
- {
- return false;
- }
-
- $user = $this->app->getIdentity();
-
- // Check if can edit own core.edit.own.
- $canEditOwn = $user->authorise('core.edit.own', $this->option . '.category.' . (int) $item->catid) && $item->created_by == $user->id;
-
- // Check the category core.edit permissions.
- return $canEditOwn || $user->authorise('core.edit', $this->option . '.category.' . (int) $item->catid);
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 2.5
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('Newsfeed', '', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_newsfeeds&view=newsfeeds' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
+ use VersionableControllerTrait;
+
+ /**
+ * Method override to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowAdd($data = array())
+ {
+ $categoryId = ArrayHelper::getValue($data, 'catid', $this->input->getInt('filter_category_id'), 'int');
+ $allow = null;
+
+ if ($categoryId) {
+ // If the category has been passed in the URL check it.
+ $allow = $this->app->getIdentity()->authorise('core.create', $this->option . '.category.' . $categoryId);
+ }
+
+ if ($allow === null) {
+ // In the absence of better information, revert to the component permissions.
+ return parent::allowAdd($data);
+ } else {
+ return $allow;
+ }
+ }
+
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
+
+ // Since there is no asset tracking, fallback to the component permissions.
+ if (!$recordId) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // Get the item.
+ $item = $this->getModel()->getItem($recordId);
+
+ // Since there is no item, return false.
+ if (empty($item)) {
+ return false;
+ }
+
+ $user = $this->app->getIdentity();
+
+ // Check if can edit own core.edit.own.
+ $canEditOwn = $user->authorise('core.edit.own', $this->option . '.category.' . (int) $item->catid) && $item->created_by == $user->id;
+
+ // Check the category core.edit permissions.
+ return $canEditOwn || $user->authorise('core.edit', $this->option . '.category.' . (int) $item->catid);
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 2.5
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('Newsfeed', '', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_newsfeeds&view=newsfeeds' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Controller/NewsfeedsController.php b/administrator/components/com_newsfeeds/src/Controller/NewsfeedsController.php
index 72bbd6303e4dc..ad56ff2ab61e5 100644
--- a/administrator/components/com_newsfeeds/src/Controller/NewsfeedsController.php
+++ b/administrator/components/com_newsfeeds/src/Controller/NewsfeedsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Newsfeed', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Extension/NewsfeedsComponent.php b/administrator/components/com_newsfeeds/src/Extension/NewsfeedsComponent.php
index a9226c389ae80..5eb61733fb079 100644
--- a/administrator/components/com_newsfeeds/src/Extension/NewsfeedsComponent.php
+++ b/administrator/components/com_newsfeeds/src/Extension/NewsfeedsComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('newsfeedsadministrator', new AdministratorService);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('newsfeedsadministrator', new AdministratorService());
+ }
- /**
- * Returns the table for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getTableNameForSection(string $section = null)
- {
- return $section === 'category' ? 'categories' : 'newsfeeds';
- }
+ /**
+ * Returns the table for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getTableNameForSection(string $section = null)
+ {
+ return $section === 'category' ? 'categories' : 'newsfeeds';
+ }
- /**
- * Returns the state column for the count items functions for the given section.
- *
- * @param string $section The section
- *
- * @return string|null
- *
- * @since 4.0.0
- */
- protected function getStateColumnForSection(string $section = null)
- {
- return 'published';
- }
+ /**
+ * Returns the state column for the count items functions for the given section.
+ *
+ * @param string $section The section
+ *
+ * @return string|null
+ *
+ * @since 4.0.0
+ */
+ protected function getStateColumnForSection(string $section = null)
+ {
+ return 'published';
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Field/Modal/NewsfeedField.php b/administrator/components/com_newsfeeds/src/Field/Modal/NewsfeedField.php
index 3dfdaa5a5b186..fc2aef842ab41 100644
--- a/administrator/components/com_newsfeeds/src/Field/Modal/NewsfeedField.php
+++ b/administrator/components/com_newsfeeds/src/Field/Modal/NewsfeedField.php
@@ -1,4 +1,5 @@
element['new'] == 'true');
- $allowEdit = ((string) $this->element['edit'] == 'true');
- $allowClear = ((string) $this->element['clear'] != 'false');
- $allowSelect = ((string) $this->element['select'] != 'false');
- $allowPropagate = ((string) $this->element['propagate'] == 'true');
-
- $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
-
- // Load language
- Factory::getLanguage()->load('com_newsfeeds', JPATH_ADMINISTRATOR);
-
- // The active newsfeed id field.
- $value = (int) $this->value ?: '';
-
- // Create the modal id.
- $modalId = 'Newsfeed_' . $this->id;
-
- /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
- $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
-
- // Add the modal field script to the document head.
- $wa->useScript('field.modal-fields');
-
- // Script to proxy the select modal function to the modal-fields.js file.
- if ($allowSelect)
- {
- static $scriptSelect = null;
-
- if (is_null($scriptSelect))
- {
- $scriptSelect = array();
- }
-
- if (!isset($scriptSelect[$this->id]))
- {
- $wa->addInlineScript("
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'Modal_Newsfeed';
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ $allowNew = ((string) $this->element['new'] == 'true');
+ $allowEdit = ((string) $this->element['edit'] == 'true');
+ $allowClear = ((string) $this->element['clear'] != 'false');
+ $allowSelect = ((string) $this->element['select'] != 'false');
+ $allowPropagate = ((string) $this->element['propagate'] == 'true');
+
+ $languages = LanguageHelper::getContentLanguages(array(0, 1), false);
+
+ // Load language
+ Factory::getLanguage()->load('com_newsfeeds', JPATH_ADMINISTRATOR);
+
+ // The active newsfeed id field.
+ $value = (int) $this->value ?: '';
+
+ // Create the modal id.
+ $modalId = 'Newsfeed_' . $this->id;
+
+ /** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
+ $wa = Factory::getApplication()->getDocument()->getWebAssetManager();
+
+ // Add the modal field script to the document head.
+ $wa->useScript('field.modal-fields');
+
+ // Script to proxy the select modal function to the modal-fields.js file.
+ if ($allowSelect) {
+ static $scriptSelect = null;
+
+ if (is_null($scriptSelect)) {
+ $scriptSelect = array();
+ }
+
+ if (!isset($scriptSelect[$this->id])) {
+ $wa->addInlineScript(
+ "
window.jSelectNewsfeed_" . $this->id . " = function (id, title, object) {
window.processModalSelect('Newsfeed', '" . $this->id . "', id, title, '', object);
}",
- [],
- ['type' => 'module']
- );
-
- Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
-
- $scriptSelect[$this->id] = true;
- }
- }
-
- // Setup variables for display.
- $linkNewsfeeds = 'index.php?option=com_newsfeeds&view=newsfeeds&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
- $linkNewsfeed = 'index.php?option=com_newsfeeds&view=newsfeed&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
- $modalTitle = Text::_('COM_NEWSFEEDS_SELECT_A_FEED');
-
- if (isset($this->element['language']))
- {
- $linkNewsfeeds .= '&forcedLanguage=' . $this->element['language'];
- $linkNewsfeed .= '&forcedLanguage=' . $this->element['language'];
- $modalTitle .= ' — ' . $this->element['label'];
- }
-
- $urlSelect = $linkNewsfeeds . '&function=jSelectNewsfeed_' . $this->id;
- $urlEdit = $linkNewsfeed . '&task=newsfeed.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
- $urlNew = $linkNewsfeed . '&task=newsfeed.add';
-
- if ($value)
- {
- $id = (int) $value;
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('name'))
- ->from($db->quoteName('#__newsfeeds'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $title = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
-
- $title = empty($title) ? Text::_('COM_NEWSFEEDS_SELECT_A_FEED') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
-
- // The current newsfeed display field.
- $html = '';
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= '';
- }
-
- $html .= ' ';
-
- // Select newsfeed button
- if ($allowSelect)
- {
- $html .= ''
- . ' ' . Text::_('JSELECT')
- . ' ';
- }
-
- // New newsfeed button
- if ($allowNew)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_CREATE')
- . ' ';
- }
-
- // Edit newsfeed button
- if ($allowEdit)
- {
- $html .= ''
- . ' ' . Text::_('JACTION_EDIT')
- . ' ';
- }
-
- // Clear newsfeed button
- if ($allowClear)
- {
- $html .= ''
- . ' ' . Text::_('JCLEAR')
- . ' ';
- }
-
- // Propagate newsfeed button
- if ($allowPropagate && count($languages) > 2)
- {
- // Strip off language tag at the end
- $tagLength = (int) strlen($this->element['language']);
- $callbackFunctionStem = substr("jSelectNewsfeed_" . $this->id, 0, -$tagLength);
-
- $html .= ''
- . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
- . ' ';
- }
-
- if ($allowSelect || $allowNew || $allowEdit || $allowClear)
- {
- $html .= ' ';
- }
-
- // Select newsfeed modal
- if ($allowSelect)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalSelect' . $modalId,
- array(
- 'title' => $modalTitle,
- 'url' => $urlSelect,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
- )
- );
- }
-
- // New newsfeed modal
- if ($allowNew)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalNew' . $modalId,
- array(
- 'title' => Text::_('COM_NEWSFEEDS_NEW_NEWSFEED'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlNew,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Edit newsfeed modal.
- if ($allowEdit)
- {
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- 'ModalEdit' . $modalId,
- array(
- 'title' => Text::_('COM_NEWSFEEDS_EDIT_NEWSFEED'),
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'closeButton' => false,
- 'url' => $urlEdit,
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' ',
- )
- );
- }
-
- // Add class='required' for client side validation
- $class = $this->required ? ' class="required modal-value"' : '';
-
- $html .= ' ';
-
- return $html;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 3.4
- */
- protected function getLabel()
- {
- return str_replace($this->id, $this->id . '_name', parent::getLabel());
- }
+ [],
+ ['type' => 'module']
+ );
+
+ Text::script('JGLOBAL_ASSOCIATIONS_PROPAGATE_FAILED');
+
+ $scriptSelect[$this->id] = true;
+ }
+ }
+
+ // Setup variables for display.
+ $linkNewsfeeds = 'index.php?option=com_newsfeeds&view=newsfeeds&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+ $linkNewsfeed = 'index.php?option=com_newsfeeds&view=newsfeed&layout=modal&tmpl=component&' . Session::getFormToken() . '=1';
+ $modalTitle = Text::_('COM_NEWSFEEDS_SELECT_A_FEED');
+
+ if (isset($this->element['language'])) {
+ $linkNewsfeeds .= '&forcedLanguage=' . $this->element['language'];
+ $linkNewsfeed .= '&forcedLanguage=' . $this->element['language'];
+ $modalTitle .= ' — ' . $this->element['label'];
+ }
+
+ $urlSelect = $linkNewsfeeds . '&function=jSelectNewsfeed_' . $this->id;
+ $urlEdit = $linkNewsfeed . '&task=newsfeed.edit&id=\' + document.getElementById("' . $this->id . '_id").value + \'';
+ $urlNew = $linkNewsfeed . '&task=newsfeed.add';
+
+ if ($value) {
+ $id = (int) $value;
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('name'))
+ ->from($db->quoteName('#__newsfeeds'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $title = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+
+ $title = empty($title) ? Text::_('COM_NEWSFEEDS_SELECT_A_FEED') : htmlspecialchars($title, ENT_QUOTES, 'UTF-8');
+
+ // The current newsfeed display field.
+ $html = '';
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= '';
+ }
+
+ $html .= ' ';
+
+ // Select newsfeed button
+ if ($allowSelect) {
+ $html .= ''
+ . ' ' . Text::_('JSELECT')
+ . ' ';
+ }
+
+ // New newsfeed button
+ if ($allowNew) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_CREATE')
+ . ' ';
+ }
+
+ // Edit newsfeed button
+ if ($allowEdit) {
+ $html .= ''
+ . ' ' . Text::_('JACTION_EDIT')
+ . ' ';
+ }
+
+ // Clear newsfeed button
+ if ($allowClear) {
+ $html .= ''
+ . ' ' . Text::_('JCLEAR')
+ . ' ';
+ }
+
+ // Propagate newsfeed button
+ if ($allowPropagate && count($languages) > 2) {
+ // Strip off language tag at the end
+ $tagLength = (int) strlen($this->element['language']);
+ $callbackFunctionStem = substr("jSelectNewsfeed_" . $this->id, 0, -$tagLength);
+
+ $html .= ''
+ . ' ' . Text::_('JGLOBAL_ASSOCIATIONS_PROPAGATE_BUTTON')
+ . ' ';
+ }
+
+ if ($allowSelect || $allowNew || $allowEdit || $allowClear) {
+ $html .= ' ';
+ }
+
+ // Select newsfeed modal
+ if ($allowSelect) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalSelect' . $modalId,
+ array(
+ 'title' => $modalTitle,
+ 'url' => $urlSelect,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' ',
+ )
+ );
+ }
+
+ // New newsfeed modal
+ if ($allowNew) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalNew' . $modalId,
+ array(
+ 'title' => Text::_('COM_NEWSFEEDS_NEW_NEWSFEED'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlNew,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Edit newsfeed modal.
+ if ($allowEdit) {
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'ModalEdit' . $modalId,
+ array(
+ 'title' => Text::_('COM_NEWSFEEDS_EDIT_NEWSFEED'),
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'closeButton' => false,
+ 'url' => $urlEdit,
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' ',
+ )
+ );
+ }
+
+ // Add class='required' for client side validation
+ $class = $this->required ? ' class="required modal-value"' : '';
+
+ $html .= ' ';
+
+ return $html;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 3.4
+ */
+ protected function getLabel()
+ {
+ return str_replace($this->id, $this->id . '_name', parent::getLabel());
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Field/NewsfeedsField.php b/administrator/components/com_newsfeeds/src/Field/NewsfeedsField.php
index e85fa5578b1fb..f0744fc212621 100644
--- a/administrator/components/com_newsfeeds/src/Field/NewsfeedsField.php
+++ b/administrator/components/com_newsfeeds/src/Field/NewsfeedsField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id', 'value'),
- $db->quoteName('name', 'text'),
- ]
- )
- ->from($db->quoteName('#__newsfeeds', 'a'))
- ->order($db->quoteName('a.name'));
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id', 'value'),
+ $db->quoteName('name', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__newsfeeds', 'a'))
+ ->order($db->quoteName('a.name'));
- // Get the options.
- $db->setQuery($query);
+ // Get the options.
+ $db->setQuery($query);
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Helper/AssociationsHelper.php b/administrator/components/com_newsfeeds/src/Helper/AssociationsHelper.php
index 85c90141fbb19..33b59a0200b95 100644
--- a/administrator/components/com_newsfeeds/src/Helper/AssociationsHelper.php
+++ b/administrator/components/com_newsfeeds/src/Helper/AssociationsHelper.php
@@ -1,4 +1,5 @@
getType($typeName);
-
- $context = $this->extension . '.item';
- $catidField = 'catid';
-
- if ($typeName === 'category')
- {
- $context = 'com_categories.item';
- $catidField = '';
- }
-
- // Get the associations.
- $associations = Associations::getAssociations(
- $this->extension,
- $type['tables']['a'],
- $context,
- $id,
- 'id',
- 'alias',
- $catidField
- );
-
- return $associations;
- }
-
- /**
- * Get item information
- *
- * @param string $typeName The item type
- * @param int $id The id of item for which we need the associated items
- *
- * @return Table|null
- *
- * @since 3.7.0
- */
- public function getItem($typeName, $id)
- {
- if (empty($id))
- {
- return null;
- }
-
- $table = null;
-
- switch ($typeName)
- {
- case 'newsfeed':
- $table = Table::getInstance('NewsfeedTable', 'Joomla\\Component\\Newsfeeds\\Administrator\\Table\\');
- break;
-
- case 'category':
- $table = Table::getInstance('Category');
- break;
- }
-
- if (empty($table))
- {
- return null;
- }
-
- $table->load($id);
-
- return $table;
- }
-
- /**
- * Get information about the type
- *
- * @param string $typeName The item type
- *
- * @return array Array of item types
- *
- * @since 3.7.0
- */
- public function getType($typeName = '')
- {
- $fields = $this->getFieldsTemplate();
- $tables = array();
- $joins = array();
- $support = $this->getSupportTemplate();
- $title = '';
-
- if (in_array($typeName, $this->itemTypes))
- {
- switch ($typeName)
- {
- case 'newsfeed':
- $fields['title'] = 'a.name';
- $fields['state'] = 'a.published';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['category'] = true;
- $support['save2copy'] = true;
-
- $tables = array(
- 'a' => '#__newsfeeds'
- );
- $title = 'newsfeed';
- break;
-
- case 'category':
- $fields['created_user_id'] = 'a.created_user_id';
- $fields['ordering'] = 'a.lft';
- $fields['level'] = 'a.level';
- $fields['catid'] = '';
- $fields['state'] = 'a.published';
-
- $support['state'] = true;
- $support['acl'] = true;
- $support['checkout'] = true;
- $support['level'] = true;
-
- $tables = array(
- 'a' => '#__categories'
- );
-
- $title = 'category';
- break;
- }
- }
-
- return array(
- 'fields' => $fields,
- 'support' => $support,
- 'tables' => $tables,
- 'joins' => $joins,
- 'title' => $title
- );
- }
+ /**
+ * The extension name
+ *
+ * @var array $extension
+ *
+ * @since 3.7.0
+ */
+ protected $extension = 'com_newsfeeds';
+
+ /**
+ * Array of item types
+ *
+ * @var array $itemTypes
+ *
+ * @since 3.7.0
+ */
+ protected $itemTypes = array('newsfeed', 'category');
+
+ /**
+ * Has the extension association support
+ *
+ * @var boolean $associationsSupport
+ *
+ * @since 3.7.0
+ */
+ protected $associationsSupport = true;
+
+ /**
+ * Method to get the associations for a given item.
+ *
+ * @param integer $id Id of the item
+ * @param string $view Name of the view
+ *
+ * @return array Array of associations for the item
+ *
+ * @since 4.0.0
+ */
+ public function getAssociationsForItem($id = 0, $view = null)
+ {
+ return AssociationHelper::getAssociations($id, $view);
+ }
+
+ /**
+ * Get the associated items for an item
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ public function getAssociations($typeName, $id)
+ {
+ $type = $this->getType($typeName);
+
+ $context = $this->extension . '.item';
+ $catidField = 'catid';
+
+ if ($typeName === 'category') {
+ $context = 'com_categories.item';
+ $catidField = '';
+ }
+
+ // Get the associations.
+ $associations = Associations::getAssociations(
+ $this->extension,
+ $type['tables']['a'],
+ $context,
+ $id,
+ 'id',
+ 'alias',
+ $catidField
+ );
+
+ return $associations;
+ }
+
+ /**
+ * Get item information
+ *
+ * @param string $typeName The item type
+ * @param int $id The id of item for which we need the associated items
+ *
+ * @return Table|null
+ *
+ * @since 3.7.0
+ */
+ public function getItem($typeName, $id)
+ {
+ if (empty($id)) {
+ return null;
+ }
+
+ $table = null;
+
+ switch ($typeName) {
+ case 'newsfeed':
+ $table = Table::getInstance('NewsfeedTable', 'Joomla\\Component\\Newsfeeds\\Administrator\\Table\\');
+ break;
+
+ case 'category':
+ $table = Table::getInstance('Category');
+ break;
+ }
+
+ if (empty($table)) {
+ return null;
+ }
+
+ $table->load($id);
+
+ return $table;
+ }
+
+ /**
+ * Get information about the type
+ *
+ * @param string $typeName The item type
+ *
+ * @return array Array of item types
+ *
+ * @since 3.7.0
+ */
+ public function getType($typeName = '')
+ {
+ $fields = $this->getFieldsTemplate();
+ $tables = array();
+ $joins = array();
+ $support = $this->getSupportTemplate();
+ $title = '';
+
+ if (in_array($typeName, $this->itemTypes)) {
+ switch ($typeName) {
+ case 'newsfeed':
+ $fields['title'] = 'a.name';
+ $fields['state'] = 'a.published';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['category'] = true;
+ $support['save2copy'] = true;
+
+ $tables = array(
+ 'a' => '#__newsfeeds'
+ );
+ $title = 'newsfeed';
+ break;
+
+ case 'category':
+ $fields['created_user_id'] = 'a.created_user_id';
+ $fields['ordering'] = 'a.lft';
+ $fields['level'] = 'a.level';
+ $fields['catid'] = '';
+ $fields['state'] = 'a.published';
+
+ $support['state'] = true;
+ $support['acl'] = true;
+ $support['checkout'] = true;
+ $support['level'] = true;
+
+ $tables = array(
+ 'a' => '#__categories'
+ );
+
+ $title = 'category';
+ break;
+ }
+ }
+
+ return array(
+ 'fields' => $fields,
+ 'support' => $support,
+ 'tables' => $tables,
+ 'joins' => $joins,
+ 'title' => $title
+ );
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Helper/NewsfeedsHelper.php b/administrator/components/com_newsfeeds/src/Helper/NewsfeedsHelper.php
index 55dc24d5ff30d..bd9dd739aa96c 100644
--- a/administrator/components/com_newsfeeds/src/Helper/NewsfeedsHelper.php
+++ b/administrator/components/com_newsfeeds/src/Helper/NewsfeedsHelper.php
@@ -1,4 +1,5 @@
getQuery(true);
- $query->select(
- [
- $db->quoteName('published', 'state'),
- 'COUNT(*) AS ' . $db->quoteName('count'),
- ]
- )
- ->from($db->quoteName('#__newsfeeds'))
- ->where($db->quoteName('catid') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER)
- ->group($db->quoteName('state'));
- $db->setQuery($query);
-
- foreach ($items as $item)
- {
- $item->count_trashed = 0;
- $item->count_archived = 0;
- $item->count_unpublished = 0;
- $item->count_published = 0;
-
- $id = (int) $item->id;
- $newfeeds = $db->loadObjectList();
-
- foreach ($newfeeds as $newsfeed)
- {
- if ($newsfeed->state == 1)
- {
- $item->count_published = $newsfeed->count;
- }
-
- if ($newsfeed->state == 0)
- {
- $item->count_unpublished = $newsfeed->count;
- }
-
- if ($newsfeed->state == 2)
- {
- $item->count_archived = $newsfeed->count;
- }
-
- if ($newsfeed->state == -2)
- {
- $item->count_trashed = $newsfeed->count;
- }
- }
- }
-
- return $items;
- }
-
- /**
- * Adds Count Items for Tag Manager.
- *
- * @param \stdClass[] &$items The newsfeed tag objects
- * @param string $extension The name of the active view.
- *
- * @return \stdClass[]
- *
- * @since 3.6
- */
- public static function countTagItems(&$items, $extension)
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $parts = explode('.', $extension);
- $section = null;
-
- if (count($parts) > 1)
- {
- $section = $parts[1];
- }
-
- $query->select(
- [
- $db->quoteName('published', 'state'),
- 'COUNT(*) AS ' . $db->quoteName('count'),
- ]
- )
- ->from($db->quoteName('#__contentitem_tag_map', 'ct'));
-
- if ($section === 'category')
- {
- $query->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('ct.content_item_id') . ' = ' . $db->quoteName('c.id'));
- }
- else
- {
- $query->join('LEFT', $db->quoteName('#__newsfeeds', 'c'), $db->quoteName('ct.content_item_id') . ' = ' . $db->quoteName('c.id'));
- }
-
- $query->where(
- [
- $db->quoteName('ct.tag_id') . ' = :id',
- $db->quoteName('ct.type_alias') . ' = :extension',
- ]
- )
- ->bind(':id', $id, ParameterType::INTEGER)
- ->bind(':extension', $extension)
- ->group($db->quoteName('state'));
-
- $db->setQuery($query);
-
- foreach ($items as $item)
- {
- $item->count_trashed = 0;
- $item->count_archived = 0;
- $item->count_unpublished = 0;
- $item->count_published = 0;
-
- // Update ID used in database query.
- $id = (int) $item->id;
- $newsfeeds = $db->loadObjectList();
-
- foreach ($newsfeeds as $newsfeed)
- {
- if ($newsfeed->state == 1)
- {
- $item->count_published = $newsfeed->count;
- }
-
- if ($newsfeed->state == 0)
- {
- $item->count_unpublished = $newsfeed->count;
- }
-
- if ($newsfeed->state == 2)
- {
- $item->count_archived = $newsfeed->count;
- }
-
- if ($newsfeed->state == -2)
- {
- $item->count_trashed = $newsfeed->count;
- }
- }
- }
-
- return $items;
- }
+ /**
+ * Name of the extension
+ *
+ * @var string
+ */
+ public static $extension = 'com_newsfeeds';
+
+ /**
+ * Adds Count Items for Category Manager.
+ *
+ * @param \stdClass[] &$items The banner category objects
+ *
+ * @return \stdClass[]
+ *
+ * @since 3.5
+ */
+ public static function countItems(&$items)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('published', 'state'),
+ 'COUNT(*) AS ' . $db->quoteName('count'),
+ ]
+ )
+ ->from($db->quoteName('#__newsfeeds'))
+ ->where($db->quoteName('catid') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->group($db->quoteName('state'));
+ $db->setQuery($query);
+
+ foreach ($items as $item) {
+ $item->count_trashed = 0;
+ $item->count_archived = 0;
+ $item->count_unpublished = 0;
+ $item->count_published = 0;
+
+ $id = (int) $item->id;
+ $newfeeds = $db->loadObjectList();
+
+ foreach ($newfeeds as $newsfeed) {
+ if ($newsfeed->state == 1) {
+ $item->count_published = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == 0) {
+ $item->count_unpublished = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == 2) {
+ $item->count_archived = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == -2) {
+ $item->count_trashed = $newsfeed->count;
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Adds Count Items for Tag Manager.
+ *
+ * @param \stdClass[] &$items The newsfeed tag objects
+ * @param string $extension The name of the active view.
+ *
+ * @return \stdClass[]
+ *
+ * @since 3.6
+ */
+ public static function countTagItems(&$items, $extension)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $parts = explode('.', $extension);
+ $section = null;
+
+ if (count($parts) > 1) {
+ $section = $parts[1];
+ }
+
+ $query->select(
+ [
+ $db->quoteName('published', 'state'),
+ 'COUNT(*) AS ' . $db->quoteName('count'),
+ ]
+ )
+ ->from($db->quoteName('#__contentitem_tag_map', 'ct'));
+
+ if ($section === 'category') {
+ $query->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('ct.content_item_id') . ' = ' . $db->quoteName('c.id'));
+ } else {
+ $query->join('LEFT', $db->quoteName('#__newsfeeds', 'c'), $db->quoteName('ct.content_item_id') . ' = ' . $db->quoteName('c.id'));
+ }
+
+ $query->where(
+ [
+ $db->quoteName('ct.tag_id') . ' = :id',
+ $db->quoteName('ct.type_alias') . ' = :extension',
+ ]
+ )
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->bind(':extension', $extension)
+ ->group($db->quoteName('state'));
+
+ $db->setQuery($query);
+
+ foreach ($items as $item) {
+ $item->count_trashed = 0;
+ $item->count_archived = 0;
+ $item->count_unpublished = 0;
+ $item->count_published = 0;
+
+ // Update ID used in database query.
+ $id = (int) $item->id;
+ $newsfeeds = $db->loadObjectList();
+
+ foreach ($newsfeeds as $newsfeed) {
+ if ($newsfeed->state == 1) {
+ $item->count_published = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == 0) {
+ $item->count_unpublished = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == 2) {
+ $item->count_archived = $newsfeed->count;
+ }
+
+ if ($newsfeed->state == -2) {
+ $item->count_trashed = $newsfeed->count;
+ }
+ }
+ }
+
+ return $items;
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php b/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
index 46640cb02322b..405f86a920f2e 100644
--- a/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
+++ b/administrator/components/com_newsfeeds/src/Model/NewsfeedModel.php
@@ -1,4 +1,5 @@
id) || $record->published != -2)
- {
- return false;
- }
-
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.delete', 'com_newsfeed.category.' . (int) $record->catid);
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 1.6
- */
- protected function canEditState($record)
- {
- if (!empty($record->catid))
- {
- return Factory::getUser()->authorise('core.edit.state', 'com_newsfeeds.category.' . (int) $record->catid);
- }
-
- return parent::canEditState($record);
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_newsfeeds.newsfeed', 'newsfeed', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
- $form->setFieldAttribute('publish_up', 'disabled', 'true');
- $form->setFieldAttribute('publish_down', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- $form->setFieldAttribute('publish_up', 'filter', 'unset');
- $form->setFieldAttribute('publish_down', 'filter', 'unset');
- }
-
- // Don't allow to change the created_by user if not allowed to access com_users.
- if (!Factory::getUser()->authorise('core.manage', 'com_users'))
- {
- $form->setFieldAttribute('created_by', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_newsfeeds.edit.newsfeed.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Prime some default values.
- if ($this->getState('newsfeed.id') == 0)
- {
- $app = Factory::getApplication();
- $data->set('catid', $app->input->get('catid', $app->getUserState('com_newsfeeds.newsfeeds.filter.category_id'), 'int'));
- }
- }
-
- $this->preprocessData('com_newsfeeds.newsfeed', $data);
-
- return $data;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 3.0
- */
- public function save($data)
- {
- $input = Factory::getApplication()->input;
-
- // Create new category, if needed.
- $createCategory = true;
-
- // If category ID is provided, check if it's valid.
- if (is_numeric($data['catid']) && $data['catid'])
- {
- $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_newsfeeds');
- }
-
- // Save New Category
- if ($createCategory && $this->canCreateCategory())
- {
- $category = [
- // Remove #new# prefix, if exists.
- 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
- 'parent_id' => 1,
- 'extension' => 'com_newsfeeds',
- 'language' => $data['language'],
- 'published' => 1,
- ];
-
- /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
- $categoryModel = Factory::getApplication()->bootComponent('com_categories')
- ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
-
- // Create new category.
- if (!$categoryModel->save($category))
- {
- $this->setError($categoryModel->getError());
-
- return false;
- }
-
- // Get the Category ID.
- $data['catid'] = $categoryModel->getState('category.id');
- }
-
- // Alter the name for save as copy
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['name'] == $origTable->name)
- {
- list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
- $data['name'] = $name;
- $data['alias'] = $alias;
- }
- else
- {
- if ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
- }
-
- $data['published'] = 0;
- }
-
- return parent::save($data);
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- if ($item = parent::getItem($pk))
- {
- // Convert the params field to an array.
- $registry = new Registry($item->metadata);
- $item->metadata = $registry->toArray();
-
- // Convert the images field to an array.
- $registry = new Registry($item->images);
- $item->images = $registry->toArray();
- }
-
- // Load associated newsfeeds items
- $assoc = Associations::isEnabled();
-
- if ($assoc)
- {
- $item->associations = array();
-
- if ($item->id != null)
- {
- $associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', $item->id);
-
- foreach ($associations as $tag => $association)
- {
- $item->associations[$tag] = $association->id;
- }
- }
- }
-
- if (!empty($item->id))
- {
- $item->tags = new TagsHelper;
- $item->tags->getTagIds($item->id, 'com_newsfeeds.newsfeed');
-
- // @todo: We probably don't need this in any client - but needs careful validation
- if (!Factory::getApplication()->isClient('api'))
- {
- $item->metadata['tags'] = $item->tags;
- }
- }
-
- return $item;
- }
-
- /**
- * Prepare and sanitise the table prior to saving.
- *
- * @param \Joomla\CMS\Table\Table $table The table object
- *
- * @return void
- */
- protected function prepareTable($table)
- {
- $date = Factory::getDate();
- $user = Factory::getUser();
-
- $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
- $table->alias = ApplicationHelper::stringURLSafe($table->alias, $table->language);
-
- if (empty($table->alias))
- {
- $table->alias = ApplicationHelper::stringURLSafe($table->name, $table->language);
- }
-
- if (empty($table->id))
- {
- // Set the values
- $table->created = $date->toSql();
-
- // Set ordering to the last item if not set
- if (empty($table->ordering))
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('MAX(' . $db->quoteName('ordering') . ')')
- ->from($db->quoteName('#__newsfeeds'));
- $db->setQuery($query);
- $max = $db->loadResult();
-
- $table->ordering = $max + 1;
- }
- }
- else
- {
- // Set the values
- $table->modified = $date->toSql();
- $table->modified_by = $user->get('id');
- }
-
- // Increment the content version number.
- $table->version++;
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array &$pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function publish(&$pks, $value = 1)
- {
- $result = parent::publish($pks, $value);
-
- // Clean extra cache for newsfeeds
- $this->cleanCache('feed_parser');
-
- return $result;
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- return [
- $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
- ];
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param Form $form The form object.
- * @param array $data The data to be injected into the form
- * @param string $group The plugin group to process
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- if ($this->canCreateCategory())
- {
- $form->setFieldAttribute('catid', 'allowAdd', 'true');
-
- // Add a prefix for categories created on the fly.
- $form->setFieldAttribute('catid', 'customPrefix', '#new#');
- }
-
- // Association newsfeeds items
- if (Associations::isEnabled())
- {
- $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
-
- if (count($languages) > 1)
- {
- $addform = new \SimpleXMLElement('');
- $fields = $addform->addChild('fields');
- $fields->addAttribute('name', 'associations');
- $fieldset = $fields->addChild('fieldset');
- $fieldset->addAttribute('name', 'item_associations');
-
- foreach ($languages as $language)
- {
- $field = $fieldset->addChild('field');
- $field->addAttribute('name', $language->lang_code);
- $field->addAttribute('type', 'modal_newsfeed');
- $field->addAttribute('language', $language->lang_code);
- $field->addAttribute('label', $language->title);
- $field->addAttribute('translate_label', 'false');
- $field->addAttribute('select', 'true');
- $field->addAttribute('new', 'true');
- $field->addAttribute('edit', 'true');
- $field->addAttribute('clear', 'true');
- $field->addAttribute('propagate', 'true');
- }
-
- $form->load($addform, false);
- }
- }
-
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Is the user allowed to create an on the fly category?
- *
- * @return boolean
- *
- * @since 3.6.1
- */
- private function canCreateCategory()
- {
- return Factory::getUser()->authorise('core.create', 'com_newsfeeds');
- }
+ use VersionableModelTrait;
+
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_newsfeeds.newsfeed';
+
+ /**
+ * The context used for the associations table
+ *
+ * @var string
+ * @since 3.4.4
+ */
+ protected $associationsContext = 'com_newsfeeds.item';
+
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_NEWSFEEDS';
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.delete', 'com_newsfeed.category.' . (int) $record->catid);
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canEditState($record)
+ {
+ if (!empty($record->catid)) {
+ return Factory::getUser()->authorise('core.edit.state', 'com_newsfeeds.category.' . (int) $record->catid);
+ }
+
+ return parent::canEditState($record);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_newsfeeds.newsfeed', 'newsfeed', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+ $form->setFieldAttribute('publish_up', 'disabled', 'true');
+ $form->setFieldAttribute('publish_down', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ $form->setFieldAttribute('publish_up', 'filter', 'unset');
+ $form->setFieldAttribute('publish_down', 'filter', 'unset');
+ }
+
+ // Don't allow to change the created_by user if not allowed to access com_users.
+ if (!Factory::getUser()->authorise('core.manage', 'com_users')) {
+ $form->setFieldAttribute('created_by', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_newsfeeds.edit.newsfeed.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Prime some default values.
+ if ($this->getState('newsfeed.id') == 0) {
+ $app = Factory::getApplication();
+ $data->set('catid', $app->input->get('catid', $app->getUserState('com_newsfeeds.newsfeeds.filter.category_id'), 'int'));
+ }
+ }
+
+ $this->preprocessData('com_newsfeeds.newsfeed', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.0
+ */
+ public function save($data)
+ {
+ $input = Factory::getApplication()->input;
+
+ // Create new category, if needed.
+ $createCategory = true;
+
+ // If category ID is provided, check if it's valid.
+ if (is_numeric($data['catid']) && $data['catid']) {
+ $createCategory = !CategoriesHelper::validateCategoryId($data['catid'], 'com_newsfeeds');
+ }
+
+ // Save New Category
+ if ($createCategory && $this->canCreateCategory()) {
+ $category = [
+ // Remove #new# prefix, if exists.
+ 'title' => strpos($data['catid'], '#new#') === 0 ? substr($data['catid'], 5) : $data['catid'],
+ 'parent_id' => 1,
+ 'extension' => 'com_newsfeeds',
+ 'language' => $data['language'],
+ 'published' => 1,
+ ];
+
+ /** @var \Joomla\Component\Categories\Administrator\Model\CategoryModel $categoryModel */
+ $categoryModel = Factory::getApplication()->bootComponent('com_categories')
+ ->getMVCFactory()->createModel('Category', 'Administrator', ['ignore_request' => true]);
+
+ // Create new category.
+ if (!$categoryModel->save($category)) {
+ $this->setError($categoryModel->getError());
+
+ return false;
+ }
+
+ // Get the Category ID.
+ $data['catid'] = $categoryModel->getState('category.id');
+ }
+
+ // Alter the name for save as copy
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['name'] == $origTable->name) {
+ list($name, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['name']);
+ $data['name'] = $name;
+ $data['alias'] = $alias;
+ } else {
+ if ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+ }
+
+ $data['published'] = 0;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ if ($item = parent::getItem($pk)) {
+ // Convert the params field to an array.
+ $registry = new Registry($item->metadata);
+ $item->metadata = $registry->toArray();
+
+ // Convert the images field to an array.
+ $registry = new Registry($item->images);
+ $item->images = $registry->toArray();
+ }
+
+ // Load associated newsfeeds items
+ $assoc = Associations::isEnabled();
+
+ if ($assoc) {
+ $item->associations = array();
+
+ if ($item->id != null) {
+ $associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', $item->id);
+
+ foreach ($associations as $tag => $association) {
+ $item->associations[$tag] = $association->id;
+ }
+ }
+ }
+
+ if (!empty($item->id)) {
+ $item->tags = new TagsHelper();
+ $item->tags->getTagIds($item->id, 'com_newsfeeds.newsfeed');
+
+ // @todo: We probably don't need this in any client - but needs careful validation
+ if (!Factory::getApplication()->isClient('api')) {
+ $item->metadata['tags'] = $item->tags;
+ }
+ }
+
+ return $item;
+ }
+
+ /**
+ * Prepare and sanitise the table prior to saving.
+ *
+ * @param \Joomla\CMS\Table\Table $table The table object
+ *
+ * @return void
+ */
+ protected function prepareTable($table)
+ {
+ $date = Factory::getDate();
+ $user = Factory::getUser();
+
+ $table->name = htmlspecialchars_decode($table->name, ENT_QUOTES);
+ $table->alias = ApplicationHelper::stringURLSafe($table->alias, $table->language);
+
+ if (empty($table->alias)) {
+ $table->alias = ApplicationHelper::stringURLSafe($table->name, $table->language);
+ }
+
+ if (empty($table->id)) {
+ // Set the values
+ $table->created = $date->toSql();
+
+ // Set ordering to the last item if not set
+ if (empty($table->ordering)) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('MAX(' . $db->quoteName('ordering') . ')')
+ ->from($db->quoteName('#__newsfeeds'));
+ $db->setQuery($query);
+ $max = $db->loadResult();
+
+ $table->ordering = $max + 1;
+ }
+ } else {
+ // Set the values
+ $table->modified = $date->toSql();
+ $table->modified_by = $user->get('id');
+ }
+
+ // Increment the content version number.
+ $table->version++;
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array &$pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $result = parent::publish($pks, $value);
+
+ // Clean extra cache for newsfeeds
+ $this->cleanCache('feed_parser');
+
+ return $result;
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ return [
+ $this->getDatabase()->quoteName('catid') . ' = ' . (int) $table->catid,
+ ];
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param Form $form The form object.
+ * @param array $data The data to be injected into the form
+ * @param string $group The plugin group to process
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ if ($this->canCreateCategory()) {
+ $form->setFieldAttribute('catid', 'allowAdd', 'true');
+
+ // Add a prefix for categories created on the fly.
+ $form->setFieldAttribute('catid', 'customPrefix', '#new#');
+ }
+
+ // Association newsfeeds items
+ if (Associations::isEnabled()) {
+ $languages = LanguageHelper::getContentLanguages(false, false, null, 'ordering', 'asc');
+
+ if (count($languages) > 1) {
+ $addform = new \SimpleXMLElement('');
+ $fields = $addform->addChild('fields');
+ $fields->addAttribute('name', 'associations');
+ $fieldset = $fields->addChild('fieldset');
+ $fieldset->addAttribute('name', 'item_associations');
+
+ foreach ($languages as $language) {
+ $field = $fieldset->addChild('field');
+ $field->addAttribute('name', $language->lang_code);
+ $field->addAttribute('type', 'modal_newsfeed');
+ $field->addAttribute('language', $language->lang_code);
+ $field->addAttribute('label', $language->title);
+ $field->addAttribute('translate_label', 'false');
+ $field->addAttribute('select', 'true');
+ $field->addAttribute('new', 'true');
+ $field->addAttribute('edit', 'true');
+ $field->addAttribute('clear', 'true');
+ $field->addAttribute('propagate', 'true');
+ }
+
+ $form->load($addform, false);
+ }
+ }
+
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Is the user allowed to create an on the fly category?
+ *
+ * @return boolean
+ *
+ * @since 3.6.1
+ */
+ private function canCreateCategory()
+ {
+ return Factory::getUser()->authorise('core.create', 'com_newsfeeds');
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php b/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
index c861401ac04f5..8bbc97324ea25 100644
--- a/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
+++ b/administrator/components/com_newsfeeds/src/Model/NewsfeedsModel.php
@@ -1,4 +1,5 @@
input->get('forcedLanguage', '', 'cmd');
-
- // Adjust the context to support modal layouts.
- if ($layout = $app->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- // Adjust the context to support forced languages.
- if ($forcedLanguage)
- {
- $this->context .= '.' . $forcedLanguage;
- }
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_newsfeeds');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
-
- // Force a language.
- if (!empty($forcedLanguage))
- {
- $this->setState('filter.language', $forcedLanguage);
- }
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.category_id');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.language');
- $id .= ':' . $this->getState('filter.level');
- $id .= ':' . serialize($this->getState('filter.tag'));
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.name'),
- $db->quoteName('a.alias'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- $db->quoteName('a.catid'),
- $db->quoteName('a.numarticles'),
- $db->quoteName('a.cache_time'),
- $db->quoteName('a.created_by'),
- $db->quoteName('a.published'),
- $db->quoteName('a.access'),
- $db->quoteName('a.ordering'),
- $db->quoteName('a.language'),
- $db->quoteName('a.publish_up'),
- $db->quoteName('a.publish_down'),
- ]
- )
- )
- ->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- $db->quoteName('uc.name', 'editor'),
- $db->quoteName('ag.title', 'access_level'),
- $db->quoteName('c.title', 'category_title'),
- ]
- )
- ->from($db->quoteName('#__newsfeeds', 'a'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
- ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'));
-
- // Join over the associations.
- if (Associations::isEnabled())
- {
- $subQuery = $db->getQuery(true)
- ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
- ->from($db->quoteName('#__associations', 'asso1'))
- ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
- ->where(
- [
- $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
- $db->quoteName('asso1.context') . ' = ' . $db->quote('com_newsfeeds.item'),
- ]
- );
-
- $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
- }
-
- // Filter by access level.
- if ($access = (int) $this->getState('filter.access'))
- {
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels());
- }
-
- // Filter by published state.
- $published = (string) $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.published') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->where($db->quoteName('a.published') . ' IN (0, 1)');
- }
-
- // Filter by category.
- $categoryId = $this->getState('filter.category_id');
-
- if (is_numeric($categoryId))
- {
- $categoryId = (int) $categoryId;
- $query->where($db->quoteName('a.catid') . ' = :categoryId')
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- }
-
- // Filter on the level.
- if ($level = (int) $this->getState('filter.level'))
- {
- $query->where($db->quoteName('c.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter by search in title
- if ($search = $this->getState('filter.search'))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :search')
- ->bind(':search', $search, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName('a.name') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
-
- // Filter by a single or group of tags.
- $tag = $this->getState('filter.tag');
-
- // Run simplified query when filtering by one tag.
- if (\is_array($tag) && \count($tag) === 1)
- {
- $tag = $tag[0];
- }
-
- if ($tag && \is_array($tag))
- {
- $tag = ArrayHelper::toInteger($tag);
-
- $subQuery = $db->getQuery(true)
- ->select('DISTINCT ' . $db->quoteName('content_item_id'))
- ->from($db->quoteName('#__contentitem_tag_map'))
- ->where(
- [
- $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
- $db->quoteName('type_alias') . ' = ' . $db->quote('com_newsfeeds.newsfeed'),
- ]
- );
-
- $query->join(
- 'INNER',
- '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- );
- }
- elseif ($tag = (int) $tag)
- {
- $query->join(
- 'INNER',
- $db->quoteName('#__contentitem_tag_map', 'tagmap'),
- $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
- )
- ->where(
- [
- $db->quoteName('tagmap.tag_id') . ' = :tag',
- $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_newsfeeds.newsfeed'),
- ]
- )
- ->bind(':tag', $tag, ParameterType::INTEGER);
- }
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 'a.name');
- $orderDirn = $this->state->get('list.direction', 'ASC');
-
- if ($orderCol == 'a.ordering' || $orderCol == 'category_title')
- {
- $ordering = [
- $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
- $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
- ];
- }
- else
- {
- $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
- }
-
- $query->order($ordering);
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'name', 'a.name',
+ 'alias', 'a.alias',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'catid', 'a.catid', 'category_id', 'category_title',
+ 'published', 'a.published',
+ 'access', 'a.access', 'access_level',
+ 'created', 'a.created',
+ 'created_by', 'a.created_by',
+ 'ordering', 'a.ordering',
+ 'language', 'a.language', 'language_title',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'cache_time', 'a.cache_time',
+ 'numarticles',
+ 'tag',
+ 'level', 'c.level',
+ 'tag',
+ );
+
+ if (Associations::isEnabled()) {
+ $config['filter_fields'][] = 'association';
+ }
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.name', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ $forcedLanguage = $app->input->get('forcedLanguage', '', 'cmd');
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Adjust the context to support forced languages.
+ if ($forcedLanguage) {
+ $this->context .= '.' . $forcedLanguage;
+ }
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_newsfeeds');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+
+ // Force a language.
+ if (!empty($forcedLanguage)) {
+ $this->setState('filter.language', $forcedLanguage);
+ }
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.category_id');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.language');
+ $id .= ':' . $this->getState('filter.level');
+ $id .= ':' . serialize($this->getState('filter.tag'));
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.name'),
+ $db->quoteName('a.alias'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ $db->quoteName('a.catid'),
+ $db->quoteName('a.numarticles'),
+ $db->quoteName('a.cache_time'),
+ $db->quoteName('a.created_by'),
+ $db->quoteName('a.published'),
+ $db->quoteName('a.access'),
+ $db->quoteName('a.ordering'),
+ $db->quoteName('a.language'),
+ $db->quoteName('a.publish_up'),
+ $db->quoteName('a.publish_down'),
+ ]
+ )
+ )
+ ->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ $db->quoteName('uc.name', 'editor'),
+ $db->quoteName('ag.title', 'access_level'),
+ $db->quoteName('c.title', 'category_title'),
+ ]
+ )
+ ->from($db->quoteName('#__newsfeeds', 'a'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ag'), $db->quoteName('ag.id') . ' = ' . $db->quoteName('a.access'))
+ ->join('LEFT', $db->quoteName('#__categories', 'c'), $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'));
+
+ // Join over the associations.
+ if (Associations::isEnabled()) {
+ $subQuery = $db->getQuery(true)
+ ->select('COUNT(' . $db->quoteName('asso1.id') . ') > 1')
+ ->from($db->quoteName('#__associations', 'asso1'))
+ ->join('INNER', $db->quoteName('#__associations', 'asso2'), $db->quoteName('asso1.key') . ' = ' . $db->quoteName('asso2.key'))
+ ->where(
+ [
+ $db->quoteName('asso1.id') . ' = ' . $db->quoteName('a.id'),
+ $db->quoteName('asso1.context') . ' = ' . $db->quote('com_newsfeeds.item'),
+ ]
+ );
+
+ $query->select('(' . $subQuery . ') AS ' . $db->quoteName('association'));
+ }
+
+ // Filter by access level.
+ if ($access = (int) $this->getState('filter.access')) {
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ $query->whereIn($db->quoteName('a.access'), $user->getAuthorisedViewLevels());
+ }
+
+ // Filter by published state.
+ $published = (string) $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.published') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->where($db->quoteName('a.published') . ' IN (0, 1)');
+ }
+
+ // Filter by category.
+ $categoryId = $this->getState('filter.category_id');
+
+ if (is_numeric($categoryId)) {
+ $categoryId = (int) $categoryId;
+ $query->where($db->quoteName('a.catid') . ' = :categoryId')
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ }
+
+ // Filter on the level.
+ if ($level = (int) $this->getState('filter.level')) {
+ $query->where($db->quoteName('c.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter by search in title
+ if ($search = $this->getState('filter.search')) {
+ if (stripos($search, 'id:') === 0) {
+ $search = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :search')
+ ->bind(':search', $search, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName('a.name') . ' LIKE :search1 OR ' . $db->quoteName('a.alias') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Filter by a single or group of tags.
+ $tag = $this->getState('filter.tag');
+
+ // Run simplified query when filtering by one tag.
+ if (\is_array($tag) && \count($tag) === 1) {
+ $tag = $tag[0];
+ }
+
+ if ($tag && \is_array($tag)) {
+ $tag = ArrayHelper::toInteger($tag);
+
+ $subQuery = $db->getQuery(true)
+ ->select('DISTINCT ' . $db->quoteName('content_item_id'))
+ ->from($db->quoteName('#__contentitem_tag_map'))
+ ->where(
+ [
+ $db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
+ $db->quoteName('type_alias') . ' = ' . $db->quote('com_newsfeeds.newsfeed'),
+ ]
+ );
+
+ $query->join(
+ 'INNER',
+ '(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ );
+ } elseif ($tag = (int) $tag) {
+ $query->join(
+ 'INNER',
+ $db->quoteName('#__contentitem_tag_map', 'tagmap'),
+ $db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
+ )
+ ->where(
+ [
+ $db->quoteName('tagmap.tag_id') . ' = :tag',
+ $db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_newsfeeds.newsfeed'),
+ ]
+ )
+ ->bind(':tag', $tag, ParameterType::INTEGER);
+ }
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 'a.name');
+ $orderDirn = $this->state->get('list.direction', 'ASC');
+
+ if ($orderCol == 'a.ordering' || $orderCol == 'category_title') {
+ $ordering = [
+ $db->quoteName('c.title') . ' ' . $db->escape($orderDirn),
+ $db->quoteName('a.ordering') . ' ' . $db->escape($orderDirn),
+ ];
+ } else {
+ $ordering = $db->escape($orderCol) . ' ' . $db->escape($orderDirn);
+ }
+
+ $query->order($ordering);
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php b/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
index 0b620ba53c22a..aaa5999f674d1 100644
--- a/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
+++ b/administrator/components/com_newsfeeds/src/Service/HTML/AdministratorService.php
@@ -1,4 +1,5 @@
$associated)
- {
- $associations[$tag] = (int) $associated->id;
- }
+ // Get the associations
+ if ($associations = Associations::getAssociations('com_newsfeeds', '#__newsfeeds', 'com_newsfeeds.item', $newsfeedid)) {
+ foreach ($associations as $tag => $associated) {
+ $associations[$tag] = (int) $associated->id;
+ }
- // Get the associated newsfeed items
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $query
- ->select(
- [
- $db->quoteName('c.id'),
- $db->quoteName('c.name', 'title'),
- $db->quoteName('cat.title', 'category_title'),
- $db->quoteName('l.sef', 'lang_sef'),
- $db->quoteName('l.lang_code'),
- $db->quoteName('l.image'),
- $db->quoteName('l.title', 'language_title'),
- ]
- )
- ->from($db->quoteName('#__newsfeeds', 'c'))
- ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
- ->where(
- [
- $db->quoteName('c.id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')',
- $db->quoteName('c.id') . ' != :id',
- ]
- )
- ->bind(':id', $newsfeedid, ParameterType::INTEGER);
- $db->setQuery($query);
+ // Get the associated newsfeed items
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $query
+ ->select(
+ [
+ $db->quoteName('c.id'),
+ $db->quoteName('c.name', 'title'),
+ $db->quoteName('cat.title', 'category_title'),
+ $db->quoteName('l.sef', 'lang_sef'),
+ $db->quoteName('l.lang_code'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.title', 'language_title'),
+ ]
+ )
+ ->from($db->quoteName('#__newsfeeds', 'c'))
+ ->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('cat.id') . ' = ' . $db->quoteName('c.catid'))
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('c.language') . ' = ' . $db->quoteName('l.lang_code'))
+ ->where(
+ [
+ $db->quoteName('c.id') . ' IN (' . implode(',', $query->bindArray(array_values($associations))) . ')',
+ $db->quoteName('c.id') . ' != :id',
+ ]
+ )
+ ->bind(':id', $newsfeedid, ParameterType::INTEGER);
+ $db->setQuery($query);
- try
- {
- $items = $db->loadObjectList('id');
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
+ try {
+ $items = $db->loadObjectList('id');
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
- if ($items)
- {
- $languages = LanguageHelper::getContentLanguages(array(0, 1));
- $content_languages = array_column($languages, 'lang_code');
+ if ($items) {
+ $languages = LanguageHelper::getContentLanguages(array(0, 1));
+ $content_languages = array_column($languages, 'lang_code');
- foreach ($items as &$item)
- {
- if (in_array($item->lang_code, $content_languages))
- {
- $text = $item->lang_code;
- $url = Route::_('index.php?option=com_newsfeeds&task=newsfeed.edit&id=' . (int) $item->id);
- $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
- . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
- $classes = 'badge bg-secondary';
+ foreach ($items as &$item) {
+ if (in_array($item->lang_code, $content_languages)) {
+ $text = $item->lang_code;
+ $url = Route::_('index.php?option=com_newsfeeds&task=newsfeed.edit&id=' . (int) $item->id);
+ $tooltip = '' . htmlspecialchars($item->language_title, ENT_QUOTES, 'UTF-8') . ' '
+ . htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8') . ' ' . Text::sprintf('JCATEGORY_SPRINTF', $item->category_title);
+ $classes = 'badge bg-secondary';
- $item->link = '' . $text . ' '
- . '' . $tooltip . '
';
- }
- else
- {
- // Display warning if Content Language is trashed or deleted
- Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
- }
- }
- }
+ $item->link = '' . $text . ' '
+ . '' . $tooltip . '
';
+ } else {
+ // Display warning if Content Language is trashed or deleted
+ Factory::getApplication()->enqueueMessage(Text::sprintf('JGLOBAL_ASSOCIATIONS_CONTENTLANGUAGE_WARNING', $item->lang_code), 'warning');
+ }
+ }
+ }
- $html = LayoutHelper::render('joomla.content.associations', $items);
- }
+ $html = LayoutHelper::render('joomla.content.associations', $items);
+ }
- return $html;
- }
+ return $html;
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/Table/NewsfeedTable.php b/administrator/components/com_newsfeeds/src/Table/NewsfeedTable.php
index 6d54fe1acf1a5..915229d3ee494 100644
--- a/administrator/components/com_newsfeeds/src/Table/NewsfeedTable.php
+++ b/administrator/components/com_newsfeeds/src/Table/NewsfeedTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_newsfeeds.newsfeed';
- parent::__construct('#__newsfeeds', 'id', $db);
- $this->setColumnAlias('title', 'name');
- }
-
- /**
- * Overloaded check method to ensure data integrity.
- *
- * @return boolean True on success.
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Check for valid name.
- if (trim($this->name) == '')
- {
- $this->setError(Text::_('COM_NEWSFEEDS_WARNING_PROVIDE_VALID_NAME'));
-
- return false;
- }
-
- if (empty($this->alias))
- {
- $this->alias = $this->name;
- }
-
- $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
-
- if (trim(str_replace('-', '', $this->alias)) == '')
- {
- $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
- }
-
- // Check for a valid category.
- if (!$this->catid = (int) $this->catid)
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
-
- return false;
- }
-
- // Check the publish down date is not earlier than publish up.
- if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up)
- {
- $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
-
- return false;
- }
-
- // Clean up description -- eliminate quotes and <> brackets
- if (!empty($this->metadesc))
- {
- // Only process if not empty
- $bad_characters = array("\"", '<', '>');
- $this->metadesc = StringHelper::str_ireplace($bad_characters, '', $this->metadesc);
- }
-
- if (is_null($this->hits))
- {
- $this->hits = 0;
- }
-
- return true;
- }
-
- /**
- * Overridden \JTable::store to set modified data.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function store($updateNulls = true)
- {
- $date = Factory::getDate();
- $user = Factory::getUser();
-
- // Set created date if not set.
- if (!(int) $this->created)
- {
- $this->created = $date->toSql();
- }
-
- if ($this->id)
- {
- // Existing item
- $this->modified_by = $user->get('id');
- $this->modified = $date->toSql();
- }
- else
- {
- // Field created_by can be set by the user, so we don't touch it if it's set.
- if (empty($this->created_by))
- {
- $this->created_by = $user->get('id');
- }
-
- if (!(int) $this->modified)
- {
- $this->modified = $this->created;
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_by;
- }
- }
-
- // Set publish_up, publish_down to null if not set
- if (!$this->publish_up)
- {
- $this->publish_up = null;
- }
-
- if (!$this->publish_down)
- {
- $this->publish_down = null;
- }
-
- // Verify that the alias is unique
- $table = Table::getInstance('NewsfeedTable', __NAMESPACE__ . '\\', array('dbo' => $this->_db));
-
- if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0))
- {
- $this->setError(Text::_('COM_NEWSFEEDS_ERROR_UNIQUE_ALIAS'));
-
- return false;
- }
-
- // Save links as punycode.
- $this->link = PunycodeHelper::urlToPunycode($this->link);
-
- return parent::store($updateNulls);
- }
-
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ use TaggableTableTrait;
+
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Ensure the params, metadata and images are json encoded in the bind method
+ *
+ * @var array
+ * @since 3.3
+ */
+ protected $_jsonEncode = array('params', 'metadata', 'images');
+
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db A database connector object
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_newsfeeds.newsfeed';
+ parent::__construct('#__newsfeeds', 'id', $db);
+ $this->setColumnAlias('title', 'name');
+ }
+
+ /**
+ * Overloaded check method to ensure data integrity.
+ *
+ * @return boolean True on success.
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Check for valid name.
+ if (trim($this->name) == '') {
+ $this->setError(Text::_('COM_NEWSFEEDS_WARNING_PROVIDE_VALID_NAME'));
+
+ return false;
+ }
+
+ if (empty($this->alias)) {
+ $this->alias = $this->name;
+ }
+
+ $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
+
+ if (trim(str_replace('-', '', $this->alias)) == '') {
+ $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
+ }
+
+ // Check for a valid category.
+ if (!$this->catid = (int) $this->catid) {
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_CATEGORY_REQUIRED'));
+
+ return false;
+ }
+
+ // Check the publish down date is not earlier than publish up.
+ if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up) {
+ $this->setError(Text::_('JGLOBAL_START_PUBLISH_AFTER_FINISH'));
+
+ return false;
+ }
+
+ // Clean up description -- eliminate quotes and <> brackets
+ if (!empty($this->metadesc)) {
+ // Only process if not empty
+ $bad_characters = array("\"", '<', '>');
+ $this->metadesc = StringHelper::str_ireplace($bad_characters, '', $this->metadesc);
+ }
+
+ if (is_null($this->hits)) {
+ $this->hits = 0;
+ }
+
+ return true;
+ }
+
+ /**
+ * Overridden \JTable::store to set modified data.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function store($updateNulls = true)
+ {
+ $date = Factory::getDate();
+ $user = Factory::getUser();
+
+ // Set created date if not set.
+ if (!(int) $this->created) {
+ $this->created = $date->toSql();
+ }
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_by = $user->get('id');
+ $this->modified = $date->toSql();
+ } else {
+ // Field created_by can be set by the user, so we don't touch it if it's set.
+ if (empty($this->created_by)) {
+ $this->created_by = $user->get('id');
+ }
+
+ if (!(int) $this->modified) {
+ $this->modified = $this->created;
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_by;
+ }
+ }
+
+ // Set publish_up, publish_down to null if not set
+ if (!$this->publish_up) {
+ $this->publish_up = null;
+ }
+
+ if (!$this->publish_down) {
+ $this->publish_down = null;
+ }
+
+ // Verify that the alias is unique
+ $table = Table::getInstance('NewsfeedTable', __NAMESPACE__ . '\\', array('dbo' => $this->_db));
+
+ if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0)) {
+ $this->setError(Text::_('COM_NEWSFEEDS_ERROR_UNIQUE_ALIAS'));
+
+ return false;
+ }
+
+ // Save links as punycode.
+ $this->link = PunycodeHelper::urlToPunycode($this->link);
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/View/Newsfeed/HtmlView.php b/administrator/components/com_newsfeeds/src/View/Newsfeed/HtmlView.php
index 612996ed2182a..1b0385e5c8b7b 100644
--- a/administrator/components/com_newsfeeds/src/View/Newsfeed/HtmlView.php
+++ b/administrator/components/com_newsfeeds/src/View/Newsfeed/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->item = $this->get('Item');
- $this->form = $this->get('Form');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // If we are forcing a language in modal (used for associations).
- if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd'))
- {
- // Set the language field to the forcedLanguage and disable changing it.
- $this->form->setValue('language', null, $forcedLanguage);
- $this->form->setFieldAttribute('language', 'readonly', 'true');
-
- // Only allow to select categories with All language or with the forced language.
- $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
-
- // Only allow to select tags with All language or with the forced language.
- $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
-
- // Since we don't track these assets at the item level, use the category id.
- $canDo = ContentHelper::getActions('com_newsfeeds', 'category', $this->item->catid);
-
- $title = $isNew ? Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEED_NEW') : Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEED_EDIT');
- ToolbarHelper::title($title, 'rss newsfeeds');
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if (!$checkedOut && ($canDo->get('core.edit') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0))
- {
- ToolbarHelper::apply('newsfeed.apply');
-
- $toolbarButtons[] = ['save', 'newsfeed.save'];
- }
-
- if (!$checkedOut && count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0)
- {
- $toolbarButtons[] = ['save2new', 'newsfeed.save2new'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'newsfeed.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('newsfeed.cancel');
- }
- else
- {
- ToolbarHelper::cancel('newsfeed.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit'))
- {
- ToolbarHelper::versions('com_newsfeeds.newsfeed', $this->item->id);
- }
- }
-
- if (!$isNew && Associations::isEnabled() && ComponentHelper::isEnabled('com_associations'))
- {
- ToolbarHelper::custom('newsfeed.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('News_Feeds:_New_or_Edit');
- }
+ /**
+ * The item object for the newsfeed
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 1.6
+ */
+ protected $item;
+
+ /**
+ * The form object for the newsfeed
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 1.6
+ */
+ protected $form;
+
+ /**
+ * The model state of the newsfeed
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ *
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->item = $this->get('Item');
+ $this->form = $this->get('Form');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // If we are forcing a language in modal (used for associations).
+ if ($this->getLayout() === 'modal' && $forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'cmd')) {
+ // Set the language field to the forcedLanguage and disable changing it.
+ $this->form->setValue('language', null, $forcedLanguage);
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
+
+ // Only allow to select categories with All language or with the forced language.
+ $this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
+
+ // Only allow to select tags with All language or with the forced language.
+ $this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
+
+ // Since we don't track these assets at the item level, use the category id.
+ $canDo = ContentHelper::getActions('com_newsfeeds', 'category', $this->item->catid);
+
+ $title = $isNew ? Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEED_NEW') : Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEED_EDIT');
+ ToolbarHelper::title($title, 'rss newsfeeds');
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if (!$checkedOut && ($canDo->get('core.edit') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0)) {
+ ToolbarHelper::apply('newsfeed.apply');
+
+ $toolbarButtons[] = ['save', 'newsfeed.save'];
+ }
+
+ if (!$checkedOut && count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0) {
+ $toolbarButtons[] = ['save2new', 'newsfeed.save2new'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'newsfeed.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('newsfeed.cancel');
+ } else {
+ ToolbarHelper::cancel('newsfeed.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit')) {
+ ToolbarHelper::versions('com_newsfeeds.newsfeed', $this->item->id);
+ }
+ }
+
+ if (!$isNew && Associations::isEnabled() && ComponentHelper::isEnabled('com_associations')) {
+ ToolbarHelper::custom('newsfeed.editAssociations', 'contract', '', 'JTOOLBAR_ASSOCIATIONS', false, false);
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('News_Feeds:_New_or_Edit');
+ }
}
diff --git a/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php b/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
index 0fbe696d4f476..4219a2cff9122 100644
--- a/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
+++ b/administrator/components/com_newsfeeds/src/View/Newsfeeds/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // We don't need toolbar in the modal layout.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
- else
- {
- // In article associations modal we need to remove language filter if forcing a language.
- // We also need to change the category filter to show show categories with All or the forced language.
- if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD'))
- {
- // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
- $languageXml = new \SimpleXMLElement(' ');
- $this->filterForm->setField($languageXml, 'filter', true);
-
- // Also, unset the active language filter so the search tools is not open by default with this filter.
- unset($this->activeFilters['language']);
-
- // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
- $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $state = $this->get('State');
- $canDo = ContentHelper::getActions('com_newsfeeds', 'category', $state->get('filter.category_id'));
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEEDS'), 'rss newsfeeds');
-
- if ($canDo->get('core.create') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0)
- {
- $toolbar->addNew('newsfeed.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('newsfeeds.publish')->listCheck(true);
- $childBar->unpublish('newsfeeds.unpublish')->listCheck(true);
- $childBar->archive('newsfeeds.archive')->listCheck(true);
-
- if ($user->authorise('core.admin'))
- {
- $childBar->checkin('newsfeeds.checkin')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') != -2)
- {
- $childBar->trash('newsfeeds.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_newsfeeds')
- && $user->authorise('core.edit', 'com_newsfeeds')
- && $user->authorise('core.edit.state', 'com_newsfeeds'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('newsfeeds.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($user->authorise('core.admin', 'com_newsfeeds') || $user->authorise('core.options', 'com_newsfeeds'))
- {
- $toolbar->preferences('com_newsfeeds');
- }
-
- $toolbar->help('News_Feeds');
- }
+ /**
+ * The list of newsfeeds
+ *
+ * @var CMSObject
+ *
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ *
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // We don't need toolbar in the modal layout.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ } else {
+ // In article associations modal we need to remove language filter if forcing a language.
+ // We also need to change the category filter to show show categories with All or the forced language.
+ if ($forcedLanguage = Factory::getApplication()->input->get('forcedLanguage', '', 'CMD')) {
+ // If the language is forced we can't allow to select the language, so transform the language selector filter into a hidden field.
+ $languageXml = new \SimpleXMLElement(' ');
+ $this->filterForm->setField($languageXml, 'filter', true);
+
+ // Also, unset the active language filter so the search tools is not open by default with this filter.
+ unset($this->activeFilters['language']);
+
+ // One last changes needed is to change the category filter to just show categories with All language or with the forced language.
+ $this->filterForm->setFieldAttribute('category_id', 'language', '*,' . $forcedLanguage, 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $state = $this->get('State');
+ $canDo = ContentHelper::getActions('com_newsfeeds', 'category', $state->get('filter.category_id'));
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_NEWSFEEDS_MANAGER_NEWSFEEDS'), 'rss newsfeeds');
+
+ if ($canDo->get('core.create') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0) {
+ $toolbar->addNew('newsfeed.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('newsfeeds.publish')->listCheck(true);
+ $childBar->unpublish('newsfeeds.unpublish')->listCheck(true);
+ $childBar->archive('newsfeeds.archive')->listCheck(true);
+
+ if ($user->authorise('core.admin')) {
+ $childBar->checkin('newsfeeds.checkin')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') != -2) {
+ $childBar->trash('newsfeeds.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_newsfeeds')
+ && $user->authorise('core.edit', 'com_newsfeeds')
+ && $user->authorise('core.edit.state', 'com_newsfeeds')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('newsfeeds.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin', 'com_newsfeeds') || $user->authorise('core.options', 'com_newsfeeds')) {
+ $toolbar->preferences('com_newsfeeds');
+ }
+
+ $toolbar->help('News_Feeds');
+ }
}
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeed/edit.php b/administrator/components/com_newsfeeds/tmpl/newsfeed/edit.php
index 34dcd86d2be5b..dd09e694e8368 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeed/edit.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeed/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$input = $app->input;
@@ -38,82 +39,82 @@
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeed/edit_display.php b/administrator/components/com_newsfeeds/tmpl/newsfeed/edit_display.php
index 9ba2e1a89b48f..889b0677275ba 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeed/edit_display.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeed/edit_display.php
@@ -1,4 +1,5 @@
-
-
-
-
+
+
+
+
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeed/modal.php b/administrator/components/com_newsfeeds/tmpl/newsfeed/modal.php
index fcf153534e31c..91f2a029c98ee 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeed/modal.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeed/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
index 7611fe76a8789..8fa3553429ed1 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -29,172 +30,172 @@
$saveOrder = $listOrder == 'a.ordering';
$assoc = Associations::isEnabled();
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_newsfeeds&task=newsfeeds.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_newsfeeds&task=newsfeeds.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_body.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_body.php
index 71e3729811e86..0578eea2932b0 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_body.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Multilanguage;
@@ -15,32 +17,32 @@
?>
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_footer.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_footer.php
index b8394dfdf6527..f43ed8b7b5b8b 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_footer.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/default_batch_footer.php
@@ -1,4 +1,5 @@
-
+
-
+
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/emptystate.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/emptystate.php
index 64c1fd1b22b52..234282c353b0e 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/emptystate.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/emptystate.php
@@ -1,4 +1,5 @@
'COM_NEWSFEEDS',
- 'formURL' => 'index.php?option=com_newsfeeds&view=newsfeeds',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:News_Feeds',
- 'icon' => 'icon-rss newsfeeds',
+ 'textPrefix' => 'COM_NEWSFEEDS',
+ 'formURL' => 'index.php?option=com_newsfeeds&view=newsfeeds',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:News_Feeds',
+ 'icon' => 'icon-rss newsfeeds',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_newsfeeds') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0)
-{
- $displayData['createURL'] = 'index.php?option=com_newsfeeds&task=newsfeed.add';
+if ($user->authorise('core.create', 'com_newsfeeds') || count($user->getAuthorisedCategories('com_newsfeeds', 'core.create')) > 0) {
+ $displayData['createURL'] = 'index.php?option=com_newsfeeds&task=newsfeed.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_newsfeeds/tmpl/newsfeeds/modal.php b/administrator/components/com_newsfeeds/tmpl/newsfeeds/modal.php
index 6ab5819394f2a..c99d6372b0724 100644
--- a/administrator/components/com_newsfeeds/tmpl/newsfeeds/modal.php
+++ b/administrator/components/com_newsfeeds/tmpl/newsfeeds/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_plugins/helpers/plugins.php b/administrator/components/com_plugins/helpers/plugins.php
index a2da98baa77f8..81ef529218600 100644
--- a/administrator/components/com_plugins/helpers/plugins.php
+++ b/administrator/components/com_plugins/helpers/plugins.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Plugins component helper.
@@ -18,5 +21,4 @@
*/
class PluginsHelper extends \Joomla\Component\Plugins\Administrator\Helper\PluginsHelper
{
-
}
diff --git a/administrator/components/com_plugins/services/provider.php b/administrator/components/com_plugins/services/provider.php
index a36ebdc941949..44576bab87204 100644
--- a/administrator/components/com_plugins/services/provider.php
+++ b/administrator/components/com_plugins/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Plugins'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Plugins'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Plugins'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Plugins'));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_plugins/src/Controller/DisplayController.php b/administrator/components/com_plugins/src/Controller/DisplayController.php
index bf6cbc9179085..5f3e173eb1e3d 100644
--- a/administrator/components/com_plugins/src/Controller/DisplayController.php
+++ b/administrator/components/com_plugins/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'plugins');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('extension_id');
-
- // Check for edit form.
- if ($view == 'plugin' && $layout == 'edit' && !$this->checkEditId('com_plugins.edit.plugin', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_plugins&view=plugins', false));
-
- return false;
- }
-
- parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'plugins';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $view = $this->input->get('view', 'plugins');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('extension_id');
+
+ // Check for edit form.
+ if ($view == 'plugin' && $layout == 'edit' && !$this->checkEditId('com_plugins.edit.plugin', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_plugins&view=plugins', false));
+
+ return false;
+ }
+
+ parent::display();
+ }
}
diff --git a/administrator/components/com_plugins/src/Controller/PluginController.php b/administrator/components/com_plugins/src/Controller/PluginController.php
index 8585648f24198..04f04cb0e49a8 100644
--- a/administrator/components/com_plugins/src/Controller/PluginController.php
+++ b/administrator/components/com_plugins/src/Controller/PluginController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Plugins\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Plugins\Administrator\Controller;
use Joomla\CMS\MVC\Controller\FormController;
diff --git a/administrator/components/com_plugins/src/Controller/PluginsController.php b/administrator/components/com_plugins/src/Controller/PluginsController.php
index aa0883e13569d..9c647466f15dc 100644
--- a/administrator/components/com_plugins/src/Controller/PluginsController.php
+++ b/administrator/components/com_plugins/src/Controller/PluginsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to get the number of activated plugins
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('Plugins');
-
- $model->setState('filter.enabled', 1);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_PLUGINS_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_PLUGINS_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Plugin', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to get the number of activated plugins
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Plugins');
+
+ $model->setState('filter.enabled', 1);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_PLUGINS_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_PLUGINS_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
}
diff --git a/administrator/components/com_plugins/src/Field/PluginElementField.php b/administrator/components/com_plugins/src/Field/PluginElementField.php
index 4f50bc7a0a48f..2b4f3d268bd9f 100644
--- a/administrator/components/com_plugins/src/Field/PluginElementField.php
+++ b/administrator/components/com_plugins/src/Field/PluginElementField.php
@@ -1,4 +1,5 @@
getDatabase();
- $folder = $this->form->getValue('folder');
+ /**
+ * Builds the query for the ordering list.
+ *
+ * @return \Joomla\Database\DatabaseQuery The query for the ordering form field.
+ */
+ protected function getQuery()
+ {
+ $db = $this->getDatabase();
+ $folder = $this->form->getValue('folder');
- // Build the query for the ordering list.
- $query = $db->getQuery(true)
- ->select(
- array(
- $db->quoteName('ordering', 'value'),
- $db->quoteName('name', 'text'),
- $db->quoteName('type'),
- $db->quote('folder'),
- $db->quote('extension_id')
- )
- )
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = :folder')
- ->order($db->quoteName('ordering'))
- ->bind(':folder', $folder);
+ // Build the query for the ordering list.
+ $query = $db->getQuery(true)
+ ->select(
+ array(
+ $db->quoteName('ordering', 'value'),
+ $db->quoteName('name', 'text'),
+ $db->quoteName('type'),
+ $db->quote('folder'),
+ $db->quote('extension_id')
+ )
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = :folder')
+ ->order($db->quoteName('ordering'))
+ ->bind(':folder', $folder);
- return $query;
- }
+ return $query;
+ }
- /**
- * Retrieves the current Item's Id.
- *
- * @return integer The current item ID.
- */
- protected function getItemId()
- {
- return (int) $this->form->getValue('extension_id');
- }
+ /**
+ * Retrieves the current Item's Id.
+ *
+ * @return integer The current item ID.
+ */
+ protected function getItemId()
+ {
+ return (int) $this->form->getValue('extension_id');
+ }
}
diff --git a/administrator/components/com_plugins/src/Helper/PluginsHelper.php b/administrator/components/com_plugins/src/Helper/PluginsHelper.php
index 39bcf135b691c..56acb59239390 100644
--- a/administrator/components/com_plugins/src/Helper/PluginsHelper.php
+++ b/administrator/components/com_plugins/src/Helper/PluginsHelper.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select('DISTINCT(folder) AS value, folder AS text')
- ->from('#__extensions')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->order('folder');
-
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- return $options;
- }
-
- /**
- * Returns a list of elements filter options.
- *
- * @return string The HTML code for the select tag
- */
- public static function elementOptions()
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select('DISTINCT(element) AS value, element AS text')
- ->from('#__extensions')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->order('element');
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- return $options;
- }
-
- /**
- * Parse the template file.
- *
- * @param string $templateBaseDir Base path to the template directory.
- * @param string $templateDir Template directory.
- *
- * @return CMSObject|bool
- */
- public function parseXMLTemplateFile($templateBaseDir, $templateDir)
- {
- $data = new CMSObject;
-
- // Check of the xml file exists.
- $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
-
- if (is_file($filePath))
- {
- $xml = Installer::parseXMLInstallFile($filePath);
-
- if ($xml['type'] != 'template')
- {
- return false;
- }
-
- foreach ($xml as $key => $value)
- {
- $data->set($key, $value);
- }
- }
-
- return $data;
- }
+ public static $extension = 'com_plugins';
+
+ /**
+ * Returns an array of standard published state filter options.
+ *
+ * @return array The HTML code for the select tag
+ */
+ public static function publishedOptions()
+ {
+ // Build the active state filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '1', 'JENABLED');
+ $options[] = HTMLHelper::_('select.option', '0', 'JDISABLED');
+
+ return $options;
+ }
+
+ /**
+ * Returns a list of folders filter options.
+ *
+ * @return string The HTML code for the select tag
+ */
+ public static function folderOptions()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT(folder) AS value, folder AS text')
+ ->from('#__extensions')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->order('folder');
+
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ return $options;
+ }
+
+ /**
+ * Returns a list of elements filter options.
+ *
+ * @return string The HTML code for the select tag
+ */
+ public static function elementOptions()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT(element) AS value, element AS text')
+ ->from('#__extensions')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->order('element');
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ return $options;
+ }
+
+ /**
+ * Parse the template file.
+ *
+ * @param string $templateBaseDir Base path to the template directory.
+ * @param string $templateDir Template directory.
+ *
+ * @return CMSObject|bool
+ */
+ public function parseXMLTemplateFile($templateBaseDir, $templateDir)
+ {
+ $data = new CMSObject();
+
+ // Check of the xml file exists.
+ $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
+
+ if (is_file($filePath)) {
+ $xml = Installer::parseXMLInstallFile($filePath);
+
+ if ($xml['type'] != 'template') {
+ return false;
+ }
+
+ foreach ($xml as $key => $value) {
+ $data->set($key, $value);
+ }
+ }
+
+ return $data;
+ }
}
diff --git a/administrator/components/com_plugins/src/Model/PluginModel.php b/administrator/components/com_plugins/src/Model/PluginModel.php
index 834c2b077e4ca..73dce3b58a259 100644
--- a/administrator/components/com_plugins/src/Model/PluginModel.php
+++ b/administrator/components/com_plugins/src/Model/PluginModel.php
@@ -1,4 +1,5 @@
'onExtensionAfterSave',
- 'event_before_save' => 'onExtensionBeforeSave',
- 'events_map' => array(
- 'save' => 'extension'
- )
- ), $config
- );
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure.
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // The folder and element vars are passed when saving the form.
- if (empty($data))
- {
- $item = $this->getItem();
- $folder = $item->folder;
- $element = $item->element;
- }
- else
- {
- $folder = ArrayHelper::getValue($data, 'folder', '', 'cmd');
- $element = ArrayHelper::getValue($data, 'element', '', 'cmd');
- }
-
- // Add the default fields directory
- Form::addFieldPath(JPATH_PLUGINS . '/' . $folder . '/' . $element . '/field');
-
- // These variables are used to add data from the plugin XML files.
- $this->setState('item.folder', $folder);
- $this->setState('item.element', $element);
-
- // Get the form.
- $form = $this->loadForm('com_plugins.plugin', 'plugin', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('enabled', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('enabled', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_plugins.edit.plugin.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_plugins.plugin', $data);
-
- return $data;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- */
- public function getItem($pk = null)
- {
- $pk = (!empty($pk)) ? $pk : (int) $this->getState('plugin.id');
-
- $cacheId = $pk;
-
- if (\is_array($cacheId))
- {
- $cacheId = serialize($cacheId);
- }
-
- if (!isset($this->_cache[$cacheId]))
- {
- // Get a row instance.
- $table = $this->getTable();
-
- // Attempt to load the row.
- $return = $table->load(\is_array($pk) ? $pk : ['extension_id' => $pk, 'type' => 'plugin']);
-
- // Check for a table object error.
- if ($return === false)
- {
- return false;
- }
-
- // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
- $properties = $table->getProperties(1);
- $this->_cache[$cacheId] = ArrayHelper::toObject($properties, CMSObject::class);
-
- // Convert the params field to an array.
- $registry = new Registry($table->params);
- $this->_cache[$cacheId]->params = $registry->toArray();
-
- // Get the plugin XML.
- $path = Path::clean(JPATH_PLUGINS . '/' . $table->folder . '/' . $table->element . '/' . $table->element . '.xml');
-
- if (file_exists($path))
- {
- $this->_cache[$cacheId]->xml = simplexml_load_file($path);
- }
- else
- {
- $this->_cache[$cacheId]->xml = null;
- }
- }
-
- return $this->_cache[$cacheId];
- }
-
- /**
- * Returns a reference to the Table object, always creating it.
- *
- * @param string $type The table type to instantiate.
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- */
- public function getTable($type = 'Extension', $prefix = 'JTable', $config = array())
- {
- return Table::getInstance($type, $prefix, $config);
- }
-
- /**
- * Auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- // Execute the parent method.
- parent::populateState();
-
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('extension_id');
- $this->setState('plugin.id', $pk);
- }
-
- /**
- * Preprocess the form.
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group Cache group name.
- *
- * @return mixed True if successful.
- *
- * @since 1.6
- *
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $folder = $this->getState('item.folder');
- $element = $this->getState('item.element');
- $lang = Factory::getLanguage();
-
- // Load the core and/or local language sys file(s) for the ordering field.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('element'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = :folder')
- ->bind(':folder', $folder);
- $db->setQuery($query);
- $elements = $db->loadColumn();
-
- foreach ($elements as $elementa)
- {
- $lang->load('plg_' . $folder . '_' . $elementa . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load('plg_' . $folder . '_' . $elementa . '.sys', JPATH_PLUGINS . '/' . $folder . '/' . $elementa);
- }
-
- if (empty($folder) || empty($element))
- {
- $app = Factory::getApplication();
- $app->redirect(Route::_('index.php?option=com_plugins&view=plugins', false));
- }
-
- $formFile = Path::clean(JPATH_PLUGINS . '/' . $folder . '/' . $element . '/' . $element . '.xml');
-
- if (!file_exists($formFile))
- {
- throw new \Exception(Text::sprintf('COM_PLUGINS_ERROR_FILE_NOT_FOUND', $element . '.xml'));
- }
-
- // Load the core and/or local language file(s).
- $lang->load('plg_' . $folder . '_' . $element, JPATH_ADMINISTRATOR)
- || $lang->load('plg_' . $folder . '_' . $element, JPATH_PLUGINS . '/' . $folder . '/' . $element);
-
- if (file_exists($formFile))
- {
- // Get the plugin form.
- if (!$form->loadFile($formFile, false, '//config'))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Get the help data from the XML file if present.
- $help = $xml->xpath('/extension/help');
-
- if (!empty($help))
- {
- $helpKey = trim((string) $help[0]['key']);
- $helpURL = trim((string) $help[0]['url']);
-
- $this->helpKey = $helpKey ?: $this->helpKey;
- $this->helpURL = $helpURL ?: $this->helpURL;
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 1.6
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('type') . ' = ' . $db->quote($table->type),
- $db->quoteName('folder') . ' = ' . $db->quote($table->folder),
- ];
- }
-
- /**
- * Override method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- // Setup type.
- $data['type'] = 'plugin';
-
- return parent::save($data);
- }
-
- /**
- * Get the necessary data to load an item help screen.
- *
- * @return object An object with key, url, and local properties for loading the item help screen.
- *
- * @since 1.6
- */
- public function getHelp()
- {
- return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
- }
-
- /**
- * Custom clean cache method, plugins are cached in 2 places for different clients.
- *
- * @param string $group Cache group name.
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_plugins');
- }
+ /**
+ * @var string The help screen key for the module.
+ * @since 1.6
+ */
+ protected $helpKey = 'Plugins:_Name_of_Plugin';
+
+ /**
+ * @var string The help screen base URL for the module.
+ * @since 1.6
+ */
+ protected $helpURL;
+
+ /**
+ * @var array An array of cached plugin items.
+ * @since 1.6
+ */
+ protected $_cache;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ $config = array_merge(
+ array(
+ 'event_after_save' => 'onExtensionAfterSave',
+ 'event_before_save' => 'onExtensionBeforeSave',
+ 'events_map' => array(
+ 'save' => 'extension'
+ )
+ ),
+ $config
+ );
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // The folder and element vars are passed when saving the form.
+ if (empty($data)) {
+ $item = $this->getItem();
+ $folder = $item->folder;
+ $element = $item->element;
+ } else {
+ $folder = ArrayHelper::getValue($data, 'folder', '', 'cmd');
+ $element = ArrayHelper::getValue($data, 'element', '', 'cmd');
+ }
+
+ // Add the default fields directory
+ Form::addFieldPath(JPATH_PLUGINS . '/' . $folder . '/' . $element . '/field');
+
+ // These variables are used to add data from the plugin XML files.
+ $this->setState('item.folder', $folder);
+ $this->setState('item.element', $element);
+
+ // Get the form.
+ $form = $this->loadForm('com_plugins.plugin', 'plugin', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('enabled', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('enabled', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_plugins.edit.plugin.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_plugins.plugin', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ */
+ public function getItem($pk = null)
+ {
+ $pk = (!empty($pk)) ? $pk : (int) $this->getState('plugin.id');
+
+ $cacheId = $pk;
+
+ if (\is_array($cacheId)) {
+ $cacheId = serialize($cacheId);
+ }
+
+ if (!isset($this->_cache[$cacheId])) {
+ // Get a row instance.
+ $table = $this->getTable();
+
+ // Attempt to load the row.
+ $return = $table->load(\is_array($pk) ? $pk : ['extension_id' => $pk, 'type' => 'plugin']);
+
+ // Check for a table object error.
+ if ($return === false) {
+ return false;
+ }
+
+ // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
+ $properties = $table->getProperties(1);
+ $this->_cache[$cacheId] = ArrayHelper::toObject($properties, CMSObject::class);
+
+ // Convert the params field to an array.
+ $registry = new Registry($table->params);
+ $this->_cache[$cacheId]->params = $registry->toArray();
+
+ // Get the plugin XML.
+ $path = Path::clean(JPATH_PLUGINS . '/' . $table->folder . '/' . $table->element . '/' . $table->element . '.xml');
+
+ if (file_exists($path)) {
+ $this->_cache[$cacheId]->xml = simplexml_load_file($path);
+ } else {
+ $this->_cache[$cacheId]->xml = null;
+ }
+ }
+
+ return $this->_cache[$cacheId];
+ }
+
+ /**
+ * Returns a reference to the Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate.
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ */
+ public function getTable($type = 'Extension', $prefix = 'JTable', $config = array())
+ {
+ return Table::getInstance($type, $prefix, $config);
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ // Execute the parent method.
+ parent::populateState();
+
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('extension_id');
+ $this->setState('plugin.id', $pk);
+ }
+
+ /**
+ * Preprocess the form.
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group Cache group name.
+ *
+ * @return mixed True if successful.
+ *
+ * @since 1.6
+ *
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $folder = $this->getState('item.folder');
+ $element = $this->getState('item.element');
+ $lang = Factory::getLanguage();
+
+ // Load the core and/or local language sys file(s) for the ordering field.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('element'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = :folder')
+ ->bind(':folder', $folder);
+ $db->setQuery($query);
+ $elements = $db->loadColumn();
+
+ foreach ($elements as $elementa) {
+ $lang->load('plg_' . $folder . '_' . $elementa . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load('plg_' . $folder . '_' . $elementa . '.sys', JPATH_PLUGINS . '/' . $folder . '/' . $elementa);
+ }
+
+ if (empty($folder) || empty($element)) {
+ $app = Factory::getApplication();
+ $app->redirect(Route::_('index.php?option=com_plugins&view=plugins', false));
+ }
+
+ $formFile = Path::clean(JPATH_PLUGINS . '/' . $folder . '/' . $element . '/' . $element . '.xml');
+
+ if (!file_exists($formFile)) {
+ throw new \Exception(Text::sprintf('COM_PLUGINS_ERROR_FILE_NOT_FOUND', $element . '.xml'));
+ }
+
+ // Load the core and/or local language file(s).
+ $lang->load('plg_' . $folder . '_' . $element, JPATH_ADMINISTRATOR)
+ || $lang->load('plg_' . $folder . '_' . $element, JPATH_PLUGINS . '/' . $folder . '/' . $element);
+
+ if (file_exists($formFile)) {
+ // Get the plugin form.
+ if (!$form->loadFile($formFile, false, '//config')) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Get the help data from the XML file if present.
+ $help = $xml->xpath('/extension/help');
+
+ if (!empty($help)) {
+ $helpKey = trim((string) $help[0]['key']);
+ $helpURL = trim((string) $help[0]['url']);
+
+ $this->helpKey = $helpKey ?: $this->helpKey;
+ $this->helpURL = $helpURL ?: $this->helpURL;
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 1.6
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('type') . ' = ' . $db->quote($table->type),
+ $db->quoteName('folder') . ' = ' . $db->quote($table->folder),
+ ];
+ }
+
+ /**
+ * Override method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ // Setup type.
+ $data['type'] = 'plugin';
+
+ return parent::save($data);
+ }
+
+ /**
+ * Get the necessary data to load an item help screen.
+ *
+ * @return object An object with key, url, and local properties for loading the item help screen.
+ *
+ * @since 1.6
+ */
+ public function getHelp()
+ {
+ return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
+ }
+
+ /**
+ * Custom clean cache method, plugins are cached in 2 places for different clients.
+ *
+ * @param string $group Cache group name.
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_plugins');
+ }
}
diff --git a/administrator/components/com_plugins/src/Model/PluginsModel.php b/administrator/components/com_plugins/src/Model/PluginsModel.php
index 6672c88654f9e..c2edcd2fd8e07 100644
--- a/administrator/components/com_plugins/src/Model/PluginsModel.php
+++ b/administrator/components/com_plugins/src/Model/PluginsModel.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Plugins\Administrator\Model;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Plugins\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
@@ -25,282 +25,262 @@
*/
class PluginsModel extends ListModel
{
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- *
- * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
- * @since 3.2
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null)
- {
- if (empty($config['filter_fields']))
- {
- $config['filter_fields'] = array(
- 'extension_id', 'a.extension_id',
- 'name', 'a.name',
- 'folder', 'a.folder',
- 'element', 'a.element',
- 'checked_out', 'a.checked_out',
- 'checked_out_time', 'a.checked_out_time',
- 'state', 'a.state',
- 'enabled', 'a.enabled',
- 'access', 'a.access', 'access_level',
- 'ordering', 'a.ordering',
- 'client_id', 'a.client_id',
- );
- }
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'folder', $direction = 'asc')
- {
- // Load the parameters.
- $params = ComponentHelper::getParams('com_plugins');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.enabled');
- $id .= ':' . $this->getState('filter.folder');
- $id .= ':' . $this->getState('filter.element');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Returns an object list.
- *
- * @param \Joomla\Database\DatabaseQuery $query A database query object.
- * @param integer $limitstart Offset.
- * @param integer $limit The number of records.
- *
- * @return array
- */
- protected function _getList($query, $limitstart = 0, $limit = 0)
- {
- $search = $this->getState('filter.search');
- $ordering = $this->getState('list.ordering', 'ordering');
-
- // If "Sort Table By:" is not set, set ordering to name
- if ($ordering == '')
- {
- $ordering = 'name';
- }
-
- $db = $this->getDatabase();
-
- if ($ordering == 'name' || (!empty($search) && stripos($search, 'id:') !== 0))
- {
- $db->setQuery($query);
- $result = $db->loadObjectList();
- $this->translate($result);
-
- if (!empty($search))
- {
- $escapedSearchString = $this->refineSearchStringToRegex($search, '/');
-
- foreach ($result as $i => $item)
- {
- if (!preg_match("/$escapedSearchString/i", $item->name))
- {
- unset($result[$i]);
- }
- }
- }
-
- $orderingDirection = strtolower($this->getState('list.direction'));
- $direction = ($orderingDirection == 'desc') ? -1 : 1;
- $result = ArrayHelper::sortObjects($result, $ordering, $direction, true, true);
-
- $total = count($result);
- $this->cache[$this->getStoreId('getTotal')] = $total;
-
- if ($total < $limitstart)
- {
- $limitstart = 0;
- }
-
- $this->cache[$this->getStoreId('getStart')] = $limitstart;
-
- return array_slice($result, $limitstart, $limit ?: null);
- }
- else
- {
- if ($ordering == 'ordering')
- {
- $query->order('a.folder ASC');
- $ordering = 'a.ordering';
- }
-
- $query->order($db->quoteName($ordering) . ' ' . $this->getState('list.direction'));
-
- if ($ordering == 'folder')
- {
- $query->order('a.ordering ASC');
- }
-
- $result = parent::_getList($query, $limitstart, $limit);
- $this->translate($result);
-
- return $result;
- }
- }
-
- /**
- * Translate a list of objects.
- *
- * @param array &$items The array of objects.
- *
- * @return array The array of translated objects.
- */
- protected function translate(&$items)
- {
- $lang = Factory::getLanguage();
-
- foreach ($items as &$item)
- {
- $source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element;
- $extension = 'plg_' . $item->folder . '_' . $item->element;
- $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($extension . '.sys', $source);
- $item->name = Text::_($item->name);
- }
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.extension_id , a.name, a.element, a.folder, a.checked_out, a.checked_out_time,' .
- ' a.enabled, a.access, a.ordering, a.note'
- )
- )
- ->from($db->quoteName('#__extensions') . ' AS a')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
-
- // Join over the users for the checked out user.
- $query->select('uc.name AS editor')
- ->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
-
- // Join over the asset groups.
- $query->select('ag.title AS access_level')
- ->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
-
- // Filter by access level.
- if ($access = $this->getState('filter.access'))
- {
- $access = (int) $access;
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Filter by published state.
- $published = (string) $this->getState('filter.enabled');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.enabled') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->whereIn($db->quoteName('a.enabled'), [0, 1]);
- }
-
- // Filter by state.
- $query->where('a.state >= 0');
-
- // Filter by folder.
- if ($folder = $this->getState('filter.folder'))
- {
- $query->where($db->quoteName('a.folder') . ' = :folder')
- ->bind(':folder', $folder);
- }
-
- // Filter by element.
- if ($element = $this->getState('filter.element'))
- {
- $query->where($db->quoteName('a.element') . ' = :element')
- ->bind(':element', $element);
- }
-
- // Filter by search in name or id.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.extension_id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- }
-
- return $query;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 3.5
- */
- protected function loadFormData()
- {
- $data = parent::loadFormData();
-
- // Set the selected filter values for pages that use the Layouts for filtering
- $data->list['sortTable'] = $this->state->get('list.ordering');
- $data->list['directionTable'] = $this->state->get('list.direction');
-
- return $data;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'extension_id', 'a.extension_id',
+ 'name', 'a.name',
+ 'folder', 'a.folder',
+ 'element', 'a.element',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'state', 'a.state',
+ 'enabled', 'a.enabled',
+ 'access', 'a.access', 'access_level',
+ 'ordering', 'a.ordering',
+ 'client_id', 'a.client_id',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'folder', $direction = 'asc')
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_plugins');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.enabled');
+ $id .= ':' . $this->getState('filter.folder');
+ $id .= ':' . $this->getState('filter.element');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Returns an object list.
+ *
+ * @param \Joomla\Database\DatabaseQuery $query A database query object.
+ * @param integer $limitstart Offset.
+ * @param integer $limit The number of records.
+ *
+ * @return array
+ */
+ protected function _getList($query, $limitstart = 0, $limit = 0)
+ {
+ $search = $this->getState('filter.search');
+ $ordering = $this->getState('list.ordering', 'ordering');
+
+ // If "Sort Table By:" is not set, set ordering to name
+ if ($ordering == '') {
+ $ordering = 'name';
+ }
+
+ $db = $this->getDatabase();
+
+ if ($ordering == 'name' || (!empty($search) && stripos($search, 'id:') !== 0)) {
+ $db->setQuery($query);
+ $result = $db->loadObjectList();
+ $this->translate($result);
+
+ if (!empty($search)) {
+ $escapedSearchString = $this->refineSearchStringToRegex($search, '/');
+
+ foreach ($result as $i => $item) {
+ if (!preg_match("/$escapedSearchString/i", $item->name)) {
+ unset($result[$i]);
+ }
+ }
+ }
+
+ $orderingDirection = strtolower($this->getState('list.direction'));
+ $direction = ($orderingDirection == 'desc') ? -1 : 1;
+ $result = ArrayHelper::sortObjects($result, $ordering, $direction, true, true);
+
+ $total = count($result);
+ $this->cache[$this->getStoreId('getTotal')] = $total;
+
+ if ($total < $limitstart) {
+ $limitstart = 0;
+ }
+
+ $this->cache[$this->getStoreId('getStart')] = $limitstart;
+
+ return array_slice($result, $limitstart, $limit ?: null);
+ } else {
+ if ($ordering == 'ordering') {
+ $query->order('a.folder ASC');
+ $ordering = 'a.ordering';
+ }
+
+ $query->order($db->quoteName($ordering) . ' ' . $this->getState('list.direction'));
+
+ if ($ordering == 'folder') {
+ $query->order('a.ordering ASC');
+ }
+
+ $result = parent::_getList($query, $limitstart, $limit);
+ $this->translate($result);
+
+ return $result;
+ }
+ }
+
+ /**
+ * Translate a list of objects.
+ *
+ * @param array &$items The array of objects.
+ *
+ * @return array The array of translated objects.
+ */
+ protected function translate(&$items)
+ {
+ $lang = Factory::getLanguage();
+
+ foreach ($items as &$item) {
+ $source = JPATH_PLUGINS . '/' . $item->folder . '/' . $item->element;
+ $extension = 'plg_' . $item->folder . '_' . $item->element;
+ $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($extension . '.sys', $source);
+ $item->name = Text::_($item->name);
+ }
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.extension_id , a.name, a.element, a.folder, a.checked_out, a.checked_out_time,' .
+ ' a.enabled, a.access, a.ordering, a.note'
+ )
+ )
+ ->from($db->quoteName('#__extensions') . ' AS a')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
+
+ // Join over the users for the checked out user.
+ $query->select('uc.name AS editor')
+ ->join('LEFT', '#__users AS uc ON uc.id=a.checked_out');
+
+ // Join over the asset groups.
+ $query->select('ag.title AS access_level')
+ ->join('LEFT', '#__viewlevels AS ag ON ag.id = a.access');
+
+ // Filter by access level.
+ if ($access = $this->getState('filter.access')) {
+ $access = (int) $access;
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Filter by published state.
+ $published = (string) $this->getState('filter.enabled');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.enabled') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->whereIn($db->quoteName('a.enabled'), [0, 1]);
+ }
+
+ // Filter by state.
+ $query->where('a.state >= 0');
+
+ // Filter by folder.
+ if ($folder = $this->getState('filter.folder')) {
+ $query->where($db->quoteName('a.folder') . ' = :folder')
+ ->bind(':folder', $folder);
+ }
+
+ // Filter by element.
+ if ($element = $this->getState('filter.element')) {
+ $query->where($db->quoteName('a.element') . ' = :element')
+ ->bind(':element', $element);
+ }
+
+ // Filter by search in name or id.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.extension_id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 3.5
+ */
+ protected function loadFormData()
+ {
+ $data = parent::loadFormData();
+
+ // Set the selected filter values for pages that use the Layouts for filtering
+ $data->list['sortTable'] = $this->state->get('list.ordering');
+ $data->list['directionTable'] = $this->state->get('list.direction');
+
+ return $data;
+ }
}
diff --git a/administrator/components/com_plugins/src/View/Plugin/HtmlView.php b/administrator/components/com_plugins/src/View/Plugin/HtmlView.php
index 888a2303002ab..313b3056e49b5 100644
--- a/administrator/components/com_plugins/src/View/Plugin/HtmlView.php
+++ b/administrator/components/com_plugins/src/View/Plugin/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->item = $this->get('Item');
- $this->form = $this->get('Form');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $canDo = ContentHelper::getActions('com_plugins');
-
- ToolbarHelper::title(Text::sprintf('COM_PLUGINS_MANAGER_PLUGIN', Text::_($this->item->name)), 'plug plugin');
-
- // If not checked out, can save the item.
- if ($canDo->get('core.edit'))
- {
- ToolbarHelper::apply('plugin.apply');
-
- ToolbarHelper::save('plugin.save');
- }
-
- ToolbarHelper::cancel('plugin.cancel', 'JTOOLBAR_CLOSE');
- ToolbarHelper::divider();
-
- // Get the help information for the plugin item.
- $lang = Factory::getLanguage();
-
- $help = $this->get('Help');
-
- if ($help->url && $lang->hasKey($help->url))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($help->url);
- $lang->setDebug($debug);
- }
- else
- {
- $url = null;
- }
-
- ToolbarHelper::inlinehelp();
- ToolbarHelper::help($help->key, false, $url);
- }
+ /**
+ * The item object for the newsfeed
+ *
+ * @var CMSObject
+ */
+ protected $item;
+
+ /**
+ * The form object for the newsfeed
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The model state of the newsfeed
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->item = $this->get('Item');
+ $this->form = $this->get('Form');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $canDo = ContentHelper::getActions('com_plugins');
+
+ ToolbarHelper::title(Text::sprintf('COM_PLUGINS_MANAGER_PLUGIN', Text::_($this->item->name)), 'plug plugin');
+
+ // If not checked out, can save the item.
+ if ($canDo->get('core.edit')) {
+ ToolbarHelper::apply('plugin.apply');
+
+ ToolbarHelper::save('plugin.save');
+ }
+
+ ToolbarHelper::cancel('plugin.cancel', 'JTOOLBAR_CLOSE');
+ ToolbarHelper::divider();
+
+ // Get the help information for the plugin item.
+ $lang = Factory::getLanguage();
+
+ $help = $this->get('Help');
+
+ if ($help->url && $lang->hasKey($help->url)) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($help->url);
+ $lang->setDebug($debug);
+ } else {
+ $url = null;
+ }
+
+ ToolbarHelper::inlinehelp();
+ ToolbarHelper::help($help->key, false, $url);
+ }
}
diff --git a/administrator/components/com_plugins/src/View/Plugins/HtmlView.php b/administrator/components/com_plugins/src/View/Plugins/HtmlView.php
index dae68430e1421..b283d03bfea56 100644
--- a/administrator/components/com_plugins/src/View/Plugins/HtmlView.php
+++ b/administrator/components/com_plugins/src/View/Plugins/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_plugins');
-
- ToolbarHelper::title(Text::_('COM_PLUGINS_MANAGER_PLUGINS'), 'plug plugin');
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- if ($canDo->get('core.edit.state'))
- {
- $toolbar->publish('plugins.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
- $toolbar->unpublish('plugins.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
- $toolbar->checkin('plugins.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.admin'))
- {
- $toolbar->preferences('com_plugins');
- }
-
- $toolbar->help('Plugins');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_plugins');
+
+ ToolbarHelper::title(Text::_('COM_PLUGINS_MANAGER_PLUGINS'), 'plug plugin');
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ if ($canDo->get('core.edit.state')) {
+ $toolbar->publish('plugins.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
+ $toolbar->unpublish('plugins.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
+ $toolbar->checkin('plugins.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin')) {
+ $toolbar->preferences('com_plugins');
+ }
+
+ $toolbar->help('Plugins');
+ }
}
diff --git a/administrator/components/com_plugins/tmpl/plugin/edit.php b/administrator/components/com_plugins/tmpl/plugin/edit.php
index 06dae096c9dd4..c97f8d19a7afd 100644
--- a/administrator/components/com_plugins/tmpl/plugin/edit.php
+++ b/administrator/components/com_plugins/tmpl/plugin/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$this->fieldsets = $this->form->getFieldsets('params');
$this->useCoreUI = true;
@@ -32,111 +33,105 @@
?>
diff --git a/administrator/components/com_plugins/tmpl/plugin/modal.php b/administrator/components/com_plugins/tmpl/plugin/modal.php
index 4c6534c5c401d..aeff088dc4fb9 100644
--- a/administrator/components/com_plugins/tmpl/plugin/modal.php
+++ b/administrator/components/com_plugins/tmpl/plugin/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_plugins/tmpl/plugins/default.php b/administrator/components/com_plugins/tmpl/plugins/default.php
index 541fd8e0f351f..296766df2ccb3 100644
--- a/administrator/components/com_plugins/tmpl/plugins/default.php
+++ b/administrator/components/com_plugins/tmpl/plugins/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = $listOrder == 'ordering';
-if ($saveOrder)
-{
- $saveOrderingUrl = 'index.php?option=com_plugins&task=plugins.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder) {
+ $saveOrderingUrl = 'index.php?option=com_plugins&task=plugins.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_postinstall/services/provider.php b/administrator/components/com_postinstall/services/provider.php
index eeb3f3a013f91..d0995305ee02e 100644
--- a/administrator/components/com_postinstall/services/provider.php
+++ b/administrator/components/com_postinstall/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Postinstall'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Postinstall'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Postinstall'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Postinstall'));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_postinstall/src/Controller/DisplayController.php b/administrator/components/com_postinstall/src/Controller/DisplayController.php
index e9a6be0e6d11e..c3ab27fd1b675 100644
--- a/administrator/components/com_postinstall/src/Controller/DisplayController.php
+++ b/administrator/components/com_postinstall/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
app->getIdentity()->authorise('core.manage', 'com_postinstall'))
- {
- throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
- }
-
- $model = $this->getModel('Messages');
-
- echo new JsonResponse($model->getItemsCount());
- }
+ /**
+ * @var string The default view.
+ * @since 1.6
+ */
+ protected $default_view = 'messages';
+
+ /**
+ * Provide the data for a badge in a menu item via JSON
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function getMenuBadgeData()
+ {
+ if (!$this->app->getIdentity()->authorise('core.manage', 'com_postinstall')) {
+ throw new \Exception(Text::_('JGLOBAL_AUTH_ACCESS_DENIED'));
+ }
+
+ $model = $this->getModel('Messages');
+
+ echo new JsonResponse($model->getItemsCount());
+ }
}
diff --git a/administrator/components/com_postinstall/src/Controller/MessageController.php b/administrator/components/com_postinstall/src/Controller/MessageController.php
index 1342187f6713c..d837fec65cce2 100644
--- a/administrator/components/com_postinstall/src/Controller/MessageController.php
+++ b/administrator/components/com_postinstall/src/Controller/MessageController.php
@@ -1,4 +1,5 @@
checkToken('get');
-
- /** @var MessagesModel $model */
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
- $eid = $this->input->getInt('eid');
-
- if (empty($eid))
- {
- $eid = $model->getJoomlaFilesExtensionId();
- }
-
- $model->resetMessages($eid);
-
- $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
- }
-
- /**
- * Unpublishes post-installation message of the specified extension.
- *
- * @return void
- *
- * @since 3.2
- */
- public function unpublish()
- {
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
-
- $id = $this->input->get('id');
-
- $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
-
- if (empty($eid))
- {
- $eid = $model->getJoomlaFilesExtensionId();
- }
-
- $model->setState('published', 0);
- $model->unpublishMessage($id);
-
- $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
- }
-
- /**
- * Re-Publishes an archived post-installation message of the specified extension.
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function republish()
- {
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
-
- $id = $this->input->get('id');
-
- $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
-
- if (empty($eid))
- {
- $eid = $model->getJoomlaFilesExtensionId();
- }
-
- $model->setState('published', 1);
- $model->republishMessage($id);
-
- $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
- }
-
- /**
- * Archives a published post-installation message of the specified extension.
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function archive()
- {
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
-
- $id = $this->input->get('id');
-
- $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
-
- if (empty($eid))
- {
- $eid = $model->getJoomlaFilesExtensionId();
- }
-
- $model->setState('published', 2);
- $model->archiveMessage($id);
-
- $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
- }
-
- /**
- * Executes the action associated with an item.
- *
- * @return void
- *
- * @since 3.2
- */
- public function action()
- {
- $this->checkToken('get');
-
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
-
- $id = $this->input->get('id');
-
- $item = $model->getItem($id);
-
- switch ($item->type)
- {
- case 'link':
- $this->setRedirect($item->action);
-
- return;
-
- case 'action':
- $helper = new PostinstallHelper;
- $file = $helper->parsePath($item->action_file);
-
- if (File::exists($file))
- {
- require_once $file;
-
- call_user_func($item->action);
- }
- break;
- }
-
- $this->setRedirect('index.php?option=com_postinstall');
- }
-
- /**
- * Hides all post-installation messages of the specified extension.
- *
- * @return void
- *
- * @since 3.8.7
- */
- public function hideAll()
- {
- $this->checkToken();
-
- /** @var MessagesModel $model */
- $model = $this->getModel('Messages', '', ['ignore_request' => true]);
- $eid = $this->input->getInt('eid');
-
- if (empty($eid))
- {
- $eid = $model->getJoomlaFilesExtensionId();
- }
-
- $model->hideMessages($eid);
- $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
- }
+ /**
+ * Resets all post-installation messages of the specified extension.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function reset()
+ {
+ $this->checkToken('get');
+
+ /** @var MessagesModel $model */
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+ $eid = $this->input->getInt('eid');
+
+ if (empty($eid)) {
+ $eid = $model->getJoomlaFilesExtensionId();
+ }
+
+ $model->resetMessages($eid);
+
+ $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
+ }
+
+ /**
+ * Unpublishes post-installation message of the specified extension.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function unpublish()
+ {
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+
+ $id = $this->input->get('id');
+
+ $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
+
+ if (empty($eid)) {
+ $eid = $model->getJoomlaFilesExtensionId();
+ }
+
+ $model->setState('published', 0);
+ $model->unpublishMessage($id);
+
+ $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
+ }
+
+ /**
+ * Re-Publishes an archived post-installation message of the specified extension.
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function republish()
+ {
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+
+ $id = $this->input->get('id');
+
+ $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
+
+ if (empty($eid)) {
+ $eid = $model->getJoomlaFilesExtensionId();
+ }
+
+ $model->setState('published', 1);
+ $model->republishMessage($id);
+
+ $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
+ }
+
+ /**
+ * Archives a published post-installation message of the specified extension.
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function archive()
+ {
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+
+ $id = $this->input->get('id');
+
+ $eid = (int) $model->getState('eid', $model->getJoomlaFilesExtensionId());
+
+ if (empty($eid)) {
+ $eid = $model->getJoomlaFilesExtensionId();
+ }
+
+ $model->setState('published', 2);
+ $model->archiveMessage($id);
+
+ $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
+ }
+
+ /**
+ * Executes the action associated with an item.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function action()
+ {
+ $this->checkToken('get');
+
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+
+ $id = $this->input->get('id');
+
+ $item = $model->getItem($id);
+
+ switch ($item->type) {
+ case 'link':
+ $this->setRedirect($item->action);
+
+ return;
+
+ case 'action':
+ $helper = new PostinstallHelper();
+ $file = $helper->parsePath($item->action_file);
+
+ if (File::exists($file)) {
+ require_once $file;
+
+ call_user_func($item->action);
+ }
+ break;
+ }
+
+ $this->setRedirect('index.php?option=com_postinstall');
+ }
+
+ /**
+ * Hides all post-installation messages of the specified extension.
+ *
+ * @return void
+ *
+ * @since 3.8.7
+ */
+ public function hideAll()
+ {
+ $this->checkToken();
+
+ /** @var MessagesModel $model */
+ $model = $this->getModel('Messages', '', ['ignore_request' => true]);
+ $eid = $this->input->getInt('eid');
+
+ if (empty($eid)) {
+ $eid = $model->getJoomlaFilesExtensionId();
+ }
+
+ $model->hideMessages($eid);
+ $this->setRedirect('index.php?option=com_postinstall&eid=' . $eid);
+ }
}
diff --git a/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php b/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php
index 8b10b66df68be..2ee2362abfece 100644
--- a/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php
+++ b/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php
@@ -1,4 +1,5 @@
input->getInt('eid');
-
- if ($eid)
- {
- $this->setState('eid', $eid);
- }
- }
-
- /**
- * Gets an item with the given id from the database
- *
- * @param integer $id The item id
- *
- * @return Object
- *
- * @since 3.2
- */
- public function getItem($id)
- {
- $db = $this->getDatabase();
- $id = (int) $id;
-
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('postinstall_message_id'),
- $db->quoteName('extension_id'),
- $db->quoteName('title_key'),
- $db->quoteName('description_key'),
- $db->quoteName('action_key'),
- $db->quoteName('language_extension'),
- $db->quoteName('language_client_id'),
- $db->quoteName('type'),
- $db->quoteName('action_file'),
- $db->quoteName('action'),
- $db->quoteName('condition_file'),
- $db->quoteName('condition_method'),
- $db->quoteName('version_introduced'),
- $db->quoteName('enabled'),
- ]
- )
- ->from($db->quoteName('#__postinstall_messages'))
- ->where($db->quoteName('postinstall_message_id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $result = $db->loadObject();
-
- return $result;
- }
-
- /**
- * Unpublishes specified post-install message
- *
- * @param integer $id The message id
- *
- * @return void
- */
- public function unpublishMessage($id)
- {
- $db = $this->getDatabase();
- $id = (int) $id;
-
- $query = $db->getQuery(true);
- $query
- ->update($db->quoteName('#__postinstall_messages'))
- ->set($db->quoteName('enabled') . ' = 0')
- ->where($db->quoteName('postinstall_message_id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- Factory::getCache()->clean('com_postinstall');
- }
-
- /**
- * Archives specified post-install message
- *
- * @param integer $id The message id
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function archiveMessage($id)
- {
- $db = $this->getDatabase();
- $id = (int) $id;
-
- $query = $db->getQuery(true);
- $query
- ->update($db->quoteName('#__postinstall_messages'))
- ->set($db->quoteName('enabled') . ' = 2')
- ->where($db->quoteName('postinstall_message_id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- Factory::getCache()->clean('com_postinstall');
- }
-
- /**
- * Republishes specified post-install message
- *
- * @param integer $id The message id
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function republishMessage($id)
- {
- $db = $this->getDatabase();
- $id = (int) $id;
-
- $query = $db->getQuery(true);
- $query
- ->update($db->quoteName('#__postinstall_messages'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('postinstall_message_id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- Factory::getCache()->clean('com_postinstall');
- }
-
- /**
- * Returns a list of messages from the #__postinstall_messages table
- *
- * @return array
- *
- * @since 3.2
- */
- public function getItems()
- {
- // Add a forced extension filtering to the list
- $eid = (int) $this->getState('eid', $this->getJoomlaFilesExtensionId());
-
- // Build a cache ID for the resulting data object
- $cacheId = 'postinstall_messages.' . $eid;
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('postinstall_message_id'),
- $db->quoteName('extension_id'),
- $db->quoteName('title_key'),
- $db->quoteName('description_key'),
- $db->quoteName('action_key'),
- $db->quoteName('language_extension'),
- $db->quoteName('language_client_id'),
- $db->quoteName('type'),
- $db->quoteName('action_file'),
- $db->quoteName('action'),
- $db->quoteName('condition_file'),
- $db->quoteName('condition_method'),
- $db->quoteName('version_introduced'),
- $db->quoteName('enabled'),
- ]
- )
- ->from($db->quoteName('#__postinstall_messages'));
- $query->where($db->quoteName('extension_id') . ' = :eid')
- ->bind(':eid', $eid, ParameterType::INTEGER);
-
- // Force filter only enabled messages
- $query->whereIn($db->quoteName('enabled'), [1, 2]);
- $db->setQuery($query);
-
- try
- {
- /** @var CallbackController $cache */
- $cache = $this->getCacheControllerFactory()->createCacheController('callback', ['defaultgroup' => 'com_postinstall']);
-
- $result = $cache->get(array($db, 'loadObjectList'), array(), md5($cacheId), false);
- }
- catch (\RuntimeException $e)
- {
- $app = Factory::getApplication();
- $app->getLogger()->warning(
- Text::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()),
- array('category' => 'jerror')
- );
-
- return array();
- }
-
- $this->onProcessList($result);
-
- return $result;
- }
-
- /**
- * Returns a count of all enabled messages from the #__postinstall_messages table
- *
- * @return integer
- *
- * @since 4.0.0
- */
- public function getItemsCount()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select(
- [
- $db->quoteName('language_extension'),
- $db->quoteName('language_client_id'),
- $db->quoteName('condition_file'),
- $db->quoteName('condition_method'),
- ]
- )
- ->from($db->quoteName('#__postinstall_messages'));
-
- // Force filter only enabled messages
- $query->where($db->quoteName('enabled') . ' = 1');
- $db->setQuery($query);
-
- try
- {
- /** @var CallbackController $cache */
- $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
- ->createCacheController('callback', ['defaultgroup' => 'com_postinstall']);
-
- // Get the resulting data object for cache ID 'all.1' from com_postinstall group.
- $result = $cache->get(array($db, 'loadObjectList'), array(), md5('all.1'), false);
- }
- catch (\RuntimeException $e)
- {
- $app = Factory::getApplication();
- $app->getLogger()->warning(
- Text::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()),
- array('category' => 'jerror')
- );
-
- return 0;
- }
-
- $this->onProcessList($result);
-
- return \count($result);
- }
-
- /**
- * Returns the name of an extension, as registered in the #__extensions table
- *
- * @param integer $eid The extension ID
- *
- * @return string The extension name
- *
- * @since 3.2
- */
- public function getExtensionName($eid)
- {
- // Load the extension's information from the database
- $db = $this->getDatabase();
- $eid = (int) $eid;
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('name'),
- $db->quoteName('element'),
- $db->quoteName('client_id'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('extension_id') . ' = :eid')
- ->bind(':eid', $eid, ParameterType::INTEGER)
- ->setLimit(1);
-
- $db->setQuery($query);
-
- $extension = $db->loadObject();
-
- if (!is_object($extension))
- {
- return '';
- }
-
- // Load language files
- $basePath = JPATH_ADMINISTRATOR;
-
- if ($extension->client_id == 0)
- {
- $basePath = JPATH_SITE;
- }
-
- $lang = Factory::getApplication()->getLanguage();
- $lang->load($extension->element, $basePath);
-
- // Return the localised name
- return Text::_(strtoupper($extension->name));
- }
-
- /**
- * Resets all messages for an extension
- *
- * @param integer $eid The extension ID whose messages we'll reset
- *
- * @return mixed False if we fail, a db cursor otherwise
- *
- * @since 3.2
- */
- public function resetMessages($eid)
- {
- $db = $this->getDatabase();
- $eid = (int) $eid;
-
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__postinstall_messages'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('extension_id') . ' = :eid')
- ->bind(':eid', $eid, ParameterType::INTEGER);
- $db->setQuery($query);
-
- $result = $db->execute();
- Factory::getCache()->clean('com_postinstall');
-
- return $result;
- }
-
- /**
- * Hides all messages for an extension
- *
- * @param integer $eid The extension ID whose messages we'll hide
- *
- * @return mixed False if we fail, a db cursor otherwise
- *
- * @since 3.8.7
- */
- public function hideMessages($eid)
- {
- $db = $this->getDatabase();
- $eid = (int) $eid;
-
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__postinstall_messages'))
- ->set($db->quoteName('enabled') . ' = 0')
- ->where($db->quoteName('extension_id') . ' = :eid')
- ->bind(':eid', $eid, ParameterType::INTEGER);
- $db->setQuery($query);
-
- $result = $db->execute();
- Factory::getCache()->clean('com_postinstall');
-
- return $result;
- }
-
- /**
- * List post-processing. This is used to run the programmatic display
- * conditions against each list item and decide if we have to show it or
- * not.
- *
- * Do note that this a core method of the RAD Layer which operates directly
- * on the list it's being fed. A little touch of modern magic.
- *
- * @param array &$resultArray A list of items to process
- *
- * @return void
- *
- * @since 3.2
- */
- protected function onProcessList(&$resultArray)
- {
- $unset_keys = array();
- $language_extensions = array();
-
- // Order the results DESC so the newest is on the top.
- $resultArray = array_reverse($resultArray);
-
- foreach ($resultArray as $key => $item)
- {
- // Filter out messages based on dynamically loaded programmatic conditions.
- if (!empty($item->condition_file) && !empty($item->condition_method))
- {
- $helper = new PostinstallHelper;
- $file = $helper->parsePath($item->condition_file);
-
- if (File::exists($file))
- {
- require_once $file;
-
- $result = call_user_func($item->condition_method);
-
- if ($result === false)
- {
- $unset_keys[] = $key;
- }
- }
- }
-
- // Load the necessary language files.
- if (!empty($item->language_extension))
- {
- $hash = $item->language_client_id . '-' . $item->language_extension;
-
- if (!in_array($hash, $language_extensions))
- {
- $language_extensions[] = $hash;
- Factory::getApplication()->getLanguage()->load($item->language_extension, $item->language_client_id == 0 ? JPATH_SITE : JPATH_ADMINISTRATOR);
- }
- }
- }
-
- if (!empty($unset_keys))
- {
- foreach ($unset_keys as $key)
- {
- unset($resultArray[$key]);
- }
- }
- }
-
- /**
- * Get the dropdown options for the list of component with post-installation messages
- *
- * @since 3.4
- *
- * @return array Compatible with JHtmlSelect::genericList
- */
- public function getComponentOptions()
- {
- $db = $this->getDatabase();
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__postinstall_messages'))
- ->group($db->quoteName('extension_id'));
- $db->setQuery($query);
- $extension_ids = $db->loadColumn();
-
- $options = array();
-
- Factory::getApplication()->getLanguage()->load('files_joomla.sys', JPATH_SITE, null, false, false);
-
- foreach ($extension_ids as $eid)
- {
- $options[] = HTMLHelper::_('select.option', $eid, $this->getExtensionName($eid));
- }
-
- return $options;
- }
-
- /**
- * Adds or updates a post-installation message (PIM) definition. You can use this in your post-installation script using this code:
- *
- * require_once JPATH_LIBRARIES . '/fof/include.php';
- * FOFModel::getTmpInstance('Messages', 'PostinstallModel')->addPostInstallationMessage($options);
- *
- * The $options array contains the following mandatory keys:
- *
- * extension_id The numeric ID of the extension this message is for (see the #__extensions table)
- *
- * type One of message, link or action. Their meaning is:
- * message Informative message. The user can dismiss it.
- * link The action button links to a URL. The URL is defined in the action parameter.
- * action A PHP action takes place when the action button is clicked. You need to specify the action_file
- * (RAD path to the PHP file) and action (PHP function name) keys. See below for more information.
- *
- * title_key The Text language key for the title of this PIM.
- * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_TITLE
- *
- * description_key The Text language key for the main body (description) of this PIM
- * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_DESCRIPTION
- *
- * action_key The Text language key for the action button. Ignored and not required when type=message
- * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_ACTION
- *
- * language_extension The extension name which holds the language keys used above.
- * For example, com_foobar, mod_something, plg_system_whatever, tpl_mytemplate
- *
- * language_client_id Should we load the frontend (0) or backend (1) language keys?
- *
- * version_introduced Which was the version of your extension where this message appeared for the first time?
- * Example: 3.2.1
- *
- * enabled Must be 1 for this message to be enabled. If you omit it, it defaults to 1.
- *
- * condition_file The RAD path to a PHP file containing a PHP function which determines whether this message should be shown to
- * the user. @see FOFTemplateUtils::parsePath() for RAD path format. Joomla! will include this file before calling
- * the condition_method.
- * Example: admin://components/com_foobar/helpers/postinstall.php
- *
- * condition_method The name of a PHP function which will be used to determine whether to show this message to the user. This must be
- * a simple PHP user function (not a class method, static method etc) which returns true to show the message and false
- * to hide it. This function is defined in the condition_file.
- * Example: com_foobar_postinstall_messageone_condition
- *
- * When type=message no additional keys are required.
- *
- * When type=link the following additional keys are required:
- *
- * action The URL which will open when the user clicks on the PIM's action button
- * Example: index.php?option=com_foobar&view=tools&task=installSampleData
- *
- * When type=action the following additional keys are required:
- *
- * action_file The RAD path to a PHP file containing a PHP function which performs the action of this PIM. @see FOFTemplateUtils::parsePath()
- * for RAD path format. Joomla! will include this file before calling the function defined in the action key below.
- * Example: admin://components/com_foobar/helpers/postinstall.php
- *
- * action The name of a PHP function which will be used to run the action of this PIM. This must be a simple PHP user function
- * (not a class method, static method etc) which returns no result.
- * Example: com_foobar_postinstall_messageone_action
- *
- * @param array $options See description
- *
- * @return $this
- *
- * @throws \Exception
- */
- public function addPostInstallationMessage(array $options)
- {
- // Make sure there are options set
- if (!is_array($options))
- {
- throw new \Exception('Post-installation message definitions must be of type array', 500);
- }
-
- // Initialise array keys
- $defaultOptions = array(
- 'extension_id' => '',
- 'type' => '',
- 'title_key' => '',
- 'description_key' => '',
- 'action_key' => '',
- 'language_extension' => '',
- 'language_client_id' => '',
- 'action_file' => '',
- 'action' => '',
- 'condition_file' => '',
- 'condition_method' => '',
- 'version_introduced' => '',
- 'enabled' => '1',
- );
-
- $options = array_merge($defaultOptions, $options);
-
- // Array normalisation. Removes array keys not belonging to a definition.
- $defaultKeys = array_keys($defaultOptions);
- $allKeys = array_keys($options);
- $extraKeys = array_diff($allKeys, $defaultKeys);
-
- if (!empty($extraKeys))
- {
- foreach ($extraKeys as $key)
- {
- unset($options[$key]);
- }
- }
-
- // Normalisation of integer values
- $options['extension_id'] = (int) $options['extension_id'];
- $options['language_client_id'] = (int) $options['language_client_id'];
- $options['enabled'] = (int) $options['enabled'];
-
- // Normalisation of 0/1 values
- foreach (array('language_client_id', 'enabled') as $key)
- {
- $options[$key] = $options[$key] ? 1 : 0;
- }
-
- // Make sure there's an extension_id
- if (!(int) $options['extension_id'])
- {
- throw new \Exception('Post-installation message definitions need an extension_id', 500);
- }
-
- // Make sure there's a valid type
- if (!in_array($options['type'], array('message', 'link', 'action')))
- {
- throw new \Exception('Post-installation message definitions need to declare a type of message, link or action', 500);
- }
-
- // Make sure there's a title key
- if (empty($options['title_key']))
- {
- throw new \Exception('Post-installation message definitions need a title key', 500);
- }
-
- // Make sure there's a description key
- if (empty($options['description_key']))
- {
- throw new \Exception('Post-installation message definitions need a description key', 500);
- }
-
- // If the type is anything other than message you need an action key
- if (($options['type'] != 'message') && empty($options['action_key']))
- {
- throw new \Exception('Post-installation message definitions need an action key when they are of type "' . $options['type'] . '"', 500);
- }
-
- // You must specify the language extension
- if (empty($options['language_extension']))
- {
- throw new \Exception('Post-installation message definitions need to specify which extension contains their language keys', 500);
- }
-
- // The action file and method are only required for the "action" type
- if ($options['type'] == 'action')
- {
- if (empty($options['action_file']))
- {
- throw new \Exception('Post-installation message definitions need an action file when they are of type "action"', 500);
- }
-
- $helper = new PostinstallHelper;
- $file_path = $helper->parsePath($options['action_file']);
-
- if (!@is_file($file_path))
- {
- throw new \Exception('The action file ' . $options['action_file'] . ' of your post-installation message definition does not exist', 500);
- }
-
- if (empty($options['action']))
- {
- throw new \Exception('Post-installation message definitions need an action (function name) when they are of type "action"', 500);
- }
- }
-
- if ($options['type'] == 'link')
- {
- if (empty($options['link']))
- {
- throw new \Exception('Post-installation message definitions need an action (URL) when they are of type "link"', 500);
- }
- }
-
- // The condition file and method are only required when the type is not "message"
- if ($options['type'] != 'message')
- {
- if (empty($options['condition_file']))
- {
- throw new \Exception('Post-installation message definitions need a condition file when they are of type "' . $options['type'] . '"', 500);
- }
-
- $helper = new PostinstallHelper;
- $file_path = $helper->parsePath($options['condition_file']);
-
- if (!@is_file($file_path))
- {
- throw new \Exception('The condition file ' . $options['condition_file'] . ' of your post-installation message definition does not exist', 500);
- }
-
- if (empty($options['condition_method']))
- {
- throw new \Exception(
- 'Post-installation message definitions need a condition method (function name) when they are of type "'
- . $options['type'] . '"',
- 500
- );
- }
- }
-
- // Check if the definition exists
- $table = $this->getTable();
- $tableName = $table->getTableName();
- $extensionId = (int) $options['extension_id'];
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName($tableName))
- ->where(
- [
- $db->quoteName('extension_id') . ' = :extensionId',
- $db->quoteName('type') . ' = :type',
- $db->quoteName('title_key') . ' = :titleKey',
- ]
- )
- ->bind(':extensionId', $extensionId, ParameterType::INTEGER)
- ->bind(':type', $options['type'])
- ->bind(':titleKey', $options['title_key']);
-
- $existingRow = $db->setQuery($query)->loadAssoc();
-
- // Is the existing definition the same as the one we're trying to save?
- if (!empty($existingRow))
- {
- $same = true;
-
- foreach ($options as $k => $v)
- {
- if ($existingRow[$k] != $v)
- {
- $same = false;
- break;
- }
- }
-
- // Trying to add the same row as the existing one; quit
- if ($same)
- {
- return $this;
- }
-
- // Otherwise it's not the same row. Remove the old row before insert a new one.
- $query = $db->getQuery(true)
- ->delete($db->quoteName($tableName))
- ->where(
- [
- $db->quoteName('extension_id') . ' = :extensionId',
- $db->quoteName('type') . ' = :type',
- $db->quoteName('title_key') . ' = :titleKey',
- ]
- )
- ->bind(':extensionId', $extensionId, ParameterType::INTEGER)
- ->bind(':type', $options['type'])
- ->bind(':titleKey', $options['title_key']);
-
- $db->setQuery($query)->execute();
- }
-
- // Insert the new row
- $options = (object) $options;
- $db->insertObject($tableName, $options);
- Factory::getCache()->clean('com_postinstall');
-
- return $this;
- }
-
- /**
- * Returns the library extension ID.
- *
- * @return integer
- *
- * @since 4.0.0
- */
- public function getJoomlaFilesExtensionId()
- {
- return ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
- }
+ /**
+ * Method to auto-populate the state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the
+ * configuration flag to ignore the request is set.
+ *
+ * @return void
+ *
+ * @note Calling getState in this method will result in recursion.
+ * @since 4.0.0
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $eid = (int) Factory::getApplication()->input->getInt('eid');
+
+ if ($eid) {
+ $this->setState('eid', $eid);
+ }
+ }
+
+ /**
+ * Gets an item with the given id from the database
+ *
+ * @param integer $id The item id
+ *
+ * @return Object
+ *
+ * @since 3.2
+ */
+ public function getItem($id)
+ {
+ $db = $this->getDatabase();
+ $id = (int) $id;
+
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('postinstall_message_id'),
+ $db->quoteName('extension_id'),
+ $db->quoteName('title_key'),
+ $db->quoteName('description_key'),
+ $db->quoteName('action_key'),
+ $db->quoteName('language_extension'),
+ $db->quoteName('language_client_id'),
+ $db->quoteName('type'),
+ $db->quoteName('action_file'),
+ $db->quoteName('action'),
+ $db->quoteName('condition_file'),
+ $db->quoteName('condition_method'),
+ $db->quoteName('version_introduced'),
+ $db->quoteName('enabled'),
+ ]
+ )
+ ->from($db->quoteName('#__postinstall_messages'))
+ ->where($db->quoteName('postinstall_message_id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $result = $db->loadObject();
+
+ return $result;
+ }
+
+ /**
+ * Unpublishes specified post-install message
+ *
+ * @param integer $id The message id
+ *
+ * @return void
+ */
+ public function unpublishMessage($id)
+ {
+ $db = $this->getDatabase();
+ $id = (int) $id;
+
+ $query = $db->getQuery(true);
+ $query
+ ->update($db->quoteName('#__postinstall_messages'))
+ ->set($db->quoteName('enabled') . ' = 0')
+ ->where($db->quoteName('postinstall_message_id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ Factory::getCache()->clean('com_postinstall');
+ }
+
+ /**
+ * Archives specified post-install message
+ *
+ * @param integer $id The message id
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function archiveMessage($id)
+ {
+ $db = $this->getDatabase();
+ $id = (int) $id;
+
+ $query = $db->getQuery(true);
+ $query
+ ->update($db->quoteName('#__postinstall_messages'))
+ ->set($db->quoteName('enabled') . ' = 2')
+ ->where($db->quoteName('postinstall_message_id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ Factory::getCache()->clean('com_postinstall');
+ }
+
+ /**
+ * Republishes specified post-install message
+ *
+ * @param integer $id The message id
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function republishMessage($id)
+ {
+ $db = $this->getDatabase();
+ $id = (int) $id;
+
+ $query = $db->getQuery(true);
+ $query
+ ->update($db->quoteName('#__postinstall_messages'))
+ ->set($db->quoteName('enabled') . ' = 1')
+ ->where($db->quoteName('postinstall_message_id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ Factory::getCache()->clean('com_postinstall');
+ }
+
+ /**
+ * Returns a list of messages from the #__postinstall_messages table
+ *
+ * @return array
+ *
+ * @since 3.2
+ */
+ public function getItems()
+ {
+ // Add a forced extension filtering to the list
+ $eid = (int) $this->getState('eid', $this->getJoomlaFilesExtensionId());
+
+ // Build a cache ID for the resulting data object
+ $cacheId = 'postinstall_messages.' . $eid;
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('postinstall_message_id'),
+ $db->quoteName('extension_id'),
+ $db->quoteName('title_key'),
+ $db->quoteName('description_key'),
+ $db->quoteName('action_key'),
+ $db->quoteName('language_extension'),
+ $db->quoteName('language_client_id'),
+ $db->quoteName('type'),
+ $db->quoteName('action_file'),
+ $db->quoteName('action'),
+ $db->quoteName('condition_file'),
+ $db->quoteName('condition_method'),
+ $db->quoteName('version_introduced'),
+ $db->quoteName('enabled'),
+ ]
+ )
+ ->from($db->quoteName('#__postinstall_messages'));
+ $query->where($db->quoteName('extension_id') . ' = :eid')
+ ->bind(':eid', $eid, ParameterType::INTEGER);
+
+ // Force filter only enabled messages
+ $query->whereIn($db->quoteName('enabled'), [1, 2]);
+ $db->setQuery($query);
+
+ try {
+ /** @var CallbackController $cache */
+ $cache = $this->getCacheControllerFactory()->createCacheController('callback', ['defaultgroup' => 'com_postinstall']);
+
+ $result = $cache->get(array($db, 'loadObjectList'), array(), md5($cacheId), false);
+ } catch (\RuntimeException $e) {
+ $app = Factory::getApplication();
+ $app->getLogger()->warning(
+ Text::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()),
+ array('category' => 'jerror')
+ );
+
+ return array();
+ }
+
+ $this->onProcessList($result);
+
+ return $result;
+ }
+
+ /**
+ * Returns a count of all enabled messages from the #__postinstall_messages table
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ public function getItemsCount()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select(
+ [
+ $db->quoteName('language_extension'),
+ $db->quoteName('language_client_id'),
+ $db->quoteName('condition_file'),
+ $db->quoteName('condition_method'),
+ ]
+ )
+ ->from($db->quoteName('#__postinstall_messages'));
+
+ // Force filter only enabled messages
+ $query->where($db->quoteName('enabled') . ' = 1');
+ $db->setQuery($query);
+
+ try {
+ /** @var CallbackController $cache */
+ $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
+ ->createCacheController('callback', ['defaultgroup' => 'com_postinstall']);
+
+ // Get the resulting data object for cache ID 'all.1' from com_postinstall group.
+ $result = $cache->get(array($db, 'loadObjectList'), array(), md5('all.1'), false);
+ } catch (\RuntimeException $e) {
+ $app = Factory::getApplication();
+ $app->getLogger()->warning(
+ Text::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()),
+ array('category' => 'jerror')
+ );
+
+ return 0;
+ }
+
+ $this->onProcessList($result);
+
+ return \count($result);
+ }
+
+ /**
+ * Returns the name of an extension, as registered in the #__extensions table
+ *
+ * @param integer $eid The extension ID
+ *
+ * @return string The extension name
+ *
+ * @since 3.2
+ */
+ public function getExtensionName($eid)
+ {
+ // Load the extension's information from the database
+ $db = $this->getDatabase();
+ $eid = (int) $eid;
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('name'),
+ $db->quoteName('element'),
+ $db->quoteName('client_id'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('extension_id') . ' = :eid')
+ ->bind(':eid', $eid, ParameterType::INTEGER)
+ ->setLimit(1);
+
+ $db->setQuery($query);
+
+ $extension = $db->loadObject();
+
+ if (!is_object($extension)) {
+ return '';
+ }
+
+ // Load language files
+ $basePath = JPATH_ADMINISTRATOR;
+
+ if ($extension->client_id == 0) {
+ $basePath = JPATH_SITE;
+ }
+
+ $lang = Factory::getApplication()->getLanguage();
+ $lang->load($extension->element, $basePath);
+
+ // Return the localised name
+ return Text::_(strtoupper($extension->name));
+ }
+
+ /**
+ * Resets all messages for an extension
+ *
+ * @param integer $eid The extension ID whose messages we'll reset
+ *
+ * @return mixed False if we fail, a db cursor otherwise
+ *
+ * @since 3.2
+ */
+ public function resetMessages($eid)
+ {
+ $db = $this->getDatabase();
+ $eid = (int) $eid;
+
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__postinstall_messages'))
+ ->set($db->quoteName('enabled') . ' = 1')
+ ->where($db->quoteName('extension_id') . ' = :eid')
+ ->bind(':eid', $eid, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ $result = $db->execute();
+ Factory::getCache()->clean('com_postinstall');
+
+ return $result;
+ }
+
+ /**
+ * Hides all messages for an extension
+ *
+ * @param integer $eid The extension ID whose messages we'll hide
+ *
+ * @return mixed False if we fail, a db cursor otherwise
+ *
+ * @since 3.8.7
+ */
+ public function hideMessages($eid)
+ {
+ $db = $this->getDatabase();
+ $eid = (int) $eid;
+
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__postinstall_messages'))
+ ->set($db->quoteName('enabled') . ' = 0')
+ ->where($db->quoteName('extension_id') . ' = :eid')
+ ->bind(':eid', $eid, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ $result = $db->execute();
+ Factory::getCache()->clean('com_postinstall');
+
+ return $result;
+ }
+
+ /**
+ * List post-processing. This is used to run the programmatic display
+ * conditions against each list item and decide if we have to show it or
+ * not.
+ *
+ * Do note that this a core method of the RAD Layer which operates directly
+ * on the list it's being fed. A little touch of modern magic.
+ *
+ * @param array &$resultArray A list of items to process
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function onProcessList(&$resultArray)
+ {
+ $unset_keys = array();
+ $language_extensions = array();
+
+ // Order the results DESC so the newest is on the top.
+ $resultArray = array_reverse($resultArray);
+
+ foreach ($resultArray as $key => $item) {
+ // Filter out messages based on dynamically loaded programmatic conditions.
+ if (!empty($item->condition_file) && !empty($item->condition_method)) {
+ $helper = new PostinstallHelper();
+ $file = $helper->parsePath($item->condition_file);
+
+ if (File::exists($file)) {
+ require_once $file;
+
+ $result = call_user_func($item->condition_method);
+
+ if ($result === false) {
+ $unset_keys[] = $key;
+ }
+ }
+ }
+
+ // Load the necessary language files.
+ if (!empty($item->language_extension)) {
+ $hash = $item->language_client_id . '-' . $item->language_extension;
+
+ if (!in_array($hash, $language_extensions)) {
+ $language_extensions[] = $hash;
+ Factory::getApplication()->getLanguage()->load($item->language_extension, $item->language_client_id == 0 ? JPATH_SITE : JPATH_ADMINISTRATOR);
+ }
+ }
+ }
+
+ if (!empty($unset_keys)) {
+ foreach ($unset_keys as $key) {
+ unset($resultArray[$key]);
+ }
+ }
+ }
+
+ /**
+ * Get the dropdown options for the list of component with post-installation messages
+ *
+ * @since 3.4
+ *
+ * @return array Compatible with JHtmlSelect::genericList
+ */
+ public function getComponentOptions()
+ {
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__postinstall_messages'))
+ ->group($db->quoteName('extension_id'));
+ $db->setQuery($query);
+ $extension_ids = $db->loadColumn();
+
+ $options = array();
+
+ Factory::getApplication()->getLanguage()->load('files_joomla.sys', JPATH_SITE, null, false, false);
+
+ foreach ($extension_ids as $eid) {
+ $options[] = HTMLHelper::_('select.option', $eid, $this->getExtensionName($eid));
+ }
+
+ return $options;
+ }
+
+ /**
+ * Adds or updates a post-installation message (PIM) definition. You can use this in your post-installation script using this code:
+ *
+ * require_once JPATH_LIBRARIES . '/fof/include.php';
+ * FOFModel::getTmpInstance('Messages', 'PostinstallModel')->addPostInstallationMessage($options);
+ *
+ * The $options array contains the following mandatory keys:
+ *
+ * extension_id The numeric ID of the extension this message is for (see the #__extensions table)
+ *
+ * type One of message, link or action. Their meaning is:
+ * message Informative message. The user can dismiss it.
+ * link The action button links to a URL. The URL is defined in the action parameter.
+ * action A PHP action takes place when the action button is clicked. You need to specify the action_file
+ * (RAD path to the PHP file) and action (PHP function name) keys. See below for more information.
+ *
+ * title_key The Text language key for the title of this PIM.
+ * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_TITLE
+ *
+ * description_key The Text language key for the main body (description) of this PIM
+ * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_DESCRIPTION
+ *
+ * action_key The Text language key for the action button. Ignored and not required when type=message
+ * Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_ACTION
+ *
+ * language_extension The extension name which holds the language keys used above.
+ * For example, com_foobar, mod_something, plg_system_whatever, tpl_mytemplate
+ *
+ * language_client_id Should we load the frontend (0) or backend (1) language keys?
+ *
+ * version_introduced Which was the version of your extension where this message appeared for the first time?
+ * Example: 3.2.1
+ *
+ * enabled Must be 1 for this message to be enabled. If you omit it, it defaults to 1.
+ *
+ * condition_file The RAD path to a PHP file containing a PHP function which determines whether this message should be shown to
+ * the user. @see FOFTemplateUtils::parsePath() for RAD path format. Joomla! will include this file before calling
+ * the condition_method.
+ * Example: admin://components/com_foobar/helpers/postinstall.php
+ *
+ * condition_method The name of a PHP function which will be used to determine whether to show this message to the user. This must be
+ * a simple PHP user function (not a class method, static method etc) which returns true to show the message and false
+ * to hide it. This function is defined in the condition_file.
+ * Example: com_foobar_postinstall_messageone_condition
+ *
+ * When type=message no additional keys are required.
+ *
+ * When type=link the following additional keys are required:
+ *
+ * action The URL which will open when the user clicks on the PIM's action button
+ * Example: index.php?option=com_foobar&view=tools&task=installSampleData
+ *
+ * When type=action the following additional keys are required:
+ *
+ * action_file The RAD path to a PHP file containing a PHP function which performs the action of this PIM. @see FOFTemplateUtils::parsePath()
+ * for RAD path format. Joomla! will include this file before calling the function defined in the action key below.
+ * Example: admin://components/com_foobar/helpers/postinstall.php
+ *
+ * action The name of a PHP function which will be used to run the action of this PIM. This must be a simple PHP user function
+ * (not a class method, static method etc) which returns no result.
+ * Example: com_foobar_postinstall_messageone_action
+ *
+ * @param array $options See description
+ *
+ * @return $this
+ *
+ * @throws \Exception
+ */
+ public function addPostInstallationMessage(array $options)
+ {
+ // Make sure there are options set
+ if (!is_array($options)) {
+ throw new \Exception('Post-installation message definitions must be of type array', 500);
+ }
+
+ // Initialise array keys
+ $defaultOptions = array(
+ 'extension_id' => '',
+ 'type' => '',
+ 'title_key' => '',
+ 'description_key' => '',
+ 'action_key' => '',
+ 'language_extension' => '',
+ 'language_client_id' => '',
+ 'action_file' => '',
+ 'action' => '',
+ 'condition_file' => '',
+ 'condition_method' => '',
+ 'version_introduced' => '',
+ 'enabled' => '1',
+ );
+
+ $options = array_merge($defaultOptions, $options);
+
+ // Array normalisation. Removes array keys not belonging to a definition.
+ $defaultKeys = array_keys($defaultOptions);
+ $allKeys = array_keys($options);
+ $extraKeys = array_diff($allKeys, $defaultKeys);
+
+ if (!empty($extraKeys)) {
+ foreach ($extraKeys as $key) {
+ unset($options[$key]);
+ }
+ }
+
+ // Normalisation of integer values
+ $options['extension_id'] = (int) $options['extension_id'];
+ $options['language_client_id'] = (int) $options['language_client_id'];
+ $options['enabled'] = (int) $options['enabled'];
+
+ // Normalisation of 0/1 values
+ foreach (array('language_client_id', 'enabled') as $key) {
+ $options[$key] = $options[$key] ? 1 : 0;
+ }
+
+ // Make sure there's an extension_id
+ if (!(int) $options['extension_id']) {
+ throw new \Exception('Post-installation message definitions need an extension_id', 500);
+ }
+
+ // Make sure there's a valid type
+ if (!in_array($options['type'], array('message', 'link', 'action'))) {
+ throw new \Exception('Post-installation message definitions need to declare a type of message, link or action', 500);
+ }
+
+ // Make sure there's a title key
+ if (empty($options['title_key'])) {
+ throw new \Exception('Post-installation message definitions need a title key', 500);
+ }
+
+ // Make sure there's a description key
+ if (empty($options['description_key'])) {
+ throw new \Exception('Post-installation message definitions need a description key', 500);
+ }
+
+ // If the type is anything other than message you need an action key
+ if (($options['type'] != 'message') && empty($options['action_key'])) {
+ throw new \Exception('Post-installation message definitions need an action key when they are of type "' . $options['type'] . '"', 500);
+ }
+
+ // You must specify the language extension
+ if (empty($options['language_extension'])) {
+ throw new \Exception('Post-installation message definitions need to specify which extension contains their language keys', 500);
+ }
+
+ // The action file and method are only required for the "action" type
+ if ($options['type'] == 'action') {
+ if (empty($options['action_file'])) {
+ throw new \Exception('Post-installation message definitions need an action file when they are of type "action"', 500);
+ }
+
+ $helper = new PostinstallHelper();
+ $file_path = $helper->parsePath($options['action_file']);
+
+ if (!@is_file($file_path)) {
+ throw new \Exception('The action file ' . $options['action_file'] . ' of your post-installation message definition does not exist', 500);
+ }
+
+ if (empty($options['action'])) {
+ throw new \Exception('Post-installation message definitions need an action (function name) when they are of type "action"', 500);
+ }
+ }
+
+ if ($options['type'] == 'link') {
+ if (empty($options['link'])) {
+ throw new \Exception('Post-installation message definitions need an action (URL) when they are of type "link"', 500);
+ }
+ }
+
+ // The condition file and method are only required when the type is not "message"
+ if ($options['type'] != 'message') {
+ if (empty($options['condition_file'])) {
+ throw new \Exception('Post-installation message definitions need a condition file when they are of type "' . $options['type'] . '"', 500);
+ }
+
+ $helper = new PostinstallHelper();
+ $file_path = $helper->parsePath($options['condition_file']);
+
+ if (!@is_file($file_path)) {
+ throw new \Exception('The condition file ' . $options['condition_file'] . ' of your post-installation message definition does not exist', 500);
+ }
+
+ if (empty($options['condition_method'])) {
+ throw new \Exception(
+ 'Post-installation message definitions need a condition method (function name) when they are of type "'
+ . $options['type'] . '"',
+ 500
+ );
+ }
+ }
+
+ // Check if the definition exists
+ $table = $this->getTable();
+ $tableName = $table->getTableName();
+ $extensionId = (int) $options['extension_id'];
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName($tableName))
+ ->where(
+ [
+ $db->quoteName('extension_id') . ' = :extensionId',
+ $db->quoteName('type') . ' = :type',
+ $db->quoteName('title_key') . ' = :titleKey',
+ ]
+ )
+ ->bind(':extensionId', $extensionId, ParameterType::INTEGER)
+ ->bind(':type', $options['type'])
+ ->bind(':titleKey', $options['title_key']);
+
+ $existingRow = $db->setQuery($query)->loadAssoc();
+
+ // Is the existing definition the same as the one we're trying to save?
+ if (!empty($existingRow)) {
+ $same = true;
+
+ foreach ($options as $k => $v) {
+ if ($existingRow[$k] != $v) {
+ $same = false;
+ break;
+ }
+ }
+
+ // Trying to add the same row as the existing one; quit
+ if ($same) {
+ return $this;
+ }
+
+ // Otherwise it's not the same row. Remove the old row before insert a new one.
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName($tableName))
+ ->where(
+ [
+ $db->quoteName('extension_id') . ' = :extensionId',
+ $db->quoteName('type') . ' = :type',
+ $db->quoteName('title_key') . ' = :titleKey',
+ ]
+ )
+ ->bind(':extensionId', $extensionId, ParameterType::INTEGER)
+ ->bind(':type', $options['type'])
+ ->bind(':titleKey', $options['title_key']);
+
+ $db->setQuery($query)->execute();
+ }
+
+ // Insert the new row
+ $options = (object) $options;
+ $db->insertObject($tableName, $options);
+ Factory::getCache()->clean('com_postinstall');
+
+ return $this;
+ }
+
+ /**
+ * Returns the library extension ID.
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ public function getJoomlaFilesExtensionId()
+ {
+ return ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
+ }
}
diff --git a/administrator/components/com_postinstall/src/View/Messages/HtmlView.php b/administrator/components/com_postinstall/src/View/Messages/HtmlView.php
index fb5190ec072b1..2bdca27ff4e59 100644
--- a/administrator/components/com_postinstall/src/View/Messages/HtmlView.php
+++ b/administrator/components/com_postinstall/src/View/Messages/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
+ /**
+ * Executes before rendering the page for the Browse task.
+ *
+ * @param string $tpl Subtemplate to use
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ /** @var MessagesModel $model */
+ $model = $this->getModel();
- $this->items = $model->getItems();
+ $this->items = $model->getItems();
- if (!\count($this->items))
- {
- $this->setLayout('emptystate');
- }
+ if (!\count($this->items)) {
+ $this->setLayout('emptystate');
+ }
- $this->joomlaFilesExtensionId = $model->getJoomlaFilesExtensionId();
- $this->eid = (int) $model->getState('eid', $this->joomlaFilesExtensionId);
+ $this->joomlaFilesExtensionId = $model->getJoomlaFilesExtensionId();
+ $this->eid = (int) $model->getState('eid', $this->joomlaFilesExtensionId);
- if (empty($this->eid))
- {
- $this->eid = $this->joomlaFilesExtensionId;
- }
+ if (empty($this->eid)) {
+ $this->eid = $this->joomlaFilesExtensionId;
+ }
- $this->toolbar();
+ $this->toolbar();
- $this->token = Factory::getSession()->getFormToken();
- $this->extension_options = $model->getComponentOptions();
+ $this->token = Factory::getSession()->getFormToken();
+ $this->extension_options = $model->getComponentOptions();
- ToolbarHelper::title(Text::sprintf('COM_POSTINSTALL_MESSAGES_TITLE', $model->getExtensionName($this->eid)), 'bell');
+ ToolbarHelper::title(Text::sprintf('COM_POSTINSTALL_MESSAGES_TITLE', $model->getExtensionName($this->eid)), 'bell');
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * displays the toolbar
- *
- * @return void
- *
- * @since 3.6
- */
- private function toolbar()
- {
- $toolbar = Toolbar::getInstance('toolbar');
+ /**
+ * displays the toolbar
+ *
+ * @return void
+ *
+ * @since 3.6
+ */
+ private function toolbar()
+ {
+ $toolbar = Toolbar::getInstance('toolbar');
- if (!empty($this->items))
- {
- $toolbar->unpublish('message.hideAll', 'COM_POSTINSTALL_HIDE_ALL_MESSAGES');
- }
+ if (!empty($this->items)) {
+ $toolbar->unpublish('message.hideAll', 'COM_POSTINSTALL_HIDE_ALL_MESSAGES');
+ }
- // Options button.
- if ($this->getCurrentUser()->authorise('core.admin', 'com_postinstall'))
- {
- $toolbar->preferences('com_postinstall');
- $toolbar->help('Post-installation_Messages_for_Joomla_CMS');
- }
- }
+ // Options button.
+ if ($this->getCurrentUser()->authorise('core.admin', 'com_postinstall')) {
+ $toolbar->preferences('com_postinstall');
+ $toolbar->help('Post-installation_Messages_for_Joomla_CMS');
+ }
+ }
}
diff --git a/administrator/components/com_postinstall/tmpl/messages/default.php b/administrator/components/com_postinstall/tmpl/messages/default.php
index cb7fd12316a7c..84cf2892442b2 100644
--- a/administrator/components/com_postinstall/tmpl/messages/default.php
+++ b/administrator/components/com_postinstall/tmpl/messages/default.php
@@ -1,4 +1,5 @@
items as $item) : ?>
- enabled === 1) : ?>
-
-
-
title_key); ?>
-
- version_introduced); ?>
-
-
- description_key); ?>
- type !== 'message') : ?>
-
- action_key); ?>
-
-
- getIdentity()->authorise('core.edit.state', 'com_postinstall')) : ?>
-
-
-
-
-
-
-
-
-
-
- enabled === 2) : ?>
-
-
-
title_key); ?>
-
- getIdentity()->authorise('core.edit.state', 'com_postinstall')) : ?>
-
-
-
-
-
-
-
-
-
-
-
+ enabled === 1) : ?>
+
+
+
title_key); ?>
+
+ version_introduced); ?>
+
+
+ description_key); ?>
+ type !== 'message') : ?>
+
+ action_key); ?>
+
+
+ getIdentity()->authorise('core.edit.state', 'com_postinstall')) : ?>
+
+
+
+
+
+
+
+
+
+
+ enabled === 2) : ?>
+
+
+
title_key); ?>
+
+ getIdentity()->authorise('core.edit.state', 'com_postinstall')) : ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_postinstall/tmpl/messages/emptystate.php b/administrator/components/com_postinstall/tmpl/messages/emptystate.php
index f58a1d4c0ed00..4c2b43aca30f3 100644
--- a/administrator/components/com_postinstall/tmpl/messages/emptystate.php
+++ b/administrator/components/com_postinstall/tmpl/messages/emptystate.php
@@ -1,4 +1,5 @@
'COM_POSTINSTALL',
- 'formURL' => 'index.php?option=com_postinstall',
- 'icon' => 'icon-bell',
- 'createURL' => 'index.php?option=com_postinstall&view=messages&task=message.reset&eid=' . $this->eid . '&' . $this->token . '=1',
+ 'textPrefix' => 'COM_POSTINSTALL',
+ 'formURL' => 'index.php?option=com_postinstall',
+ 'icon' => 'icon-bell',
+ 'createURL' => 'index.php?option=com_postinstall&view=messages&task=message.reset&eid=' . $this->eid . '&' . $this->token . '=1',
];
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_privacy/services/provider.php b/administrator/components/com_privacy/services/provider.php
index fc62cfaf46de2..d4c329ecda62c 100644
--- a/administrator/components/com_privacy/services/provider.php
+++ b/administrator/components/com_privacy/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Privacy'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Privacy'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Privacy'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Privacy'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Privacy'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Privacy'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new PrivacyComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new PrivacyComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_privacy/src/Controller/ConsentsController.php b/administrator/components/com_privacy/src/Controller/ConsentsController.php
index 10976f3d2c2a3..fa8cda8a0a5d4 100644
--- a/administrator/components/com_privacy/src/Controller/ConsentsController.php
+++ b/administrator/components/com_privacy/src/Controller/ConsentsController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $ids = (array) $this->input->get('cid', [], 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), CMSApplication::MSG_ERROR);
- }
- else
- {
- /** @var ConsentsModel $model */
- $model = $this->getModel();
-
- if (!$model->invalidate($ids))
- {
- $this->setMessage($model->getError());
- }
- else
- {
- $this->setMessage(Text::plural('COM_PRIVACY_N_CONSENTS_INVALIDATED', count($ids)));
- }
- }
-
- $this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
- }
-
- /**
- * Method to invalidate all consents of a specific subject.
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function invalidateAll()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $filters = $this->input->get('filter', [], 'array');
-
- $this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
-
- if (isset($filters['subject']) && $filters['subject'] != '')
- {
- $subject = $filters['subject'];
- }
- else
- {
- $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'));
-
- return;
- }
-
- /** @var ConsentsModel $model */
- $model = $this->getModel();
-
- if (!$model->invalidateAll($subject))
- {
- $this->setMessage($model->getError());
- }
-
- $this->setMessage(Text::_('COM_PRIVACY_CONSENTS_INVALIDATED_ALL'));
- }
+ /**
+ * Method to invalidate specific consents.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function invalidate()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', [], 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'), CMSApplication::MSG_ERROR);
+ } else {
+ /** @var ConsentsModel $model */
+ $model = $this->getModel();
+
+ if (!$model->invalidate($ids)) {
+ $this->setMessage($model->getError());
+ } else {
+ $this->setMessage(Text::plural('COM_PRIVACY_N_CONSENTS_INVALIDATED', count($ids)));
+ }
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
+ }
+
+ /**
+ * Method to invalidate all consents of a specific subject.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function invalidateAll()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $filters = $this->input->get('filter', [], 'array');
+
+ $this->setRedirect(Route::_('index.php?option=com_privacy&view=consents', false));
+
+ if (isset($filters['subject']) && $filters['subject'] != '') {
+ $subject = $filters['subject'];
+ } else {
+ $this->app->enqueueMessage(Text::_('JERROR_NO_ITEMS_SELECTED'));
+
+ return;
+ }
+
+ /** @var ConsentsModel $model */
+ $model = $this->getModel();
+
+ if (!$model->invalidateAll($subject)) {
+ $this->setMessage($model->getError());
+ }
+
+ $this->setMessage(Text::_('COM_PRIVACY_CONSENTS_INVALIDATED_ALL'));
+ }
}
diff --git a/administrator/components/com_privacy/src/Controller/DisplayController.php b/administrator/components/com_privacy/src/Controller/DisplayController.php
index 33d3fbc8dd4d3..48d5a1ab71622 100644
--- a/administrator/components/com_privacy/src/Controller/DisplayController.php
+++ b/administrator/components/com_privacy/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
app->getDocument();
-
- // Set the default view name and format from the Request.
- $vName = $this->input->get('view', $this->default_view);
- $vFormat = $document->getType();
- $lName = $this->input->get('layout', 'default', 'string');
-
- // Get and render the view.
- if ($view = $this->getView($vName, $vFormat))
- {
- $model = $this->getModel($vName);
- $view->setModel($model, true);
-
- if ($vName === 'request')
- {
- // For the default layout, we need to also push the action logs model into the view
- if ($lName === 'default')
- {
- $logsModel = $this->app->bootComponent('com_actionlogs')
- ->getMVCFactory()->createModel('Actionlogs', 'Administrator', ['ignore_request' => true]);
-
- // Set default ordering for the context
- $logsModel->setState('list.fullordering', 'a.log_date DESC');
-
- // And push the model into the view
- $view->setModel($logsModel, false);
- }
-
- // For the edit layout, if mail sending is disabled then redirect back to the list view as the form is unusable in this state
- if ($lName === 'edit' && !$this->app->get('mailonline', 1))
- {
- $this->setRedirect(
- Route::_('index.php?option=com_privacy&view=requests', false),
- Text::_('COM_PRIVACY_WARNING_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'),
- 'warning'
- );
-
- return $this;
- }
- }
-
- $view->setLayout($lName);
-
- // Push document object into the view.
- $view->document = $document;
-
- $view->display();
- }
-
- return $this;
- }
-
- /**
- * Fetch and report number urgent privacy requests in JSON format, for AJAX requests
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function getNumberUrgentRequests()
- {
- // Check for a valid token. If invalid, send a 403 with the error message.
- if (!Session::checkToken('get'))
- {
- $this->app->setHeader('status', 403, true);
- $this->app->sendHeaders();
- echo new JsonResponse(new \Exception(Text::_('JINVALID_TOKEN'), 403));
- $this->app->close();
- }
-
- /** @var RequestsModel $model */
- $model = $this->getModel('requests');
- $numberUrgentRequests = $model->getNumberUrgentRequests();
-
- echo new JsonResponse(['number_urgent_requests' => $numberUrgentRequests]);
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 3.9.0
+ */
+ protected $default_view = 'requests';
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
+ *
+ * @return $this
+ *
+ * @since 3.9.0
+ */
+ public function display($cachable = false, $urlparams = [])
+ {
+ // Get the document object.
+ $document = $this->app->getDocument();
+
+ // Set the default view name and format from the Request.
+ $vName = $this->input->get('view', $this->default_view);
+ $vFormat = $document->getType();
+ $lName = $this->input->get('layout', 'default', 'string');
+
+ // Get and render the view.
+ if ($view = $this->getView($vName, $vFormat)) {
+ $model = $this->getModel($vName);
+ $view->setModel($model, true);
+
+ if ($vName === 'request') {
+ // For the default layout, we need to also push the action logs model into the view
+ if ($lName === 'default') {
+ $logsModel = $this->app->bootComponent('com_actionlogs')
+ ->getMVCFactory()->createModel('Actionlogs', 'Administrator', ['ignore_request' => true]);
+
+ // Set default ordering for the context
+ $logsModel->setState('list.fullordering', 'a.log_date DESC');
+
+ // And push the model into the view
+ $view->setModel($logsModel, false);
+ }
+
+ // For the edit layout, if mail sending is disabled then redirect back to the list view as the form is unusable in this state
+ if ($lName === 'edit' && !$this->app->get('mailonline', 1)) {
+ $this->setRedirect(
+ Route::_('index.php?option=com_privacy&view=requests', false),
+ Text::_('COM_PRIVACY_WARNING_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'),
+ 'warning'
+ );
+
+ return $this;
+ }
+ }
+
+ $view->setLayout($lName);
+
+ // Push document object into the view.
+ $view->document = $document;
+
+ $view->display();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch and report number urgent privacy requests in JSON format, for AJAX requests
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function getNumberUrgentRequests()
+ {
+ // Check for a valid token. If invalid, send a 403 with the error message.
+ if (!Session::checkToken('get')) {
+ $this->app->setHeader('status', 403, true);
+ $this->app->sendHeaders();
+ echo new JsonResponse(new \Exception(Text::_('JINVALID_TOKEN'), 403));
+ $this->app->close();
+ }
+
+ /** @var RequestsModel $model */
+ $model = $this->getModel('requests');
+ $numberUrgentRequests = $model->getNumberUrgentRequests();
+
+ echo new JsonResponse(['number_urgent_requests' => $numberUrgentRequests]);
+ }
}
diff --git a/administrator/components/com_privacy/src/Controller/RequestController.php b/administrator/components/com_privacy/src/Controller/RequestController.php
index 2f4c55c3ec1fb..84a0baaacedf0 100644
--- a/administrator/components/com_privacy/src/Controller/RequestController.php
+++ b/administrator/components/com_privacy/src/Controller/RequestController.php
@@ -1,4 +1,5 @@
checkToken();
-
- /** @var RequestModel $model */
- $model = $this->getModel();
-
- /** @var RequestTable $table */
- $table = $model->getTable();
-
- // Determine the name of the primary key for the data.
- if (empty($key))
- {
- $key = $table->getKeyName();
- }
-
- // To avoid data collisions the urlVar may be different from the primary key.
- if (empty($urlVar))
- {
- $urlVar = $key;
- }
-
- $recordId = $this->input->getInt($urlVar);
-
- $item = $model->getItem($recordId);
-
- // Ensure this record can transition to the requested state
- if (!$this->canTransition($item, '2'))
- {
- $this->setMessage(Text::_('COM_PRIVACY_ERROR_COMPLETE_TRANSITION_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Build the data array for the update
- $data = [
- $key => $recordId,
- 'status' => '2',
- ];
-
- // Access check.
- if (!$this->allowSave($data, $key))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Attempt to save the data.
- if (!$model->save($data))
- {
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Log the request completed
- $model->logRequestCompleted($recordId);
-
- $this->setMessage(Text::_('COM_PRIVACY_REQUEST_COMPLETED'));
-
- $url = 'index.php?option=com_privacy&view=requests';
-
- // Check if there is a return value
- $return = $this->input->get('return', null, 'base64');
-
- if (!is_null($return) && Uri::isInternal(base64_decode($return)))
- {
- $url = base64_decode($return);
- }
-
- // Redirect to the list screen.
- $this->setRedirect(Route::_($url, false));
-
- return true;
- }
-
- /**
- * Method to email the data export for a request.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function emailexport()
- {
- // Check for request forgeries.
- $this->checkToken('get');
-
- /** @var ExportModel $model */
- $model = $this->getModel('Export');
-
- $recordId = $this->input->getUint('id');
-
- if (!$model->emailDataExport($recordId))
- {
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_EXPORT_EMAIL_FAILED', $model->getError()), 'error');
- }
- else
- {
- $this->setMessage(Text::_('COM_PRIVACY_EXPORT_EMAILED'));
- }
-
- $url = 'index.php?option=com_privacy&view=requests';
-
- // Check if there is a return value
- $return = $this->input->get('return', null, 'base64');
-
- if (!is_null($return) && Uri::isInternal(base64_decode($return)))
- {
- $url = base64_decode($return);
- }
-
- // Redirect to the list screen.
- $this->setRedirect(Route::_($url, false));
-
- return true;
- }
-
- /**
- * Method to export the data for a request.
- *
- * @return $this
- *
- * @since 3.9.0
- */
- public function export()
- {
- $this->input->set('view', 'export');
-
- return $this->display();
- }
-
- /**
- * Method to invalidate a request.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function invalidate($key = null, $urlVar = null)
- {
- // Check for request forgeries.
- $this->checkToken();
-
- /** @var RequestModel $model */
- $model = $this->getModel();
-
- /** @var RequestTable $table */
- $table = $model->getTable();
-
- // Determine the name of the primary key for the data.
- if (empty($key))
- {
- $key = $table->getKeyName();
- }
-
- // To avoid data collisions the urlVar may be different from the primary key.
- if (empty($urlVar))
- {
- $urlVar = $key;
- }
-
- $recordId = $this->input->getInt($urlVar);
-
- $item = $model->getItem($recordId);
-
- // Ensure this record can transition to the requested state
- if (!$this->canTransition($item, '-1'))
- {
- $this->setMessage(Text::_('COM_PRIVACY_ERROR_INVALID_TRANSITION_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Build the data array for the update
- $data = [
- $key => $recordId,
- 'status' => '-1',
- ];
-
- // Access check.
- if (!$this->allowSave($data, $key))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Attempt to save the data.
- if (!$model->save($data))
- {
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- // Log the request invalidated
- $model->logRequestInvalidated($recordId);
-
- $this->setMessage(Text::_('COM_PRIVACY_REQUEST_INVALIDATED'));
-
- $url = 'index.php?option=com_privacy&view=requests';
-
- // Check if there is a return value
- $return = $this->input->get('return', null, 'base64');
-
- if (!is_null($return) && Uri::isInternal(base64_decode($return)))
- {
- $url = base64_decode($return);
- }
-
- // Redirect to the list screen.
- $this->setRedirect(Route::_($url, false));
-
- return true;
- }
-
- /**
- * Method to remove the user data for a privacy remove request.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function remove()
- {
- // Check for request forgeries.
- $this->checkToken('request');
-
- /** @var RemoveModel $model */
- $model = $this->getModel('Remove');
-
- $recordId = $this->input->getUint('id');
-
- if (!$model->removeDataForRequest($recordId))
- {
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_REMOVE_DATA_FAILED', $model->getError()), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=com_privacy&view=request&id=' . $recordId, false
- )
- );
-
- return false;
- }
-
- $this->setMessage(Text::_('COM_PRIVACY_DATA_REMOVED'));
-
- $url = 'index.php?option=com_privacy&view=requests';
-
- // Check if there is a return value
- $return = $this->input->get('return', null, 'base64');
-
- if (!is_null($return) && Uri::isInternal(base64_decode($return)))
- {
- $url = base64_decode($return);
- }
-
- // Redirect to the list screen.
- $this->setRedirect(Route::_($url, false));
-
- return true;
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = [])
- {
- // This hook only processes new items
- if (!$model->getState($model->getName() . '.new', false))
- {
- return;
- }
-
- if (!$model->logRequestCreated($model->getState($model->getName() . '.id')))
- {
- if ($error = $model->getError())
- {
- $this->app->enqueueMessage($error, 'warning');
- }
- }
-
- if (!$model->notifyUserAdminCreatedRequest($model->getState($model->getName() . '.id')))
- {
- if ($error = $model->getError())
- {
- $this->app->enqueueMessage($error, 'warning');
- }
- }
- else
- {
- $this->app->enqueueMessage(Text::_('COM_PRIVACY_MSG_CONFIRM_EMAIL_SENT_TO_USER'));
- }
- }
-
- /**
- * Method to determine if an item can transition to the specified status.
- *
- * @param object $item The item being updated.
- * @param string $newStatus The new status of the item.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- private function canTransition($item, $newStatus)
- {
- switch ($item->status)
- {
- case '0':
- // A pending item can only move to invalid through this controller due to the requirement for a user to confirm the request
- return $newStatus === '-1';
-
- case '1':
- // A confirmed item can be marked completed or invalid
- return in_array($newStatus, ['-1', '2'], true);
-
- // An item which is already in an invalid or complete state cannot transition, likewise if we don't know the state don't change anything
- case '-1':
- case '2':
- default:
- return false;
- }
- }
+ /**
+ * Method to complete a request.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function complete($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ /** @var RequestModel $model */
+ $model = $this->getModel();
+
+ /** @var RequestTable $table */
+ $table = $model->getTable();
+
+ // Determine the name of the primary key for the data.
+ if (empty($key)) {
+ $key = $table->getKeyName();
+ }
+
+ // To avoid data collisions the urlVar may be different from the primary key.
+ if (empty($urlVar)) {
+ $urlVar = $key;
+ }
+
+ $recordId = $this->input->getInt($urlVar);
+
+ $item = $model->getItem($recordId);
+
+ // Ensure this record can transition to the requested state
+ if (!$this->canTransition($item, '2')) {
+ $this->setMessage(Text::_('COM_PRIVACY_ERROR_COMPLETE_TRANSITION_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Build the data array for the update
+ $data = [
+ $key => $recordId,
+ 'status' => '2',
+ ];
+
+ // Access check.
+ if (!$this->allowSave($data, $key)) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($data)) {
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Log the request completed
+ $model->logRequestCompleted($recordId);
+
+ $this->setMessage(Text::_('COM_PRIVACY_REQUEST_COMPLETED'));
+
+ $url = 'index.php?option=com_privacy&view=requests';
+
+ // Check if there is a return value
+ $return = $this->input->get('return', null, 'base64');
+
+ if (!is_null($return) && Uri::isInternal(base64_decode($return))) {
+ $url = base64_decode($return);
+ }
+
+ // Redirect to the list screen.
+ $this->setRedirect(Route::_($url, false));
+
+ return true;
+ }
+
+ /**
+ * Method to email the data export for a request.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function emailexport()
+ {
+ // Check for request forgeries.
+ $this->checkToken('get');
+
+ /** @var ExportModel $model */
+ $model = $this->getModel('Export');
+
+ $recordId = $this->input->getUint('id');
+
+ if (!$model->emailDataExport($recordId)) {
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_EXPORT_EMAIL_FAILED', $model->getError()), 'error');
+ } else {
+ $this->setMessage(Text::_('COM_PRIVACY_EXPORT_EMAILED'));
+ }
+
+ $url = 'index.php?option=com_privacy&view=requests';
+
+ // Check if there is a return value
+ $return = $this->input->get('return', null, 'base64');
+
+ if (!is_null($return) && Uri::isInternal(base64_decode($return))) {
+ $url = base64_decode($return);
+ }
+
+ // Redirect to the list screen.
+ $this->setRedirect(Route::_($url, false));
+
+ return true;
+ }
+
+ /**
+ * Method to export the data for a request.
+ *
+ * @return $this
+ *
+ * @since 3.9.0
+ */
+ public function export()
+ {
+ $this->input->set('view', 'export');
+
+ return $this->display();
+ }
+
+ /**
+ * Method to invalidate a request.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function invalidate($key = null, $urlVar = null)
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ /** @var RequestModel $model */
+ $model = $this->getModel();
+
+ /** @var RequestTable $table */
+ $table = $model->getTable();
+
+ // Determine the name of the primary key for the data.
+ if (empty($key)) {
+ $key = $table->getKeyName();
+ }
+
+ // To avoid data collisions the urlVar may be different from the primary key.
+ if (empty($urlVar)) {
+ $urlVar = $key;
+ }
+
+ $recordId = $this->input->getInt($urlVar);
+
+ $item = $model->getItem($recordId);
+
+ // Ensure this record can transition to the requested state
+ if (!$this->canTransition($item, '-1')) {
+ $this->setMessage(Text::_('COM_PRIVACY_ERROR_INVALID_TRANSITION_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Build the data array for the update
+ $data = [
+ $key => $recordId,
+ 'status' => '-1',
+ ];
+
+ // Access check.
+ if (!$this->allowSave($data, $key)) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($data)) {
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ // Log the request invalidated
+ $model->logRequestInvalidated($recordId);
+
+ $this->setMessage(Text::_('COM_PRIVACY_REQUEST_INVALIDATED'));
+
+ $url = 'index.php?option=com_privacy&view=requests';
+
+ // Check if there is a return value
+ $return = $this->input->get('return', null, 'base64');
+
+ if (!is_null($return) && Uri::isInternal(base64_decode($return))) {
+ $url = base64_decode($return);
+ }
+
+ // Redirect to the list screen.
+ $this->setRedirect(Route::_($url, false));
+
+ return true;
+ }
+
+ /**
+ * Method to remove the user data for a privacy remove request.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function remove()
+ {
+ // Check for request forgeries.
+ $this->checkToken('request');
+
+ /** @var RemoveModel $model */
+ $model = $this->getModel('Remove');
+
+ $recordId = $this->input->getUint('id');
+
+ if (!$model->removeDataForRequest($recordId)) {
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('COM_PRIVACY_ERROR_REMOVE_DATA_FAILED', $model->getError()), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=com_privacy&view=request&id=' . $recordId,
+ false
+ )
+ );
+
+ return false;
+ }
+
+ $this->setMessage(Text::_('COM_PRIVACY_DATA_REMOVED'));
+
+ $url = 'index.php?option=com_privacy&view=requests';
+
+ // Check if there is a return value
+ $return = $this->input->get('return', null, 'base64');
+
+ if (!is_null($return) && Uri::isInternal(base64_decode($return))) {
+ $url = base64_decode($return);
+ }
+
+ // Redirect to the list screen.
+ $this->setRedirect(Route::_($url, false));
+
+ return true;
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = [])
+ {
+ // This hook only processes new items
+ if (!$model->getState($model->getName() . '.new', false)) {
+ return;
+ }
+
+ if (!$model->logRequestCreated($model->getState($model->getName() . '.id'))) {
+ if ($error = $model->getError()) {
+ $this->app->enqueueMessage($error, 'warning');
+ }
+ }
+
+ if (!$model->notifyUserAdminCreatedRequest($model->getState($model->getName() . '.id'))) {
+ if ($error = $model->getError()) {
+ $this->app->enqueueMessage($error, 'warning');
+ }
+ } else {
+ $this->app->enqueueMessage(Text::_('COM_PRIVACY_MSG_CONFIRM_EMAIL_SENT_TO_USER'));
+ }
+ }
+
+ /**
+ * Method to determine if an item can transition to the specified status.
+ *
+ * @param object $item The item being updated.
+ * @param string $newStatus The new status of the item.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ private function canTransition($item, $newStatus)
+ {
+ switch ($item->status) {
+ case '0':
+ // A pending item can only move to invalid through this controller due to the requirement for a user to confirm the request
+ return $newStatus === '-1';
+
+ case '1':
+ // A confirmed item can be marked completed or invalid
+ return in_array($newStatus, ['-1', '2'], true);
+
+ // An item which is already in an invalid or complete state cannot transition, likewise if we don't know the state don't change anything
+ case '-1':
+ case '2':
+ default:
+ return false;
+ }
+ }
}
diff --git a/administrator/components/com_privacy/src/Controller/RequestsController.php b/administrator/components/com_privacy/src/Controller/RequestsController.php
index 4b70cfab10129..dd59161712d49 100644
--- a/administrator/components/com_privacy/src/Controller/RequestsController.php
+++ b/administrator/components/com_privacy/src/Controller/RequestsController.php
@@ -1,4 +1,5 @@
true])
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return BaseDatabaseModel|boolean Model object on success; otherwise false on failure.
+ *
+ * @since 3.9.0
+ */
+ public function getModel($name = 'Request', $prefix = 'Administrator', $config = ['ignore_request' => true])
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_privacy/src/Dispatcher/Dispatcher.php b/administrator/components/com_privacy/src/Dispatcher/Dispatcher.php
index ea1e2921de28d..b2e57a8f7f163 100644
--- a/administrator/components/com_privacy/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_privacy/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ /**
+ * Method to check component access permission
+ *
+ * @return void
+ */
+ protected function checkAccess()
+ {
+ // Check the user has permission to access this component if in the backend
+ if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_privacy/src/Export/Domain.php b/administrator/components/com_privacy/src/Export/Domain.php
index 5ed379483007e..c81ecce20d964 100644
--- a/administrator/components/com_privacy/src/Export/Domain.php
+++ b/administrator/components/com_privacy/src/Export/Domain.php
@@ -1,4 +1,5 @@
items[] = $item;
- }
+ /**
+ * Add an item to the domain
+ *
+ * @param Item $item The item to add
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function addItem(Item $item)
+ {
+ $this->items[] = $item;
+ }
- /**
- * Get the domain's items
- *
- * @return Item[]
- *
- * @since 3.9.0
- */
- public function getItems()
- {
- return $this->items;
- }
+ /**
+ * Get the domain's items
+ *
+ * @return Item[]
+ *
+ * @since 3.9.0
+ */
+ public function getItems()
+ {
+ return $this->items;
+ }
}
diff --git a/administrator/components/com_privacy/src/Export/Field.php b/administrator/components/com_privacy/src/Export/Field.php
index 81c94dec78783..78a9de411cd81 100644
--- a/administrator/components/com_privacy/src/Export/Field.php
+++ b/administrator/components/com_privacy/src/Export/Field.php
@@ -1,4 +1,5 @@
fields[] = $field;
- }
+ /**
+ * Add a field to the item
+ *
+ * @param Field $field The field to add
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function addField(Field $field)
+ {
+ $this->fields[] = $field;
+ }
- /**
- * Get the item's fields
- *
- * @return Field[]
- *
- * @since 3.9.0
- */
- public function getFields()
- {
- return $this->fields;
- }
+ /**
+ * Get the item's fields
+ *
+ * @return Field[]
+ *
+ * @since 3.9.0
+ */
+ public function getFields()
+ {
+ return $this->fields;
+ }
}
diff --git a/administrator/components/com_privacy/src/Extension/PrivacyComponent.php b/administrator/components/com_privacy/src/Extension/PrivacyComponent.php
index d270d19b49cc0..28cad9ea7e7d6 100644
--- a/administrator/components/com_privacy/src/Extension/PrivacyComponent.php
+++ b/administrator/components/com_privacy/src/Extension/PrivacyComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('privacy', new Privacy);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('privacy', new Privacy());
+ }
}
diff --git a/administrator/components/com_privacy/src/Field/RequeststatusField.php b/administrator/components/com_privacy/src/Field/RequeststatusField.php
index 56bb22bdeb6f1..adfcccf2b4342 100644
--- a/administrator/components/com_privacy/src/Field/RequeststatusField.php
+++ b/administrator/components/com_privacy/src/Field/RequeststatusField.php
@@ -1,4 +1,5 @@
'COM_PRIVACY_STATUS_INVALID',
- '0' => 'COM_PRIVACY_STATUS_PENDING',
- '1' => 'COM_PRIVACY_STATUS_CONFIRMED',
- '2' => 'COM_PRIVACY_STATUS_COMPLETED',
- ];
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $predefinedOptions = [
+ '-1' => 'COM_PRIVACY_STATUS_INVALID',
+ '0' => 'COM_PRIVACY_STATUS_PENDING',
+ '1' => 'COM_PRIVACY_STATUS_CONFIRMED',
+ '2' => 'COM_PRIVACY_STATUS_COMPLETED',
+ ];
}
diff --git a/administrator/components/com_privacy/src/Field/RequesttypeField.php b/administrator/components/com_privacy/src/Field/RequesttypeField.php
index 9d0cbacf65acc..fbf8b8c3e0b32 100644
--- a/administrator/components/com_privacy/src/Field/RequesttypeField.php
+++ b/administrator/components/com_privacy/src/Field/RequesttypeField.php
@@ -1,4 +1,5 @@
'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_EXPORT',
- 'remove' => 'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_REMOVE',
- ];
+ /**
+ * Available types
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $predefinedOptions = [
+ 'export' => 'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_EXPORT',
+ 'remove' => 'COM_PRIVACY_HEADING_REQUEST_TYPE_TYPE_REMOVE',
+ ];
}
diff --git a/administrator/components/com_privacy/src/Helper/PrivacyHelper.php b/administrator/components/com_privacy/src/Helper/PrivacyHelper.php
index a8c0f9fabaadc..57e94f73ec87c 100644
--- a/administrator/components/com_privacy/src/Helper/PrivacyHelper.php
+++ b/administrator/components/com_privacy/src/Helper/PrivacyHelper.php
@@ -1,4 +1,5 @@
');
+ /**
+ * Render the data request as a XML document.
+ *
+ * @param Domain[] $exportData The data to be exported.
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ public static function renderDataAsXml(array $exportData)
+ {
+ $export = new \SimpleXMLElement(' ');
- foreach ($exportData as $domain)
- {
- $xmlDomain = $export->addChild('domain');
- $xmlDomain->addAttribute('name', $domain->name);
- $xmlDomain->addAttribute('description', $domain->description);
+ foreach ($exportData as $domain) {
+ $xmlDomain = $export->addChild('domain');
+ $xmlDomain->addAttribute('name', $domain->name);
+ $xmlDomain->addAttribute('description', $domain->description);
- foreach ($domain->getItems() as $item)
- {
- $xmlItem = $xmlDomain->addChild('item');
+ foreach ($domain->getItems() as $item) {
+ $xmlItem = $xmlDomain->addChild('item');
- if ($item->id)
- {
- $xmlItem->addAttribute('id', $item->id);
- }
+ if ($item->id) {
+ $xmlItem->addAttribute('id', $item->id);
+ }
- foreach ($item->getFields() as $field)
- {
- $xmlItem->{$field->name} = $field->value;
- }
- }
- }
+ foreach ($item->getFields() as $field) {
+ $xmlItem->{$field->name} = $field->value;
+ }
+ }
+ }
- $dom = new \DOMDocument;
- $dom->loadXML($export->asXML());
- $dom->formatOutput = true;
+ $dom = new \DOMDocument();
+ $dom->loadXML($export->asXML());
+ $dom->formatOutput = true;
- return $dom->saveXML();
- }
+ return $dom->saveXML();
+ }
- /**
- * Gets the privacyconsent system plugin extension id.
- *
- * @return integer The privacyconsent system plugin extension id.
- *
- * @since 3.9.2
- */
- public static function getPrivacyConsentPluginId()
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('privacyconsent'));
+ /**
+ * Gets the privacyconsent system plugin extension id.
+ *
+ * @return integer The privacyconsent system plugin extension id.
+ *
+ * @since 3.9.2
+ */
+ public static function getPrivacyConsentPluginId()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('privacyconsent'));
- $db->setQuery($query);
+ $db->setQuery($query);
- return (int) $db->loadResult();
- }
+ return (int) $db->loadResult();
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/CapabilitiesModel.php b/administrator/components/com_privacy/src/Model/CapabilitiesModel.php
index 98296574a4751..de797a8059f3c 100644
--- a/administrator/components/com_privacy/src/Model/CapabilitiesModel.php
+++ b/administrator/components/com_privacy/src/Model/CapabilitiesModel.php
@@ -1,4 +1,5 @@
[
- Text::_('COM_PRIVACY_CORE_CAPABILITY_SESSION_IP_ADDRESS_AND_COOKIE'),
- Text::sprintf('COM_PRIVACY_CORE_CAPABILITY_LOGGING_IP_ADDRESS', $app->get('log_path', JPATH_ADMINISTRATOR . '/logs')),
- Text::_('COM_PRIVACY_CORE_CAPABILITY_COMMUNICATION_WITH_JOOMLA_ORG'),
- ],
- ];
+ $coreCapabilities = [
+ Text::_('COM_PRIVACY_HEADING_CORE_CAPABILITIES') => [
+ Text::_('COM_PRIVACY_CORE_CAPABILITY_SESSION_IP_ADDRESS_AND_COOKIE'),
+ Text::sprintf('COM_PRIVACY_CORE_CAPABILITY_LOGGING_IP_ADDRESS', $app->get('log_path', JPATH_ADMINISTRATOR . '/logs')),
+ Text::_('COM_PRIVACY_CORE_CAPABILITY_COMMUNICATION_WITH_JOOMLA_ORG'),
+ ],
+ ];
- /*
- * We will search for capabilities from the following plugin groups:
- *
- * - Authentication: These plugins by design process user information and may have capabilities such as creating cookies
- * - Captcha: These plugins may communicate information to third party systems
- * - Installer: These plugins can add additional install capabilities to the Extension Manager, such as the Install from Web service
- * - Privacy: These plugins are the primary integration point into this component
- * - User: These plugins are intended to extend the user management system
- *
- * This is in addition to plugin groups which are imported before this method is triggered, generally this is the system group.
- */
+ /*
+ * We will search for capabilities from the following plugin groups:
+ *
+ * - Authentication: These plugins by design process user information and may have capabilities such as creating cookies
+ * - Captcha: These plugins may communicate information to third party systems
+ * - Installer: These plugins can add additional install capabilities to the Extension Manager, such as the Install from Web service
+ * - Privacy: These plugins are the primary integration point into this component
+ * - User: These plugins are intended to extend the user management system
+ *
+ * This is in addition to plugin groups which are imported before this method is triggered, generally this is the system group.
+ */
- PluginHelper::importPlugin('authentication');
- PluginHelper::importPlugin('captcha');
- PluginHelper::importPlugin('installer');
- PluginHelper::importPlugin('privacy');
- PluginHelper::importPlugin('user');
+ PluginHelper::importPlugin('authentication');
+ PluginHelper::importPlugin('captcha');
+ PluginHelper::importPlugin('installer');
+ PluginHelper::importPlugin('privacy');
+ PluginHelper::importPlugin('user');
- $pluginResults = $app->triggerEvent('onPrivacyCollectAdminCapabilities');
+ $pluginResults = $app->triggerEvent('onPrivacyCollectAdminCapabilities');
- // We are going to "cheat" here and include this component's capabilities without using a plugin
- $extensionCapabilities = [
- Text::_('COM_PRIVACY') => [
- Text::_('COM_PRIVACY_EXTENSION_CAPABILITY_PERSONAL_INFO'),
- ],
- ];
+ // We are going to "cheat" here and include this component's capabilities without using a plugin
+ $extensionCapabilities = [
+ Text::_('COM_PRIVACY') => [
+ Text::_('COM_PRIVACY_EXTENSION_CAPABILITY_PERSONAL_INFO'),
+ ],
+ ];
- foreach ($pluginResults as $pluginResult)
- {
- $extensionCapabilities += $pluginResult;
- }
+ foreach ($pluginResults as $pluginResult) {
+ $extensionCapabilities += $pluginResult;
+ }
- // Sort the extension list alphabetically
- ksort($extensionCapabilities);
+ // Sort the extension list alphabetically
+ ksort($extensionCapabilities);
- // Always prepend the core capabilities to the array
- return $coreCapabilities + $extensionCapabilities;
- }
+ // Always prepend the core capabilities to the array
+ return $coreCapabilities + $extensionCapabilities;
+ }
- /**
- * Method to auto-populate the model state.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function populateState()
- {
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_privacy'));
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function populateState()
+ {
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_privacy'));
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/ConsentsModel.php b/administrator/components/com_privacy/src/Model/ConsentsModel.php
index b50b1992fea73..8e75377879459 100644
--- a/administrator/components/com_privacy/src/Model/ConsentsModel.php
+++ b/administrator/components/com_privacy/src/Model/ConsentsModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select($this->getState('list.select', 'a.*'));
- $query->from($db->quoteName('#__privacy_consents', 'a'));
-
- // Join over the users for the username and name.
- $query->select($db->quoteName('u.username', 'username'))
- ->select($db->quoteName('u.name', 'name'));
- $query->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.user_id');
-
- // Filter by search in email
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $ids, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'uid:') === 0)
- {
- $uid = (int) substr($search, 4);
- $query->where($db->quoteName('a.user_id') . ' = :uid')
- ->bind(':uid', $uid, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'name:') === 0)
- {
- $search = '%' . substr($search, 5) . '%';
- $query->where($db->quoteName('u.name') . ' LIKE :search')
- ->bind(':search', $search);
- }
- else
- {
- $search = '%' . $search . '%';
- $query->where('(' . $db->quoteName('u.username') . ' LIKE :search)')
- ->bind(':search', $search);
- }
- }
-
- $state = $this->getState('filter.state');
-
- if ($state != '')
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
-
- // Handle the list ordering.
- $ordering = $this->getState('list.ordering');
- $direction = $this->getState('list.direction');
-
- if (!empty($ordering))
- {
- $query->order($db->escape($ordering) . ' ' . $db->escape($direction));
- }
-
- return $query;
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string
- *
- * @since 3.9.0
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function populateState($ordering = 'a.id', $direction = 'desc')
- {
- // Load the filter state.
- $this->setState(
- 'filter.search',
- $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search')
- );
-
- $this->setState(
- 'filter.subject',
- $this->getUserStateFromRequest($this->context . '.filter.subject', 'filter_subject')
- );
-
- $this->setState(
- 'filter.state',
- $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state')
- );
-
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_privacy'));
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to invalidate specific consents.
- *
- * @param array $pks The ids of the consents to invalidate.
- *
- * @return boolean True on success.
- */
- public function invalidate($pks)
- {
- // Sanitize the ids.
- $pks = (array) $pks;
- $pks = ArrayHelper::toInteger($pks);
-
- try
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__privacy_consents'))
- ->set($db->quoteName('state') . ' = -1')
- ->whereIn($db->quoteName('id'), $pks)
- ->where($db->quoteName('state') . ' = 1');
- $db->setQuery($query);
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Method to invalidate a group of specific consents.
- *
- * @param array $subject The subject of the consents to invalidate.
- *
- * @return boolean True on success.
- */
- public function invalidateAll($subject)
- {
- try
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__privacy_consents'))
- ->set($db->quoteName('state') . ' = -1')
- ->where($db->quoteName('subject') . ' = :subject')
- ->where($db->quoteName('state') . ' = 1')
- ->bind(':subject', $subject);
- $db->setQuery($query);
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return true;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 3.9.0
+ */
+ public function __construct($config = [])
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = [
+ 'id', 'a.id',
+ 'user_id', 'a.user_id',
+ 'subject', 'a.subject',
+ 'created', 'a.created',
+ 'username', 'u.username',
+ 'name', 'u.name',
+ 'state', 'a.state',
+ ];
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to get a DatabaseQuery object for retrieving the data set from a database.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 3.9.0
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select($this->getState('list.select', 'a.*'));
+ $query->from($db->quoteName('#__privacy_consents', 'a'));
+
+ // Join over the users for the username and name.
+ $query->select($db->quoteName('u.username', 'username'))
+ ->select($db->quoteName('u.name', 'name'));
+ $query->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.user_id');
+
+ // Filter by search in email
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $ids, ParameterType::INTEGER);
+ } elseif (stripos($search, 'uid:') === 0) {
+ $uid = (int) substr($search, 4);
+ $query->where($db->quoteName('a.user_id') . ' = :uid')
+ ->bind(':uid', $uid, ParameterType::INTEGER);
+ } elseif (stripos($search, 'name:') === 0) {
+ $search = '%' . substr($search, 5) . '%';
+ $query->where($db->quoteName('u.name') . ' LIKE :search')
+ ->bind(':search', $search);
+ } else {
+ $search = '%' . $search . '%';
+ $query->where('(' . $db->quoteName('u.username') . ' LIKE :search)')
+ ->bind(':search', $search);
+ }
+ }
+
+ $state = $this->getState('filter.state');
+
+ if ($state != '') {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ }
+
+ // Handle the list ordering.
+ $ordering = $this->getState('list.ordering');
+ $direction = $this->getState('list.direction');
+
+ if (!empty($ordering)) {
+ $query->order($db->escape($ordering) . ' ' . $db->escape($direction));
+ }
+
+ return $query;
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function populateState($ordering = 'a.id', $direction = 'desc')
+ {
+ // Load the filter state.
+ $this->setState(
+ 'filter.search',
+ $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search')
+ );
+
+ $this->setState(
+ 'filter.subject',
+ $this->getUserStateFromRequest($this->context . '.filter.subject', 'filter_subject')
+ );
+
+ $this->setState(
+ 'filter.state',
+ $this->getUserStateFromRequest($this->context . '.filter.state', 'filter_state')
+ );
+
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_privacy'));
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to invalidate specific consents.
+ *
+ * @param array $pks The ids of the consents to invalidate.
+ *
+ * @return boolean True on success.
+ */
+ public function invalidate($pks)
+ {
+ // Sanitize the ids.
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+
+ try {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__privacy_consents'))
+ ->set($db->quoteName('state') . ' = -1')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->where($db->quoteName('state') . ' = 1');
+ $db->setQuery($query);
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to invalidate a group of specific consents.
+ *
+ * @param array $subject The subject of the consents to invalidate.
+ *
+ * @return boolean True on success.
+ */
+ public function invalidateAll($subject)
+ {
+ try {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__privacy_consents'))
+ ->set($db->quoteName('state') . ' = -1')
+ ->where($db->quoteName('subject') . ' = :subject')
+ ->where($db->quoteName('state') . ' = 1')
+ ->bind(':subject', $subject);
+ $db->setQuery($query);
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/ExportModel.php b/administrator/components/com_privacy/src/Model/ExportModel.php
index b190bcdf3162e..748833b80f13a 100644
--- a/administrator/components/com_privacy/src/Model/ExportModel.php
+++ b/administrator/components/com_privacy/src/Model/ExportModel.php
@@ -1,4 +1,5 @@
getState($this->getName() . '.request_id');
-
- if (!$id)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
-
- return false;
- }
-
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
+ /**
+ * Create the export document for an information request.
+ *
+ * @param integer $id The request ID to process
+ *
+ * @return Domain[]|boolean A SimpleXMLElement object for a successful export or boolean false on an error
+ *
+ * @since 3.9.0
+ */
+ public function collectDataForExportRequest($id = null)
+ {
+ $id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
+
+ if (!$id) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
+
+ return false;
+ }
+
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ if ($table->request_type !== 'export') {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
+
+ return false;
+ }
+
+ if ($table->status != 1) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
+
+ return false;
+ }
+
+ // If there is a user account associated with the email address, load it here for use in the plugins
+ $db = $this->getDatabase();
+
+ $userId = (int) $db->setQuery(
+ $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__users'))
+ ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
+ ->bind(':email', $table->email)
+ ->setLimit(1)
+ )->loadResult();
+
+ $user = $userId ? User::getInstance($userId) : null;
+
+ // Log the export
+ $this->logExport($table);
+
+ PluginHelper::importPlugin('privacy');
+
+ $pluginResults = Factory::getApplication()->triggerEvent('onPrivacyExportRequest', [$table, $user]);
+
+ $domains = [];
+
+ foreach ($pluginResults as $pluginDomains) {
+ $domains = array_merge($domains, $pluginDomains);
+ }
+
+ return $domains;
+ }
+
+ /**
+ * Email the data export to the user.
+ *
+ * @param integer $id The request ID to process
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function emailDataExport($id = null)
+ {
+ $id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
+
+ if (!$id) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
+
+ return false;
+ }
+
+ $exportData = $this->collectDataForExportRequest($id);
+
+ if ($exportData === false) {
+ // Error is already set, we just need to bail
+ return false;
+ }
+
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ if ($table->request_type !== 'export') {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
- return false;
- }
+ return false;
+ }
- if ($table->request_type !== 'export')
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
-
- return false;
- }
-
- if ($table->status != 1)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
-
- return false;
- }
-
- // If there is a user account associated with the email address, load it here for use in the plugins
- $db = $this->getDatabase();
-
- $userId = (int) $db->setQuery(
- $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__users'))
- ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
- ->bind(':email', $table->email)
- ->setLimit(1)
- )->loadResult();
-
- $user = $userId ? User::getInstance($userId) : null;
-
- // Log the export
- $this->logExport($table);
-
- PluginHelper::importPlugin('privacy');
-
- $pluginResults = Factory::getApplication()->triggerEvent('onPrivacyExportRequest', [$table, $user]);
-
- $domains = [];
-
- foreach ($pluginResults as $pluginDomains)
- {
- $domains = array_merge($domains, $pluginDomains);
- }
-
- return $domains;
- }
-
- /**
- * Email the data export to the user.
- *
- * @param integer $id The request ID to process
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function emailDataExport($id = null)
- {
- $id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
-
- if (!$id)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_EXPORT'));
-
- return false;
- }
-
- $exportData = $this->collectDataForExportRequest($id);
-
- if ($exportData === false)
- {
- // Error is already set, we just need to bail
- return false;
- }
-
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- if ($table->request_type !== 'export')
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_EXPORT'));
-
- return false;
- }
-
- if ($table->status != 1)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
-
- return false;
- }
-
- // Log the email
- $this->logExportEmailed($table);
-
- /*
- * If there is an associated user account, we will attempt to send this email in the user's preferred language.
- * Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
- * for translating all messages.
- *
- * Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
- */
-
- $lang = Factory::getLanguage();
-
- $db = $this->getDatabase();
-
- $userId = (int) $db->setQuery(
- $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__users'))
- ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
- ->bind(':email', $table->email),
- 0,
- 1
- )->loadResult();
-
- if ($userId)
- {
- $receiver = User::getInstance($userId);
-
- /*
- * We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
- * falling back to the site language, falling back to the currently active language
- */
-
- $langCode = $receiver->getParam('admin_language', '');
-
- if (!$langCode)
- {
- $langCode = $receiver->getParam('language', $lang->getTag());
- }
-
- $lang = Language::getInstance($langCode, $lang->getDebug());
- }
-
- // Ensure the right language files have been loaded
- $lang->load('com_privacy', JPATH_ADMINISTRATOR)
- || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
-
- // The mailer can be set to either throw Exceptions or return boolean false, account for both
- try
- {
- $app = Factory::getApplication();
- $mailer = new MailTemplate('com_privacy.userdataexport', $app->getLanguage()->getTag());
-
- $templateData = [
- 'sitename' => $app->get('sitename'),
- 'url' => Uri::root(),
- ];
-
- $mailer->addRecipient($table->email);
- $mailer->addTemplateData($templateData);
- $mailer->addAttachment('user-data_' . Uri::getInstance()->toString(['host']) . '.xml', PrivacyHelper::renderDataAsXml($exportData));
-
- if ($mailer->send() === false)
- {
- $this->setError($mailer->ErrorInfo);
-
- return false;
- }
-
- return true;
- }
- catch (phpmailerException $exception)
- {
- $this->setError($exception->getMessage());
-
- return false;
- }
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $name The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $options Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @throws \Exception
- * @since 3.9.0
- */
- public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
- {
- return parent::getTable($name, $prefix, $options);
- }
-
- /**
- * Log the data export to the action log system.
- *
- * @param RequestTable $request The request record being processed
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function logExport(RequestTable $request)
- {
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'export',
- 'id' => $request->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT', 'com_privacy.request', $user->id);
- }
-
- /**
- * Log the data export email to the action log system.
- *
- * @param RequestTable $request The request record being processed
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function logExportEmailed(RequestTable $request)
- {
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'export_emailed',
- 'id' => $request->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT_EMAILED', 'com_privacy.request', $user->id);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function populateState()
- {
- // Get the pk of the record from the request.
- $this->setState($this->getName() . '.request_id', Factory::getApplication()->input->getUint('id'));
-
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_privacy'));
- }
-
- /**
- * Method to fetch an instance of the action log model.
- *
- * @return ActionlogModel
- *
- * @since 4.0.0
- */
- private function getActionlogModel(): ActionlogModel
- {
- return Factory::getApplication()->bootComponent('com_actionlogs')
- ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
- }
+ if ($table->status != 1) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_EXPORT_UNCONFIRMED_REQUEST'));
+
+ return false;
+ }
+
+ // Log the email
+ $this->logExportEmailed($table);
+
+ /*
+ * If there is an associated user account, we will attempt to send this email in the user's preferred language.
+ * Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
+ * for translating all messages.
+ *
+ * Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
+ */
+
+ $lang = Factory::getLanguage();
+
+ $db = $this->getDatabase();
+
+ $userId = (int) $db->setQuery(
+ $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__users'))
+ ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
+ ->bind(':email', $table->email),
+ 0,
+ 1
+ )->loadResult();
+
+ if ($userId) {
+ $receiver = User::getInstance($userId);
+
+ /*
+ * We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
+ * falling back to the site language, falling back to the currently active language
+ */
+
+ $langCode = $receiver->getParam('admin_language', '');
+
+ if (!$langCode) {
+ $langCode = $receiver->getParam('language', $lang->getTag());
+ }
+
+ $lang = Language::getInstance($langCode, $lang->getDebug());
+ }
+
+ // Ensure the right language files have been loaded
+ $lang->load('com_privacy', JPATH_ADMINISTRATOR)
+ || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
+
+ // The mailer can be set to either throw Exceptions or return boolean false, account for both
+ try {
+ $app = Factory::getApplication();
+ $mailer = new MailTemplate('com_privacy.userdataexport', $app->getLanguage()->getTag());
+
+ $templateData = [
+ 'sitename' => $app->get('sitename'),
+ 'url' => Uri::root(),
+ ];
+
+ $mailer->addRecipient($table->email);
+ $mailer->addTemplateData($templateData);
+ $mailer->addAttachment('user-data_' . Uri::getInstance()->toString(['host']) . '.xml', PrivacyHelper::renderDataAsXml($exportData));
+
+ if ($mailer->send() === false) {
+ $this->setError($mailer->ErrorInfo);
+
+ return false;
+ }
+
+ return true;
+ } catch (phpmailerException $exception) {
+ $this->setError($exception->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $name The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $options Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @throws \Exception
+ * @since 3.9.0
+ */
+ public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
+ {
+ return parent::getTable($name, $prefix, $options);
+ }
+
+ /**
+ * Log the data export to the action log system.
+ *
+ * @param RequestTable $request The request record being processed
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function logExport(RequestTable $request)
+ {
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'export',
+ 'id' => $request->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT', 'com_privacy.request', $user->id);
+ }
+
+ /**
+ * Log the data export email to the action log system.
+ *
+ * @param RequestTable $request The request record being processed
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function logExportEmailed(RequestTable $request)
+ {
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'export_emailed',
+ 'id' => $request->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_EXPORT_EMAILED', 'com_privacy.request', $user->id);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function populateState()
+ {
+ // Get the pk of the record from the request.
+ $this->setState($this->getName() . '.request_id', Factory::getApplication()->input->getUint('id'));
+
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_privacy'));
+ }
+
+ /**
+ * Method to fetch an instance of the action log model.
+ *
+ * @return ActionlogModel
+ *
+ * @since 4.0.0
+ */
+ private function getActionlogModel(): ActionlogModel
+ {
+ return Factory::getApplication()->bootComponent('com_actionlogs')
+ ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/RemoveModel.php b/administrator/components/com_privacy/src/Model/RemoveModel.php
index ffaff9a83d628..39a102c3ed037 100644
--- a/administrator/components/com_privacy/src/Model/RemoveModel.php
+++ b/administrator/components/com_privacy/src/Model/RemoveModel.php
@@ -1,4 +1,5 @@
getState($this->getName() . '.request_id');
-
- if (!$id)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_REMOVE'));
-
- return false;
- }
-
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- if ($table->request_type !== 'remove')
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_REMOVE'));
-
- return false;
- }
-
- if ($table->status != 1)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_UNCONFIRMED_REQUEST'));
-
- return false;
- }
-
- // If there is a user account associated with the email address, load it here for use in the plugins
- $db = $this->getDatabase();
-
- $userId = (int) $db->setQuery(
- $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__users'))
- ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
- ->bind(':email', $table->email)
- ->setLimit(1)
- )->loadResult();
-
- $user = $userId ? User::getInstance($userId) : null;
-
- $canRemove = true;
-
- PluginHelper::importPlugin('privacy');
-
- /** @var Status[] $pluginResults */
- $pluginResults = Factory::getApplication()->triggerEvent('onPrivacyCanRemoveData', [$table, $user]);
-
- foreach ($pluginResults as $status)
- {
- if (!$status->canRemove)
- {
- $this->setError($status->reason ?: Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_DATA'));
-
- $canRemove = false;
- }
- }
-
- if (!$canRemove)
- {
- $this->logRemoveBlocked($table, $this->getErrors());
-
- return false;
- }
-
- // Log the removal
- $this->logRemove($table);
-
- Factory::getApplication()->triggerEvent('onPrivacyRemoveData', [$table, $user]);
-
- return true;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $name The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $options Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @throws \Exception
- * @since 3.9.0
- */
- public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
- {
- return parent::getTable($name, $prefix, $options);
- }
-
- /**
- * Log the data removal to the action log system.
- *
- * @param RequestTable $request The request record being processed
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function logRemove(RequestTable $request)
- {
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'remove',
- 'id' => $request->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE', 'com_privacy.request', $user->id);
- }
-
- /**
- * Log the data removal being blocked to the action log system.
- *
- * @param RequestTable $request The request record being processed
- * @param string[] $reasons The reasons given why the record could not be removed.
- *
- * @return void
- *
- * @since 3.9.0
- */
- public function logRemoveBlocked(RequestTable $request, array $reasons)
- {
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'remove-blocked',
- 'id' => $request->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- 'reasons' => implode('; ', $reasons),
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE_BLOCKED', 'com_privacy.request', $user->id);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function populateState()
- {
- // Get the pk of the record from the request.
- $this->setState($this->getName() . '.request_id', Factory::getApplication()->input->getUint('id'));
-
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_privacy'));
- }
-
- /**
- * Method to fetch an instance of the action log model.
- *
- * @return ActionlogModel
- *
- * @since 4.0.0
- */
- private function getActionlogModel(): ActionlogModel
- {
- return Factory::getApplication()->bootComponent('com_actionlogs')
- ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
- }
+ /**
+ * Remove the user data.
+ *
+ * @param integer $id The request ID to process
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function removeDataForRequest($id = null)
+ {
+ $id = !empty($id) ? $id : (int) $this->getState($this->getName() . '.request_id');
+
+ if (!$id) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_ID_REQUIRED_FOR_REMOVE'));
+
+ return false;
+ }
+
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ if ($table->request_type !== 'remove') {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_REQUEST_TYPE_NOT_REMOVE'));
+
+ return false;
+ }
+
+ if ($table->status != 1) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_UNCONFIRMED_REQUEST'));
+
+ return false;
+ }
+
+ // If there is a user account associated with the email address, load it here for use in the plugins
+ $db = $this->getDatabase();
+
+ $userId = (int) $db->setQuery(
+ $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__users'))
+ ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
+ ->bind(':email', $table->email)
+ ->setLimit(1)
+ )->loadResult();
+
+ $user = $userId ? User::getInstance($userId) : null;
+
+ $canRemove = true;
+
+ PluginHelper::importPlugin('privacy');
+
+ /** @var Status[] $pluginResults */
+ $pluginResults = Factory::getApplication()->triggerEvent('onPrivacyCanRemoveData', [$table, $user]);
+
+ foreach ($pluginResults as $status) {
+ if (!$status->canRemove) {
+ $this->setError($status->reason ?: Text::_('COM_PRIVACY_ERROR_CANNOT_REMOVE_DATA'));
+
+ $canRemove = false;
+ }
+ }
+
+ if (!$canRemove) {
+ $this->logRemoveBlocked($table, $this->getErrors());
+
+ return false;
+ }
+
+ // Log the removal
+ $this->logRemove($table);
+
+ Factory::getApplication()->triggerEvent('onPrivacyRemoveData', [$table, $user]);
+
+ return true;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $name The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $options Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @throws \Exception
+ * @since 3.9.0
+ */
+ public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
+ {
+ return parent::getTable($name, $prefix, $options);
+ }
+
+ /**
+ * Log the data removal to the action log system.
+ *
+ * @param RequestTable $request The request record being processed
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function logRemove(RequestTable $request)
+ {
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'remove',
+ 'id' => $request->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE', 'com_privacy.request', $user->id);
+ }
+
+ /**
+ * Log the data removal being blocked to the action log system.
+ *
+ * @param RequestTable $request The request record being processed
+ * @param string[] $reasons The reasons given why the record could not be removed.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ public function logRemoveBlocked(RequestTable $request, array $reasons)
+ {
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'remove-blocked',
+ 'id' => $request->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $request->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ 'reasons' => implode('; ', $reasons),
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_REMOVE_BLOCKED', 'com_privacy.request', $user->id);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function populateState()
+ {
+ // Get the pk of the record from the request.
+ $this->setState($this->getName() . '.request_id', Factory::getApplication()->input->getUint('id'));
+
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_privacy'));
+ }
+
+ /**
+ * Method to fetch an instance of the action log model.
+ *
+ * @return ActionlogModel
+ *
+ * @since 4.0.0
+ */
+ private function getActionlogModel(): ActionlogModel
+ {
+ return Factory::getApplication()->bootComponent('com_actionlogs')
+ ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/RequestModel.php b/administrator/components/com_privacy/src/Model/RequestModel.php
index 6dde5737a1912..f034b675c3e40 100644
--- a/administrator/components/com_privacy/src/Model/RequestModel.php
+++ b/administrator/components/com_privacy/src/Model/RequestModel.php
@@ -1,4 +1,5 @@
loadForm('com_privacy.request', 'request', ['control' => 'jform', 'load_data' => $loadData]);
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $name The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $options Configuration array for model. Optional.
- *
- * @return Table A Table object
- *
- * @throws \Exception
- * @since 3.9.0
- */
- public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
- {
- return parent::getTable($name, $prefix, $options);
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return array The default data is an empty array.
- *
- * @since 3.9.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_privacy.edit.request.data', []);
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- return $data;
- }
-
- /**
- * Log the completion of a request to the action log system.
- *
- * @param integer $id The ID of the request to process.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function logRequestCompleted($id)
- {
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'request-completed',
- 'requesttype' => $table->request_type,
- 'subjectemail' => $table->email,
- 'id' => $table->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_COMPLETED_REQUEST', 'com_privacy.request', $user->id);
-
- return true;
- }
-
- /**
- * Log the creation of a request to the action log system.
- *
- * @param integer $id The ID of the request to process.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function logRequestCreated($id)
- {
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'request-created',
- 'requesttype' => $table->request_type,
- 'subjectemail' => $table->email,
- 'id' => $table->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_CREATED_REQUEST', 'com_privacy.request', $user->id);
-
- return true;
- }
-
- /**
- * Log the invalidation of a request to the action log system.
- *
- * @param integer $id The ID of the request to process.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function logRequestInvalidated($id)
- {
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $user = Factory::getUser();
-
- $message = [
- 'action' => 'request-invalidated',
- 'requesttype' => $table->request_type,
- 'subjectemail' => $table->email,
- 'id' => $table->id,
- 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
- 'userid' => $user->id,
- 'username' => $user->username,
- 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
- ];
-
- $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_INVALIDATED_REQUEST', 'com_privacy.request', $user->id);
-
- return true;
- }
-
- /**
- * Notifies the user that an information request has been created by a site administrator.
- *
- * Because confirmation tokens are stored in the database as a hashed value, this method will generate a new confirmation token
- * for the request.
- *
- * @param integer $id The ID of the request to process.
- *
- * @return boolean
- *
- * @since 3.9.0
- */
- public function notifyUserAdminCreatedRequest($id)
- {
- /** @var RequestTable $table */
- $table = $this->getTable();
-
- if (!$table->load($id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- /*
- * If there is an associated user account, we will attempt to send this email in the user's preferred language.
- * Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
- * for translating all messages.
- *
- * Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
- */
-
- $lang = Factory::getLanguage();
-
- $db = $this->getDatabase();
-
- $userId = (int) $db->setQuery(
- $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__users'))
- ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
- ->bind(':email', $table->email)
- ->setLimit(1)
- )->loadResult();
-
- if ($userId)
- {
- $receiver = User::getInstance($userId);
-
- /*
- * We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
- * falling back to the site language, falling back to the currently active language
- */
-
- $langCode = $receiver->getParam('admin_language', '');
-
- if (!$langCode)
- {
- $langCode = $receiver->getParam('language', $lang->getTag());
- }
-
- $lang = Language::getInstance($langCode, $lang->getDebug());
- }
-
- // Ensure the right language files have been loaded
- $lang->load('com_privacy', JPATH_ADMINISTRATOR)
- || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
-
- // Regenerate the confirmation token
- $token = ApplicationHelper::getHash(UserHelper::genRandomPassword());
- $hashedToken = UserHelper::hashPassword($token);
-
- $table->confirm_token = $hashedToken;
- $table->confirm_token_created_at = Factory::getDate()->toSql();
-
- try
- {
- $table->store();
- }
- catch (ExecutionFailureException $exception)
- {
- $this->setError($exception->getMessage());
-
- return false;
- }
-
- // The mailer can be set to either throw Exceptions or return boolean false, account for both
- try
- {
- $app = Factory::getApplication();
-
- $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
-
- $templateData = [
- 'sitename' => $app->get('sitename'),
- 'url' => Uri::root(),
- 'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm&confirm_token=' . $token, false, $linkMode, true),
- 'formurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm', false, $linkMode, true),
- 'token' => $token,
- ];
-
- switch ($table->request_type)
- {
- case 'export':
- $mailer = new MailTemplate('com_privacy.notification.admin.export', $app->getLanguage()->getTag());
-
- break;
-
- case 'remove':
- $mailer = new MailTemplate('com_privacy.notification.admin.remove', $app->getLanguage()->getTag());
-
- break;
-
- default:
- $this->setError(Text::_('COM_PRIVACY_ERROR_UNKNOWN_REQUEST_TYPE'));
-
- return false;
- }
-
- $mailer->addTemplateData($templateData);
- $mailer->addRecipient($table->email);
-
- $mailer->send();
-
- return true;
- }
- catch (MailDisabledException | phpmailerException $exception)
- {
- $this->setError($exception->getMessage());
-
- return false;
- }
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success, False on error.
- *
- * @since 3.9.0
- */
- public function save($data)
- {
- $table = $this->getTable();
- $key = $table->getKeyName();
- $pk = !empty($data[$key]) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
-
- if (!$pk && !Factory::getApplication()->get('mailonline', 1))
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'));
-
- return false;
- }
-
- return parent::save($data);
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see \Joomla\CMS\Form\FormRule
- * @see JFilterInput
- * @since 3.9.0
- */
- public function validate($form, $data, $group = null)
- {
- $validatedData = parent::validate($form, $data, $group);
-
- // If parent validation failed there's no point in doing our extended validation
- if ($validatedData === false)
- {
- return false;
- }
-
- // Make sure the status is always 0
- $validatedData['status'] = 0;
-
- // The user cannot create a request for their own account
- if (strtolower(Factory::getUser()->email) === strtolower($validatedData['email']))
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_FOR_SELF'));
-
- return false;
- }
-
- // Check for an active request for this email address
- $db = $this->getDatabase();
-
- $query = $db->getQuery(true)
- ->select('COUNT(id)')
- ->from($db->quoteName('#__privacy_requests'))
- ->where($db->quoteName('email') . ' = :email')
- ->where($db->quoteName('request_type') . ' = :requesttype')
- ->whereIn($db->quoteName('status'), [0, 1])
- ->bind(':email', $validatedData['email'])
- ->bind(':requesttype', $validatedData['request_type']);
-
- $activeRequestCount = (int) $db->setQuery($query)->loadResult();
-
- if ($activeRequestCount > 0)
- {
- $this->setError(Text::_('COM_PRIVACY_ERROR_ACTIVE_REQUEST_FOR_EMAIL'));
-
- return false;
- }
-
- return $validatedData;
- }
-
- /**
- * Method to fetch an instance of the action log model.
- *
- * @return ActionlogModel
- *
- * @since 4.0.0
- */
- private function getActionlogModel(): ActionlogModel
- {
- return Factory::getApplication()->bootComponent('com_actionlogs')
- ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
- }
+ /**
+ * Clean the cache
+ *
+ * @param string $group The cache group
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function cleanCache($group = 'com_privacy')
+ {
+ parent::cleanCache('com_privacy');
+ }
+
+ /**
+ * Method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 3.9.0
+ */
+ public function getForm($data = [], $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_privacy.request', 'request', ['control' => 'jform', 'load_data' => $loadData]);
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $name The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $options Configuration array for model. Optional.
+ *
+ * @return Table A Table object
+ *
+ * @throws \Exception
+ * @since 3.9.0
+ */
+ public function getTable($name = 'Request', $prefix = 'Administrator', $options = [])
+ {
+ return parent::getTable($name, $prefix, $options);
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return array The default data is an empty array.
+ *
+ * @since 3.9.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_privacy.edit.request.data', []);
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Log the completion of a request to the action log system.
+ *
+ * @param integer $id The ID of the request to process.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function logRequestCompleted($id)
+ {
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'request-completed',
+ 'requesttype' => $table->request_type,
+ 'subjectemail' => $table->email,
+ 'id' => $table->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_COMPLETED_REQUEST', 'com_privacy.request', $user->id);
+
+ return true;
+ }
+
+ /**
+ * Log the creation of a request to the action log system.
+ *
+ * @param integer $id The ID of the request to process.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function logRequestCreated($id)
+ {
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'request-created',
+ 'requesttype' => $table->request_type,
+ 'subjectemail' => $table->email,
+ 'id' => $table->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_CREATED_REQUEST', 'com_privacy.request', $user->id);
+
+ return true;
+ }
+
+ /**
+ * Log the invalidation of a request to the action log system.
+ *
+ * @param integer $id The ID of the request to process.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function logRequestInvalidated($id)
+ {
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ $message = [
+ 'action' => 'request-invalidated',
+ 'requesttype' => $table->request_type,
+ 'subjectemail' => $table->email,
+ 'id' => $table->id,
+ 'itemlink' => 'index.php?option=com_privacy&view=request&id=' . $table->id,
+ 'userid' => $user->id,
+ 'username' => $user->username,
+ 'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
+ ];
+
+ $this->getActionlogModel()->addLog([$message], 'COM_PRIVACY_ACTION_LOG_ADMIN_INVALIDATED_REQUEST', 'com_privacy.request', $user->id);
+
+ return true;
+ }
+
+ /**
+ * Notifies the user that an information request has been created by a site administrator.
+ *
+ * Because confirmation tokens are stored in the database as a hashed value, this method will generate a new confirmation token
+ * for the request.
+ *
+ * @param integer $id The ID of the request to process.
+ *
+ * @return boolean
+ *
+ * @since 3.9.0
+ */
+ public function notifyUserAdminCreatedRequest($id)
+ {
+ /** @var RequestTable $table */
+ $table = $this->getTable();
+
+ if (!$table->load($id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ /*
+ * If there is an associated user account, we will attempt to send this email in the user's preferred language.
+ * Because of this, it is expected that Language::_() is directly called and that the Text class is NOT used
+ * for translating all messages.
+ *
+ * Error messages will still be displayed to the administrator, so those messages should continue to use the Text class.
+ */
+
+ $lang = Factory::getLanguage();
+
+ $db = $this->getDatabase();
+
+ $userId = (int) $db->setQuery(
+ $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__users'))
+ ->where('LOWER(' . $db->quoteName('email') . ') = LOWER(:email)')
+ ->bind(':email', $table->email)
+ ->setLimit(1)
+ )->loadResult();
+
+ if ($userId) {
+ $receiver = User::getInstance($userId);
+
+ /*
+ * We don't know if the user has admin access, so we will check if they have an admin language in their parameters,
+ * falling back to the site language, falling back to the currently active language
+ */
+
+ $langCode = $receiver->getParam('admin_language', '');
+
+ if (!$langCode) {
+ $langCode = $receiver->getParam('language', $lang->getTag());
+ }
+
+ $lang = Language::getInstance($langCode, $lang->getDebug());
+ }
+
+ // Ensure the right language files have been loaded
+ $lang->load('com_privacy', JPATH_ADMINISTRATOR)
+ || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
+
+ // Regenerate the confirmation token
+ $token = ApplicationHelper::getHash(UserHelper::genRandomPassword());
+ $hashedToken = UserHelper::hashPassword($token);
+
+ $table->confirm_token = $hashedToken;
+ $table->confirm_token_created_at = Factory::getDate()->toSql();
+
+ try {
+ $table->store();
+ } catch (ExecutionFailureException $exception) {
+ $this->setError($exception->getMessage());
+
+ return false;
+ }
+
+ // The mailer can be set to either throw Exceptions or return boolean false, account for both
+ try {
+ $app = Factory::getApplication();
+
+ $linkMode = $app->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
+
+ $templateData = [
+ 'sitename' => $app->get('sitename'),
+ 'url' => Uri::root(),
+ 'tokenurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm&confirm_token=' . $token, false, $linkMode, true),
+ 'formurl' => Route::link('site', 'index.php?option=com_privacy&view=confirm', false, $linkMode, true),
+ 'token' => $token,
+ ];
+
+ switch ($table->request_type) {
+ case 'export':
+ $mailer = new MailTemplate('com_privacy.notification.admin.export', $app->getLanguage()->getTag());
+
+ break;
+
+ case 'remove':
+ $mailer = new MailTemplate('com_privacy.notification.admin.remove', $app->getLanguage()->getTag());
+
+ break;
+
+ default:
+ $this->setError(Text::_('COM_PRIVACY_ERROR_UNKNOWN_REQUEST_TYPE'));
+
+ return false;
+ }
+
+ $mailer->addTemplateData($templateData);
+ $mailer->addRecipient($table->email);
+
+ $mailer->send();
+
+ return true;
+ } catch (MailDisabledException | phpmailerException $exception) {
+ $this->setError($exception->getMessage());
+
+ return false;
+ }
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success, False on error.
+ *
+ * @since 3.9.0
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+ $key = $table->getKeyName();
+ $pk = !empty($data[$key]) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
+
+ if (!$pk && !Factory::getApplication()->get('mailonline', 1)) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_WHEN_SENDMAIL_DISABLED'));
+
+ return false;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see \Joomla\CMS\Form\FormRule
+ * @see JFilterInput
+ * @since 3.9.0
+ */
+ public function validate($form, $data, $group = null)
+ {
+ $validatedData = parent::validate($form, $data, $group);
+
+ // If parent validation failed there's no point in doing our extended validation
+ if ($validatedData === false) {
+ return false;
+ }
+
+ // Make sure the status is always 0
+ $validatedData['status'] = 0;
+
+ // The user cannot create a request for their own account
+ if (strtolower(Factory::getUser()->email) === strtolower($validatedData['email'])) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_CANNOT_CREATE_REQUEST_FOR_SELF'));
+
+ return false;
+ }
+
+ // Check for an active request for this email address
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select('COUNT(id)')
+ ->from($db->quoteName('#__privacy_requests'))
+ ->where($db->quoteName('email') . ' = :email')
+ ->where($db->quoteName('request_type') . ' = :requesttype')
+ ->whereIn($db->quoteName('status'), [0, 1])
+ ->bind(':email', $validatedData['email'])
+ ->bind(':requesttype', $validatedData['request_type']);
+
+ $activeRequestCount = (int) $db->setQuery($query)->loadResult();
+
+ if ($activeRequestCount > 0) {
+ $this->setError(Text::_('COM_PRIVACY_ERROR_ACTIVE_REQUEST_FOR_EMAIL'));
+
+ return false;
+ }
+
+ return $validatedData;
+ }
+
+ /**
+ * Method to fetch an instance of the action log model.
+ *
+ * @return ActionlogModel
+ *
+ * @since 4.0.0
+ */
+ private function getActionlogModel(): ActionlogModel
+ {
+ return Factory::getApplication()->bootComponent('com_actionlogs')
+ ->getMVCFactory()->createModel('Actionlog', 'Administrator', ['ignore_request' => true]);
+ }
}
diff --git a/administrator/components/com_privacy/src/Model/RequestsModel.php b/administrator/components/com_privacy/src/Model/RequestsModel.php
index c7d542d66437d..26692ab0c5108 100644
--- a/administrator/components/com_privacy/src/Model/RequestsModel.php
+++ b/administrator/components/com_privacy/src/Model/RequestsModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select($this->getState('list.select', 'a.*'));
- $query->from($db->quoteName('#__privacy_requests', 'a'));
-
- // Filter by status
- $status = $this->getState('filter.status');
-
- if (is_numeric($status))
- {
- $status = (int) $status;
- $query->where($db->quoteName('a.status') . ' = :status')
- ->bind(':status', $status, ParameterType::INTEGER);
- }
-
- // Filter by request type
- $requestType = $this->getState('filter.request_type', '');
-
- if ($requestType)
- {
- $query->where($db->quoteName('a.request_type') . ' = :requesttype')
- ->bind(':requesttype', $requestType);
- }
-
- // Filter by search in email
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . $search . '%';
- $query->where('(' . $db->quoteName('a.email') . ' LIKE :search)')
- ->bind(':search', $search);
- }
- }
-
- // Handle the list ordering.
- $ordering = $this->getState('list.ordering');
- $direction = $this->getState('list.direction');
-
- if (!empty($ordering))
- {
- $query->order($db->escape($ordering) . ' ' . $db->escape($direction));
- }
-
- return $query;
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string
- *
- * @since 3.9.0
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.status');
- $id .= ':' . $this->getState('filter.request_type');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function populateState($ordering = 'a.id', $direction = 'desc')
- {
- // Load the filter state.
- $this->setState(
- 'filter.search',
- $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search')
- );
-
- $this->setState(
- 'filter.status',
- $this->getUserStateFromRequest($this->context . '.filter.status', 'filter_status', '', 'int')
- );
-
- $this->setState(
- 'filter.request_type',
- $this->getUserStateFromRequest($this->context . '.filter.request_type', 'filter_request_type', '', 'string')
- );
-
- // Load the parameters.
- $this->setState('params', ComponentHelper::getParams('com_privacy'));
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to return number privacy requests older than X days.
- *
- * @return integer
- *
- * @since 3.9.0
- */
- public function getNumberUrgentRequests()
- {
- // Load the parameters.
- $params = ComponentHelper::getComponent('com_privacy')->getParams();
- $notify = (int) $params->get('notify', 14);
- $now = Factory::getDate()->toSql();
- $period = '-' . $notify;
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('COUNT(*)');
- $query->from($db->quoteName('#__privacy_requests'));
- $query->where($db->quoteName('status') . ' = 1 ');
- $query->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('requested_at'));
- $db->setQuery($query);
-
- return (int) $db->loadResult();
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 3.9.0
+ */
+ public function __construct($config = [])
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = [
+ 'id', 'a.id',
+ 'email', 'a.email',
+ 'requested_at', 'a.requested_at',
+ 'request_type', 'a.request_type',
+ 'status', 'a.status',
+ ];
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to get a DatabaseQuery object for retrieving the data set from a database.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 3.9.0
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select($this->getState('list.select', 'a.*'));
+ $query->from($db->quoteName('#__privacy_requests', 'a'));
+
+ // Filter by status
+ $status = $this->getState('filter.status');
+
+ if (is_numeric($status)) {
+ $status = (int) $status;
+ $query->where($db->quoteName('a.status') . ' = :status')
+ ->bind(':status', $status, ParameterType::INTEGER);
+ }
+
+ // Filter by request type
+ $requestType = $this->getState('filter.request_type', '');
+
+ if ($requestType) {
+ $query->where($db->quoteName('a.request_type') . ' = :requesttype')
+ ->bind(':requesttype', $requestType);
+ }
+
+ // Filter by search in email
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . $search . '%';
+ $query->where('(' . $db->quoteName('a.email') . ' LIKE :search)')
+ ->bind(':search', $search);
+ }
+ }
+
+ // Handle the list ordering.
+ $ordering = $this->getState('list.ordering');
+ $direction = $this->getState('list.direction');
+
+ if (!empty($ordering)) {
+ $query->order($db->escape($ordering) . ' ' . $db->escape($direction));
+ }
+
+ return $query;
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.status');
+ $id .= ':' . $this->getState('filter.request_type');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function populateState($ordering = 'a.id', $direction = 'desc')
+ {
+ // Load the filter state.
+ $this->setState(
+ 'filter.search',
+ $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search')
+ );
+
+ $this->setState(
+ 'filter.status',
+ $this->getUserStateFromRequest($this->context . '.filter.status', 'filter_status', '', 'int')
+ );
+
+ $this->setState(
+ 'filter.request_type',
+ $this->getUserStateFromRequest($this->context . '.filter.request_type', 'filter_request_type', '', 'string')
+ );
+
+ // Load the parameters.
+ $this->setState('params', ComponentHelper::getParams('com_privacy'));
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to return number privacy requests older than X days.
+ *
+ * @return integer
+ *
+ * @since 3.9.0
+ */
+ public function getNumberUrgentRequests()
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getComponent('com_privacy')->getParams();
+ $notify = (int) $params->get('notify', 14);
+ $now = Factory::getDate()->toSql();
+ $period = '-' . $notify;
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)');
+ $query->from($db->quoteName('#__privacy_requests'));
+ $query->where($db->quoteName('status') . ' = 1 ');
+ $query->where($query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('requested_at'));
+ $db->setQuery($query);
+
+ return (int) $db->loadResult();
+ }
}
diff --git a/administrator/components/com_privacy/src/Plugin/PrivacyPlugin.php b/administrator/components/com_privacy/src/Plugin/PrivacyPlugin.php
index 0679a47148290..90acf5589e30c 100644
--- a/administrator/components/com_privacy/src/Plugin/PrivacyPlugin.php
+++ b/administrator/components/com_privacy/src/Plugin/PrivacyPlugin.php
@@ -1,4 +1,5 @@
name = $name;
- $domain->description = $description;
-
- return $domain;
- }
-
- /**
- * Create an item object for an array
- *
- * @param array $data The array data to convert
- * @param integer|null $itemId The ID of this item
- *
- * @return Item
- *
- * @since 3.9.0
- */
- protected function createItemFromArray(array $data, $itemId = null)
- {
- $item = new Item;
- $item->id = $itemId;
-
- foreach ($data as $key => $value)
- {
- if (is_object($value))
- {
- $value = (array) $value;
- }
-
- if (is_array($value))
- {
- $value = print_r($value, true);
- }
-
- $field = new Field;
- $field->name = $key;
- $field->value = $value;
-
- $item->addField($field);
- }
-
- return $item;
- }
-
- /**
- * Create an item object for a Table object
- *
- * @param Table $table The Table object to convert
- *
- * @return Item
- *
- * @since 3.9.0
- */
- protected function createItemForTable($table)
- {
- $data = [];
-
- foreach (array_keys($table->getFields()) as $fieldName)
- {
- $data[$fieldName] = $table->$fieldName;
- }
-
- return $this->createItemFromArray($data, $table->{$table->getKeyName(false)});
- }
-
- /**
- * Helper function to create the domain for the items custom fields.
- *
- * @param string $context The context
- * @param array $items The items
- *
- * @return Domain
- *
- * @since 3.9.0
- */
- protected function createCustomFieldsDomain($context, $items = array())
- {
- if (!is_array($items))
- {
- $items = [$items];
- }
-
- $parts = FieldsHelper::extract($context);
-
- if (!$parts)
- {
- return [];
- }
-
- $type = str_replace('com_', '', $parts[0]);
-
- $domain = $this->createDomain($type . '_' . $parts[1] . '_custom_fields', 'joomla_' . $type . '_' . $parts[1] . '_custom_fields_data');
-
- foreach ($items as $item)
- {
- // Get item's fields, also preparing their value property for manual display
- $fields = FieldsHelper::getFields($parts[0] . '.' . $parts[1], $item);
-
- foreach ($fields as $field)
- {
- $fieldValue = is_array($field->value) ? implode(', ', $field->value) : $field->value;
-
- $data = [
- $type . '_id' => $item->id,
- 'field_name' => $field->name,
- 'field_title' => $field->title,
- 'field_value' => $fieldValue,
- ];
-
- $domain->addItem($this->createItemFromArray($data));
- }
- }
-
- return $domain;
- }
+ /**
+ * Database object
+ *
+ * @var \Joomla\Database\DatabaseDriver
+ * @since 3.9.0
+ */
+ protected $db;
+
+ /**
+ * Affects constructor behaviour. If true, language files will be loaded automatically.
+ *
+ * @var boolean
+ * @since 3.9.0
+ */
+ protected $autoloadLanguage = true;
+
+ /**
+ * Create a new domain object
+ *
+ * @param string $name The domain's name
+ * @param string $description The domain's description
+ *
+ * @return Domain
+ *
+ * @since 3.9.0
+ */
+ protected function createDomain($name, $description = '')
+ {
+ $domain = new Domain();
+ $domain->name = $name;
+ $domain->description = $description;
+
+ return $domain;
+ }
+
+ /**
+ * Create an item object for an array
+ *
+ * @param array $data The array data to convert
+ * @param integer|null $itemId The ID of this item
+ *
+ * @return Item
+ *
+ * @since 3.9.0
+ */
+ protected function createItemFromArray(array $data, $itemId = null)
+ {
+ $item = new Item();
+ $item->id = $itemId;
+
+ foreach ($data as $key => $value) {
+ if (is_object($value)) {
+ $value = (array) $value;
+ }
+
+ if (is_array($value)) {
+ $value = print_r($value, true);
+ }
+
+ $field = new Field();
+ $field->name = $key;
+ $field->value = $value;
+
+ $item->addField($field);
+ }
+
+ return $item;
+ }
+
+ /**
+ * Create an item object for a Table object
+ *
+ * @param Table $table The Table object to convert
+ *
+ * @return Item
+ *
+ * @since 3.9.0
+ */
+ protected function createItemForTable($table)
+ {
+ $data = [];
+
+ foreach (array_keys($table->getFields()) as $fieldName) {
+ $data[$fieldName] = $table->$fieldName;
+ }
+
+ return $this->createItemFromArray($data, $table->{$table->getKeyName(false)});
+ }
+
+ /**
+ * Helper function to create the domain for the items custom fields.
+ *
+ * @param string $context The context
+ * @param array $items The items
+ *
+ * @return Domain
+ *
+ * @since 3.9.0
+ */
+ protected function createCustomFieldsDomain($context, $items = array())
+ {
+ if (!is_array($items)) {
+ $items = [$items];
+ }
+
+ $parts = FieldsHelper::extract($context);
+
+ if (!$parts) {
+ return [];
+ }
+
+ $type = str_replace('com_', '', $parts[0]);
+
+ $domain = $this->createDomain($type . '_' . $parts[1] . '_custom_fields', 'joomla_' . $type . '_' . $parts[1] . '_custom_fields_data');
+
+ foreach ($items as $item) {
+ // Get item's fields, also preparing their value property for manual display
+ $fields = FieldsHelper::getFields($parts[0] . '.' . $parts[1], $item);
+
+ foreach ($fields as $field) {
+ $fieldValue = is_array($field->value) ? implode(', ', $field->value) : $field->value;
+
+ $data = [
+ $type . '_id' => $item->id,
+ 'field_name' => $field->name,
+ 'field_title' => $field->title,
+ 'field_value' => $fieldValue,
+ ];
+
+ $domain->addItem($this->createItemFromArray($data));
+ }
+ }
+
+ return $domain;
+ }
}
diff --git a/administrator/components/com_privacy/src/Removal/Status.php b/administrator/components/com_privacy/src/Removal/Status.php
index 03b698de5b24e..a3a53f5ded7a2 100644
--- a/administrator/components/com_privacy/src/Removal/Status.php
+++ b/administrator/components/com_privacy/src/Removal/Status.php
@@ -1,4 +1,5 @@
' . Text::_('COM_PRIVACY_STATUS_COMPLETED') . '';
+ /**
+ * Render a status badge
+ *
+ * @param integer $status The item status
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ public function statusLabel($status)
+ {
+ switch ($status) {
+ case 2:
+ return '' . Text::_('COM_PRIVACY_STATUS_COMPLETED') . ' ';
- case 1:
- return '' . Text::_('COM_PRIVACY_STATUS_CONFIRMED') . ' ';
+ case 1:
+ return '' . Text::_('COM_PRIVACY_STATUS_CONFIRMED') . ' ';
- case -1:
- return '' . Text::_('COM_PRIVACY_STATUS_INVALID') . ' ';
+ case -1:
+ return '' . Text::_('COM_PRIVACY_STATUS_INVALID') . ' ';
- default:
- case 0:
- return '' . Text::_('COM_PRIVACY_STATUS_PENDING') . ' ';
- }
- }
+ default:
+ case 0:
+ return '' . Text::_('COM_PRIVACY_STATUS_PENDING') . ' ';
+ }
+ }
}
diff --git a/administrator/components/com_privacy/src/Table/ConsentTable.php b/administrator/components/com_privacy/src/Table/ConsentTable.php
index 56330ce800cbf..c743b6273f84e 100644
--- a/administrator/components/com_privacy/src/Table/ConsentTable.php
+++ b/administrator/components/com_privacy/src/Table/ConsentTable.php
@@ -1,4 +1,5 @@
id)
- {
- if (!$this->remind)
- {
- $this->remind = '0';
- }
+ // Set default values for new records
+ if (!$this->id) {
+ if (!$this->remind) {
+ $this->remind = '0';
+ }
- if (!$this->created)
- {
- $this->created = $date->toSql();
- }
- }
+ if (!$this->created) {
+ $this->created = $date->toSql();
+ }
+ }
- return parent::store($updateNulls);
- }
+ return parent::store($updateNulls);
+ }
}
diff --git a/administrator/components/com_privacy/src/Table/RequestTable.php b/administrator/components/com_privacy/src/Table/RequestTable.php
index 8028385bffbbb..d772779df86c8 100644
--- a/administrator/components/com_privacy/src/Table/RequestTable.php
+++ b/administrator/components/com_privacy/src/Table/RequestTable.php
@@ -1,4 +1,5 @@
id)
- {
- if (!$this->status)
- {
- $this->status = '0';
- }
+ // Set default values for new records
+ if (!$this->id) {
+ if (!$this->status) {
+ $this->status = '0';
+ }
- if (!$this->requested_at)
- {
- $this->requested_at = $date->toSql();
- }
+ if (!$this->requested_at) {
+ $this->requested_at = $date->toSql();
+ }
- if (!$this->confirm_token_created_at)
- {
- $this->confirm_token_created_at = null;
- }
- }
+ if (!$this->confirm_token_created_at) {
+ $this->confirm_token_created_at = null;
+ }
+ }
- return parent::store($updateNulls);
- }
+ return parent::store($updateNulls);
+ }
}
diff --git a/administrator/components/com_privacy/src/View/Capabilities/HtmlView.php b/administrator/components/com_privacy/src/View/Capabilities/HtmlView.php
index ca86b2fc1b540..108bea965b403 100644
--- a/administrator/components/com_privacy/src/View/Capabilities/HtmlView.php
+++ b/administrator/components/com_privacy/src/View/Capabilities/HtmlView.php
@@ -1,4 +1,5 @@
capabilities = $this->get('Capabilities');
- $this->state = $this->get('State');
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see BaseHtmlView::loadTemplate()
+ * @since 3.9.0
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ // Initialise variables
+ $this->capabilities = $this->get('Capabilities');
+ $this->state = $this->get('State');
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new Genericdataexception(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new Genericdataexception(implode("\n", $errors), 500);
+ }
- $this->addToolbar();
+ $this->addToolbar();
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CAPABILITIES'), 'lock');
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CAPABILITIES'), 'lock');
- ToolbarHelper::preferences('com_privacy');
+ ToolbarHelper::preferences('com_privacy');
- ToolbarHelper::help('Privacy:_Extension_Capabilities');
- }
+ ToolbarHelper::help('Privacy:_Extension_Capabilities');
+ }
}
diff --git a/administrator/components/com_privacy/src/View/Consents/HtmlView.php b/administrator/components/com_privacy/src/View/Consents/HtmlView.php
index 13be2edb3c840..63d8243b54ab5 100644
--- a/administrator/components/com_privacy/src/View/Consents/HtmlView.php
+++ b/administrator/components/com_privacy/src/View/Consents/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->items = $model->getItems();
- $this->pagination = $model->getPagination();
- $this->state = $model->getState();
- $this->filterForm = $model->getFilterForm();
- $this->activeFilters = $model->getActiveFilters();
-
- if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new Genericdataexception(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CONSENTS'), 'lock');
-
- $bar = Toolbar::getInstance('toolbar');
-
- // Add a button to invalidate a consent
- if (!$this->isEmptyState)
- {
- $bar->appendButton(
- 'Confirm',
- 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_CONFIRM_MSG',
- 'trash',
- 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE',
- 'consents.invalidate',
- true
- );
- }
-
- // If the filter is restricted to a specific subject, show the "Invalidate all" button
- if ($this->state->get('filter.subject') != '')
- {
- $bar->appendButton(
- 'Confirm',
- 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL_CONFIRM_MSG',
- 'cancel',
- 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL',
- 'consents.invalidateAll',
- false
- );
- }
-
- ToolbarHelper::preferences('com_privacy');
-
- ToolbarHelper::help('Privacy:_Consents');
- }
+ /**
+ * The active search tools filters
+ *
+ * @var array
+ * @since 3.9.0
+ * @note Must be public to be accessed from the search tools layout
+ */
+ public $activeFilters;
+
+ /**
+ * Form instance containing the search tools filter form
+ *
+ * @var Form
+ * @since 3.9.0
+ * @note Must be public to be accessed from the search tools layout
+ */
+ public $filterForm;
+
+ /**
+ * The items to display
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ * @since 3.9.0
+ */
+ protected $pagination;
+
+ /**
+ * The state information
+ *
+ * @var CMSObject
+ * @since 3.9.0
+ */
+ protected $state;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see BaseHtmlView::loadTemplate()
+ * @since 3.9.0
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ /** @var ConsentsModel $model */
+ $model = $this->getModel();
+ $this->items = $model->getItems();
+ $this->pagination = $model->getPagination();
+ $this->state = $model->getState();
+ $this->filterForm = $model->getFilterForm();
+ $this->activeFilters = $model->getActiveFilters();
+
+ if (!count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new Genericdataexception(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_CONSENTS'), 'lock');
+
+ $bar = Toolbar::getInstance('toolbar');
+
+ // Add a button to invalidate a consent
+ if (!$this->isEmptyState) {
+ $bar->appendButton(
+ 'Confirm',
+ 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_CONFIRM_MSG',
+ 'trash',
+ 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE',
+ 'consents.invalidate',
+ true
+ );
+ }
+
+ // If the filter is restricted to a specific subject, show the "Invalidate all" button
+ if ($this->state->get('filter.subject') != '') {
+ $bar->appendButton(
+ 'Confirm',
+ 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL_CONFIRM_MSG',
+ 'cancel',
+ 'COM_PRIVACY_CONSENTS_TOOLBAR_INVALIDATE_ALL',
+ 'consents.invalidateAll',
+ false
+ );
+ }
+
+ ToolbarHelper::preferences('com_privacy');
+
+ ToolbarHelper::help('Privacy:_Consents');
+ }
}
diff --git a/administrator/components/com_privacy/src/View/Export/XmlView.php b/administrator/components/com_privacy/src/View/Export/XmlView.php
index 8fdee62f5a3c1..d2e341e22ed3c 100644
--- a/administrator/components/com_privacy/src/View/Export/XmlView.php
+++ b/administrator/components/com_privacy/src/View/Export/XmlView.php
@@ -1,4 +1,5 @@
getModel();
-
- $exportData = $model->collectDataForExportRequest();
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $requestId = $model->getState($model->getName() . '.request_id');
-
- // This document should always be downloaded
- $this->document->setDownload(true);
- $this->document->setName('export-request-' . $requestId);
-
- echo PrivacyHelper::renderDataAsXml($exportData);
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return mixed A string if successful, otherwise an Error object.
+ *
+ * @since 3.9.0
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ /** @var ExportModel $model */
+ $model = $this->getModel();
+
+ $exportData = $model->collectDataForExportRequest();
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $requestId = $model->getState($model->getName() . '.request_id');
+
+ // This document should always be downloaded
+ $this->document->setDownload(true);
+ $this->document->setName('export-request-' . $requestId);
+
+ echo PrivacyHelper::renderDataAsXml($exportData);
+ }
}
diff --git a/administrator/components/com_privacy/src/View/Request/HtmlView.php b/administrator/components/com_privacy/src/View/Request/HtmlView.php
index 0ca8c3de68cfc..adc1647c1cc48 100644
--- a/administrator/components/com_privacy/src/View/Request/HtmlView.php
+++ b/administrator/components/com_privacy/src/View/Request/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->item = $model->getItem();
- $this->state = $model->getState();
-
- // Variables only required for the default layout
- if ($this->getLayout() === 'default')
- {
- /** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel $logsModel */
- $logsModel = $this->getModel('actionlogs');
-
- $this->actionlogs = $logsModel->getLogsForItem('com_privacy.request', $this->item->id);
-
- // Load the com_actionlogs language strings for use in the layout
- $lang = Factory::getLanguage();
- $lang->load('com_actionlogs', JPATH_ADMINISTRATOR)
- || $lang->load('com_actionlogs', JPATH_ADMINISTRATOR . '/components/com_actionlogs');
- }
-
- // Variables only required for the edit layout
- if ($this->getLayout() === 'edit')
- {
- $this->form = $this->get('Form');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- // Set the title and toolbar based on the layout
- if ($this->getLayout() === 'edit')
- {
- ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_ADD_REQUEST'), 'lock');
-
- ToolbarHelper::save('request.save');
- ToolbarHelper::cancel('request.cancel');
- ToolbarHelper::help('Privacy:_New_Information_Request');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_SHOW_REQUEST'), 'lock');
-
- $bar = Toolbar::getInstance('toolbar');
-
- // Add transition and action buttons based on item status
- switch ($this->item->status)
- {
- case '0':
- $bar->appendButton('Standard', 'cancel-circle', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate', false);
-
- break;
-
- case '1':
- $return = '&return=' . base64_encode('index.php?option=com_privacy&view=request&id=' . (int) $this->item->id);
-
- $bar->appendButton('Standard', 'apply', 'COM_PRIVACY_TOOLBAR_COMPLETE', 'request.complete', false);
- $bar->appendButton('Standard', 'cancel-circle', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate', false);
-
- if ($this->item->request_type === 'export')
- {
- ToolbarHelper::link(
- Route::_('index.php?option=com_privacy&task=request.export&format=xml&id=' . (int) $this->item->id . $return),
- 'COM_PRIVACY_ACTION_EXPORT_DATA',
- 'download'
- );
-
- if (Factory::getApplication()->get('mailonline', 1))
- {
- ToolbarHelper::link(
- Route::_(
- 'index.php?option=com_privacy&task=request.emailexport&id=' . (int) $this->item->id . $return
- . '&' . Session::getFormToken() . '=1'
- ),
- 'COM_PRIVACY_ACTION_EMAIL_EXPORT_DATA',
- 'mail'
- );
- }
- }
-
- if ($this->item->request_type === 'remove')
- {
- $bar->appendButton('Standard', 'delete', 'COM_PRIVACY_ACTION_DELETE_DATA', 'request.remove', false);
- }
-
- break;
-
- // Item is in a "locked" state and cannot transition
- default:
- break;
- }
-
- ToolbarHelper::cancel('request.cancel', 'JTOOLBAR_CLOSE');
- ToolbarHelper::help('Privacy:_Review_Information_Request');
- }
- }
+ /**
+ * The action logs for the item
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $actionlogs;
+
+ /**
+ * The form object
+ *
+ * @var Form
+ * @since 3.9.0
+ */
+ protected $form;
+
+ /**
+ * The item record
+ *
+ * @var CMSObject
+ * @since 3.9.0
+ */
+ protected $item;
+
+ /**
+ * The state information
+ *
+ * @var CMSObject
+ * @since 3.9.0
+ */
+ protected $state;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see BaseHtmlView::loadTemplate()
+ * @since 3.9.0
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ /** @var RequestsModel $model */
+ $model = $this->getModel();
+ $this->item = $model->getItem();
+ $this->state = $model->getState();
+
+ // Variables only required for the default layout
+ if ($this->getLayout() === 'default') {
+ /** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel $logsModel */
+ $logsModel = $this->getModel('actionlogs');
+
+ $this->actionlogs = $logsModel->getLogsForItem('com_privacy.request', $this->item->id);
+
+ // Load the com_actionlogs language strings for use in the layout
+ $lang = Factory::getLanguage();
+ $lang->load('com_actionlogs', JPATH_ADMINISTRATOR)
+ || $lang->load('com_actionlogs', JPATH_ADMINISTRATOR . '/components/com_actionlogs');
+ }
+
+ // Variables only required for the edit layout
+ if ($this->getLayout() === 'edit') {
+ $this->form = $this->get('Form');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ // Set the title and toolbar based on the layout
+ if ($this->getLayout() === 'edit') {
+ ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_ADD_REQUEST'), 'lock');
+
+ ToolbarHelper::save('request.save');
+ ToolbarHelper::cancel('request.cancel');
+ ToolbarHelper::help('Privacy:_New_Information_Request');
+ } else {
+ ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUEST_SHOW_REQUEST'), 'lock');
+
+ $bar = Toolbar::getInstance('toolbar');
+
+ // Add transition and action buttons based on item status
+ switch ($this->item->status) {
+ case '0':
+ $bar->appendButton('Standard', 'cancel-circle', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate', false);
+
+ break;
+
+ case '1':
+ $return = '&return=' . base64_encode('index.php?option=com_privacy&view=request&id=' . (int) $this->item->id);
+
+ $bar->appendButton('Standard', 'apply', 'COM_PRIVACY_TOOLBAR_COMPLETE', 'request.complete', false);
+ $bar->appendButton('Standard', 'cancel-circle', 'COM_PRIVACY_TOOLBAR_INVALIDATE', 'request.invalidate', false);
+
+ if ($this->item->request_type === 'export') {
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_privacy&task=request.export&format=xml&id=' . (int) $this->item->id . $return),
+ 'COM_PRIVACY_ACTION_EXPORT_DATA',
+ 'download'
+ );
+
+ if (Factory::getApplication()->get('mailonline', 1)) {
+ ToolbarHelper::link(
+ Route::_(
+ 'index.php?option=com_privacy&task=request.emailexport&id=' . (int) $this->item->id . $return
+ . '&' . Session::getFormToken() . '=1'
+ ),
+ 'COM_PRIVACY_ACTION_EMAIL_EXPORT_DATA',
+ 'mail'
+ );
+ }
+ }
+
+ if ($this->item->request_type === 'remove') {
+ $bar->appendButton('Standard', 'delete', 'COM_PRIVACY_ACTION_DELETE_DATA', 'request.remove', false);
+ }
+
+ break;
+
+ // Item is in a "locked" state and cannot transition
+ default:
+ break;
+ }
+
+ ToolbarHelper::cancel('request.cancel', 'JTOOLBAR_CLOSE');
+ ToolbarHelper::help('Privacy:_Review_Information_Request');
+ }
+ }
}
diff --git a/administrator/components/com_privacy/src/View/Requests/HtmlView.php b/administrator/components/com_privacy/src/View/Requests/HtmlView.php
index 6e23cc33c2df6..874964cb7d0af 100644
--- a/administrator/components/com_privacy/src/View/Requests/HtmlView.php
+++ b/administrator/components/com_privacy/src/View/Requests/HtmlView.php
@@ -1,4 +1,5 @@
getModel();
- $this->items = $model->getItems();
- $this->pagination = $model->getPagination();
- $this->state = $model->getState();
- $this->filterForm = $model->getFilterForm();
- $this->activeFilters = $model->getActiveFilters();
- $this->urgentRequestAge = (int) ComponentHelper::getParams('com_privacy')->get('notify', 14);
- $this->sendMailEnabled = (bool) Factory::getApplication()->get('mailonline', 1);
-
- if (!count($this->items) && $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new Genericdataexception(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.9.0
- */
- protected function addToolbar()
- {
- ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUESTS'), 'lock');
-
- // Requests can only be created if mail sending is enabled
- if (Factory::getApplication()->get('mailonline', 1))
- {
- ToolbarHelper::addNew('request.add');
- }
-
- ToolbarHelper::preferences('com_privacy');
- ToolbarHelper::help('Privacy:_Information_Requests');
- }
+ /**
+ * The active search tools filters
+ *
+ * @var array
+ * @since 3.9.0
+ * @note Must be public to be accessed from the search tools layout
+ */
+ public $activeFilters;
+
+ /**
+ * Form instance containing the search tools filter form
+ *
+ * @var Form
+ * @since 3.9.0
+ * @note Must be public to be accessed from the search tools layout
+ */
+ public $filterForm;
+
+ /**
+ * The items to display
+ *
+ * @var array
+ * @since 3.9.0
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var Pagination
+ * @since 3.9.0
+ */
+ protected $pagination;
+
+ /**
+ * Flag indicating the site supports sending email
+ *
+ * @var boolean
+ * @since 3.9.0
+ */
+ protected $sendMailEnabled;
+
+ /**
+ * The state information
+ *
+ * @var CMSObject
+ * @since 3.9.0
+ */
+ protected $state;
+
+ /**
+ * The age of urgent requests
+ *
+ * @var integer
+ * @since 3.9.0
+ */
+ protected $urgentRequestAge;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @see BaseHtmlView::loadTemplate()
+ * @since 3.9.0
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ /** @var RequestsModel $model */
+ $model = $this->getModel();
+ $this->items = $model->getItems();
+ $this->pagination = $model->getPagination();
+ $this->state = $model->getState();
+ $this->filterForm = $model->getFilterForm();
+ $this->activeFilters = $model->getActiveFilters();
+ $this->urgentRequestAge = (int) ComponentHelper::getParams('com_privacy')->get('notify', 14);
+ $this->sendMailEnabled = (bool) Factory::getApplication()->get('mailonline', 1);
+
+ if (!count($this->items) && $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new Genericdataexception(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.9.0
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_PRIVACY_VIEW_REQUESTS'), 'lock');
+
+ // Requests can only be created if mail sending is enabled
+ if (Factory::getApplication()->get('mailonline', 1)) {
+ ToolbarHelper::addNew('request.add');
+ }
+
+ ToolbarHelper::preferences('com_privacy');
+ ToolbarHelper::help('Privacy:_Information_Requests');
+ }
}
diff --git a/administrator/components/com_privacy/tmpl/capabilities/default.php b/administrator/components/com_privacy/tmpl/capabilities/default.php
index fe0bfc66ecc47..137ed9db13173 100644
--- a/administrator/components/com_privacy/tmpl/capabilities/default.php
+++ b/administrator/components/com_privacy/tmpl/capabilities/default.php
@@ -1,4 +1,5 @@
-
-
-
-
- capabilities)) : ?>
-
-
-
-
-
- capabilities as $extension => $capabilities) : ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ capabilities)) : ?>
+
+
+
+
+
+ capabilities as $extension => $capabilities) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_privacy/tmpl/consents/default.php b/administrator/components/com_privacy/tmpl/consents/default.php
index f4240c1933ea2..0878d0b277689 100644
--- a/administrator/components/com_privacy/tmpl/consents/default.php
+++ b/administrator/components/com_privacy/tmpl/consents/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -29,100 +30,100 @@
$now = Factory::getDate();
$stateIcons = array(-1 => 'delete', 0 => 'archive', 1 => 'publish');
$stateMsgs = array(
- -1 => Text::_('COM_PRIVACY_CONSENTS_STATE_INVALIDATED'),
- 0 => Text::_('COM_PRIVACY_CONSENTS_STATE_OBSOLETE'),
- 1 => Text::_('COM_PRIVACY_CONSENTS_STATE_VALID')
+ -1 => Text::_('COM_PRIVACY_CONSENTS_STATE_INVALIDATED'),
+ 0 => Text::_('COM_PRIVACY_CONSENTS_STATE_OBSOLETE'),
+ 1 => Text::_('COM_PRIVACY_CONSENTS_STATE_VALID')
);
?>
diff --git a/administrator/components/com_privacy/tmpl/consents/emptystate.php b/administrator/components/com_privacy/tmpl/consents/emptystate.php
index 4527f0f230941..a76086b554fc5 100644
--- a/administrator/components/com_privacy/tmpl/consents/emptystate.php
+++ b/administrator/components/com_privacy/tmpl/consents/emptystate.php
@@ -1,4 +1,5 @@
'COM_PRIVACY_CONSENTS',
- 'formURL' => 'index.php?option=com_privacy&view=consents',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Privacy:_Consents',
- 'icon' => 'icon-lock',
+ 'textPrefix' => 'COM_PRIVACY_CONSENTS',
+ 'formURL' => 'index.php?option=com_privacy&view=consents',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Privacy:_Consents',
+ 'icon' => 'icon-lock',
];
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_privacy/tmpl/request/default.php b/administrator/components/com_privacy/tmpl/request/default.php
index 8fa51ae5ad271..95ae325ba8ab9 100644
--- a/administrator/components/com_privacy/tmpl/request/default.php
+++ b/administrator/components/com_privacy/tmpl/request/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_privacy/tmpl/request/edit.php b/administrator/components/com_privacy/tmpl/request/edit.php
index d38658acf3345..682e79313dd4d 100644
--- a/administrator/components/com_privacy/tmpl/request/edit.php
+++ b/administrator/components/com_privacy/tmpl/request/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_privacy/tmpl/requests/default.php b/administrator/components/com_privacy/tmpl/requests/default.php
index 9c7a9fa19fcd6..5f8c0c0a59f08 100644
--- a/administrator/components/com_privacy/tmpl/requests/default.php
+++ b/administrator/components/com_privacy/tmpl/requests/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -34,96 +35,96 @@
?>
diff --git a/administrator/components/com_privacy/tmpl/requests/emptystate.php b/administrator/components/com_privacy/tmpl/requests/emptystate.php
index c345a7be9d0f1..14eb521edf0ca 100644
--- a/administrator/components/com_privacy/tmpl/requests/emptystate.php
+++ b/administrator/components/com_privacy/tmpl/requests/emptystate.php
@@ -1,4 +1,5 @@
'COM_PRIVACY_REQUESTS',
- 'formURL' => 'index.php?option=com_privacy&view=requests',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Privacy:_Information_Requests',
- 'icon' => 'icon-lock',
+ 'textPrefix' => 'COM_PRIVACY_REQUESTS',
+ 'formURL' => 'index.php?option=com_privacy&view=requests',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:Privacy:_Information_Requests',
+ 'icon' => 'icon-lock',
];
-if (Factory::getApplication()->get('mailonline', 1))
-{
- $displayData['createURL'] = 'index.php?option=com_privacy&task=request.add';
+if (Factory::getApplication()->get('mailonline', 1)) {
+ $displayData['createURL'] = 'index.php?option=com_privacy&task=request.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_redirect/helpers/redirect.php b/administrator/components/com_redirect/helpers/redirect.php
index 0f6c31e9f6dae..5d155966520f4 100644
--- a/administrator/components/com_redirect/helpers/redirect.php
+++ b/administrator/components/com_redirect/helpers/redirect.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Redirect component helper.
diff --git a/administrator/components/com_redirect/layouts/toolbar/batch.php b/administrator/components/com_redirect/layouts/toolbar/batch.php
index 98e9e6de4b6ff..c2b2ead1992a5 100644
--- a/administrator/components/com_redirect/layouts/toolbar/batch.php
+++ b/administrator/components/com_redirect/layouts/toolbar/batch.php
@@ -1,4 +1,5 @@
-
-
+
+
diff --git a/administrator/components/com_redirect/services/provider.php b/administrator/components/com_redirect/services/provider.php
index 54db6ae48a2a7..d52d7e8230510 100644
--- a/administrator/components/com_redirect/services/provider.php
+++ b/administrator/components/com_redirect/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Redirect'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Redirect'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Redirect'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Redirect'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new RedirectComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new RedirectComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_redirect/src/Controller/DisplayController.php b/administrator/components/com_redirect/src/Controller/DisplayController.php
index 541446f8dd621..430d595386a0e 100644
--- a/administrator/components/com_redirect/src/Controller/DisplayController.php
+++ b/administrator/components/com_redirect/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'links');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached.
+ * @param mixed $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $view = $this->input->get('view', 'links');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
- if ($view === 'links')
- {
- $pluginEnabled = PluginHelper::isEnabled('system', 'redirect');
- $collectUrlsEnabled = RedirectHelper::collectUrlsEnabled();
+ if ($view === 'links') {
+ $pluginEnabled = PluginHelper::isEnabled('system', 'redirect');
+ $collectUrlsEnabled = RedirectHelper::collectUrlsEnabled();
- // Show messages about the enabled plugin and if the plugin should collect URLs
- if ($pluginEnabled && $collectUrlsEnabled)
- {
- $this->app->enqueueMessage(Text::sprintf('COM_REDIRECT_COLLECT_URLS_ENABLED', Text::_('COM_REDIRECT_PLUGIN_ENABLED')), 'notice');
- }
- else
- {
- $redirectPluginId = RedirectHelper::getRedirectPluginId();
- $link = HTMLHelper::_(
- 'link',
- '#plugin' . $redirectPluginId . 'Modal',
- Text::_('COM_REDIRECT_SYSTEM_PLUGIN'),
- 'class="alert-link" data-bs-toggle="modal" id="title-' . $redirectPluginId . '"'
- );
+ // Show messages about the enabled plugin and if the plugin should collect URLs
+ if ($pluginEnabled && $collectUrlsEnabled) {
+ $this->app->enqueueMessage(Text::sprintf('COM_REDIRECT_COLLECT_URLS_ENABLED', Text::_('COM_REDIRECT_PLUGIN_ENABLED')), 'notice');
+ } else {
+ $redirectPluginId = RedirectHelper::getRedirectPluginId();
+ $link = HTMLHelper::_(
+ 'link',
+ '#plugin' . $redirectPluginId . 'Modal',
+ Text::_('COM_REDIRECT_SYSTEM_PLUGIN'),
+ 'class="alert-link" data-bs-toggle="modal" id="title-' . $redirectPluginId . '"'
+ );
- if ($pluginEnabled && !$collectUrlsEnabled)
- {
- $this->app->enqueueMessage(
- Text::sprintf('COM_REDIRECT_COLLECT_MODAL_URLS_DISABLED', Text::_('COM_REDIRECT_PLUGIN_ENABLED'), $link),
- 'notice'
- );
- }
- else
- {
- $this->app->enqueueMessage(Text::sprintf('COM_REDIRECT_PLUGIN_MODAL_DISABLED', $link), 'error');
- }
- }
- }
+ if ($pluginEnabled && !$collectUrlsEnabled) {
+ $this->app->enqueueMessage(
+ Text::sprintf('COM_REDIRECT_COLLECT_MODAL_URLS_DISABLED', Text::_('COM_REDIRECT_PLUGIN_ENABLED'), $link),
+ 'notice'
+ );
+ } else {
+ $this->app->enqueueMessage(Text::sprintf('COM_REDIRECT_PLUGIN_MODAL_DISABLED', $link), 'error');
+ }
+ }
+ }
- // Check for edit form.
- if ($view == 'link' && $layout == 'edit' && !$this->checkEditId('com_redirect.edit.link', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($view == 'link' && $layout == 'edit' && !$this->checkEditId('com_redirect.edit.link', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_redirect&view=links', false));
+ $this->setRedirect(Route::_('index.php?option=com_redirect&view=links', false));
- return false;
- }
+ return false;
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_redirect/src/Controller/LinkController.php b/administrator/components/com_redirect/src/Controller/LinkController.php
index e6b38f58b2e46..943ff69a495ac 100644
--- a/administrator/components/com_redirect/src/Controller/LinkController.php
+++ b/administrator/components/com_redirect/src/Controller/LinkController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Redirect\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Redirect\Administrator\Controller;
use Joomla\CMS\MVC\Controller\FormController;
@@ -19,5 +19,5 @@
*/
class LinkController extends FormController
{
- // Parent class access checks are sufficient for this controller.
+ // Parent class access checks are sufficient for this controller.
}
diff --git a/administrator/components/com_redirect/src/Controller/LinksController.php b/administrator/components/com_redirect/src/Controller/LinksController.php
index f18202fe86e99..aebc413a5c2b5 100644
--- a/administrator/components/com_redirect/src/Controller/LinksController.php
+++ b/administrator/components/com_redirect/src/Controller/LinksController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('COM_REDIRECT_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- $newUrl = $this->input->getString('new_url');
- $comment = $this->input->getString('comment');
-
- // Get the model.
- $model = $this->getModel();
-
- // Remove the items.
- if (!$model->activate($ids, $newUrl, $comment))
- {
- $this->app->enqueueMessage($model->getError(), 'warning');
- }
- else
- {
- $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_UPDATED', count($ids)));
- }
- }
-
- $this->setRedirect('index.php?option=com_redirect&view=links');
- }
-
- /**
- * Method to duplicate URLs in records.
- *
- * @return void
- *
- * @since 3.6.0
- */
- public function duplicateUrls()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (empty($ids))
- {
- $this->app->enqueueMessage(Text::_('COM_REDIRECT_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- $newUrl = $this->input->getString('new_url');
- $comment = $this->input->getString('comment');
-
- // Get the model.
- $model = $this->getModel();
-
- // Remove the items.
- if (!$model->duplicateUrls($ids, $newUrl, $comment))
- {
- $this->app->enqueueMessage($model->getError(), 'warning');
- }
- else
- {
- $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_UPDATED', count($ids)));
- }
- }
-
- $this->setRedirect('index.php?option=com_redirect&view=links');
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The name of the model.
- * @param string $prefix The prefix of the model.
- * @param array $config An array of settings.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model instance
- *
- * @since 1.6
- */
- public function getModel($name = 'Link', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Executes the batch process to add URLs to the database
- *
- * @return void
- */
- public function batch()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $batch_urls_request = $this->input->post->get('batch_urls', array(), 'array');
- $batch_urls_lines = array_map('trim', explode("\n", $batch_urls_request[0]));
-
- $batch_urls = array();
-
- foreach ($batch_urls_lines as $batch_urls_line)
- {
- if (!empty($batch_urls_line))
- {
- $params = ComponentHelper::getParams('com_redirect');
- $separator = $params->get('separator', '|');
-
- // Basic check to make sure the correct separator is being used
- if (!\Joomla\String\StringHelper::strpos($batch_urls_line, $separator))
- {
- $this->setMessage(Text::sprintf('COM_REDIRECT_NO_SEPARATOR_FOUND', $separator), 'error');
- $this->setRedirect('index.php?option=com_redirect&view=links');
-
- return;
- }
-
- $batch_urls[] = array_map('trim', explode($separator, $batch_urls_line));
- }
- }
-
- // Set default message on error - overwrite if successful
- $this->setMessage(Text::_('COM_REDIRECT_NO_ITEM_ADDED'), 'error');
-
- if (!empty($batch_urls))
- {
- $model = $this->getModel('Links');
-
- // Execute the batch process
- if ($model->batchProcess($batch_urls))
- {
- $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_ADDED', count($batch_urls)));
- }
- }
-
- $this->setRedirect('index.php?option=com_redirect&view=links');
- }
-
- /**
- * Clean out the unpublished links.
- *
- * @return void
- *
- * @since 3.5
- */
- public function purge()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $model = $this->getModel('Links');
-
- if ($model->purge())
- {
- $message = Text::_('COM_REDIRECT_CLEAR_SUCCESS');
- }
- else
- {
- $message = Text::_('COM_REDIRECT_CLEAR_FAIL');
- }
-
- $this->setRedirect('index.php?option=com_redirect&view=links', $message);
- }
+ /**
+ * Method to update a record.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function activate()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('COM_REDIRECT_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ $newUrl = $this->input->getString('new_url');
+ $comment = $this->input->getString('comment');
+
+ // Get the model.
+ $model = $this->getModel();
+
+ // Remove the items.
+ if (!$model->activate($ids, $newUrl, $comment)) {
+ $this->app->enqueueMessage($model->getError(), 'warning');
+ } else {
+ $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_UPDATED', count($ids)));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_redirect&view=links');
+ }
+
+ /**
+ * Method to duplicate URLs in records.
+ *
+ * @return void
+ *
+ * @since 3.6.0
+ */
+ public function duplicateUrls()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (empty($ids)) {
+ $this->app->enqueueMessage(Text::_('COM_REDIRECT_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ $newUrl = $this->input->getString('new_url');
+ $comment = $this->input->getString('comment');
+
+ // Get the model.
+ $model = $this->getModel();
+
+ // Remove the items.
+ if (!$model->duplicateUrls($ids, $newUrl, $comment)) {
+ $this->app->enqueueMessage($model->getError(), 'warning');
+ } else {
+ $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_UPDATED', count($ids)));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_redirect&view=links');
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The name of the model.
+ * @param string $prefix The prefix of the model.
+ * @param array $config An array of settings.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model instance
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Link', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Executes the batch process to add URLs to the database
+ *
+ * @return void
+ */
+ public function batch()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $batch_urls_request = $this->input->post->get('batch_urls', array(), 'array');
+ $batch_urls_lines = array_map('trim', explode("\n", $batch_urls_request[0]));
+
+ $batch_urls = array();
+
+ foreach ($batch_urls_lines as $batch_urls_line) {
+ if (!empty($batch_urls_line)) {
+ $params = ComponentHelper::getParams('com_redirect');
+ $separator = $params->get('separator', '|');
+
+ // Basic check to make sure the correct separator is being used
+ if (!\Joomla\String\StringHelper::strpos($batch_urls_line, $separator)) {
+ $this->setMessage(Text::sprintf('COM_REDIRECT_NO_SEPARATOR_FOUND', $separator), 'error');
+ $this->setRedirect('index.php?option=com_redirect&view=links');
+
+ return;
+ }
+
+ $batch_urls[] = array_map('trim', explode($separator, $batch_urls_line));
+ }
+ }
+
+ // Set default message on error - overwrite if successful
+ $this->setMessage(Text::_('COM_REDIRECT_NO_ITEM_ADDED'), 'error');
+
+ if (!empty($batch_urls)) {
+ $model = $this->getModel('Links');
+
+ // Execute the batch process
+ if ($model->batchProcess($batch_urls)) {
+ $this->setMessage(Text::plural('COM_REDIRECT_N_LINKS_ADDED', count($batch_urls)));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_redirect&view=links');
+ }
+
+ /**
+ * Clean out the unpublished links.
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public function purge()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $model = $this->getModel('Links');
+
+ if ($model->purge()) {
+ $message = Text::_('COM_REDIRECT_CLEAR_SUCCESS');
+ } else {
+ $message = Text::_('COM_REDIRECT_CLEAR_FAIL');
+ }
+
+ $this->setRedirect('index.php?option=com_redirect&view=links', $message);
+ }
}
diff --git a/administrator/components/com_redirect/src/Extension/RedirectComponent.php b/administrator/components/com_redirect/src/Extension/RedirectComponent.php
index 2f155999b68b6..3bf9ddfa191ce 100644
--- a/administrator/components/com_redirect/src/Extension/RedirectComponent.php
+++ b/administrator/components/com_redirect/src/Extension/RedirectComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('redirect', new Redirect);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('redirect', new Redirect());
+ }
}
diff --git a/administrator/components/com_redirect/src/Field/RedirectField.php b/administrator/components/com_redirect/src/Field/RedirectField.php
index 719c7b3c79c2a..aaf2f20b4e78e 100644
--- a/administrator/components/com_redirect/src/Field/RedirectField.php
+++ b/administrator/components/com_redirect/src/Field/RedirectField.php
@@ -1,4 +1,5 @@
'HTTP/1.1 100 Continue',
- 101 => 'HTTP/1.1 101 Switching Protocols',
- 102 => 'HTTP/1.1 102 Processing',
- 103 => 'HTTP/1.1 103 Early Hints',
- 200 => 'HTTP/1.1 200 OK',
- 201 => 'HTTP/1.1 201 Created',
- 202 => 'HTTP/1.1 202 Accepted',
- 203 => 'HTTP/1.1 203 Non-Authoritative Information',
- 204 => 'HTTP/1.1 204 No Content',
- 205 => 'HTTP/1.1 205 Reset Content',
- 206 => 'HTTP/1.1 206 Partial Content',
- 207 => 'HTTP/1.1 207 Multi-Status',
- 208 => 'HTTP/1.1 208 Already Reported',
- 226 => 'HTTP/1.1 226 IM Used',
- 300 => 'HTTP/1.1 300 Multiple Choices',
- 301 => 'HTTP/1.1 301 Moved Permanently',
- 302 => 'HTTP/1.1 302 Found',
- 303 => 'HTTP/1.1 303 See other',
- 304 => 'HTTP/1.1 304 Not Modified',
- 305 => 'HTTP/1.1 305 Use Proxy',
- 306 => 'HTTP/1.1 306 (Unused)',
- 307 => 'HTTP/1.1 307 Temporary Redirect',
- 308 => 'HTTP/1.1 308 Permanent Redirect',
- 400 => 'HTTP/1.1 400 Bad Request',
- 401 => 'HTTP/1.1 401 Unauthorized',
- 402 => 'HTTP/1.1 402 Payment Required',
- 403 => 'HTTP/1.1 403 Forbidden',
- 404 => 'HTTP/1.1 404 Not Found',
- 405 => 'HTTP/1.1 405 Method Not Allowed',
- 406 => 'HTTP/1.1 406 Not Acceptable',
- 407 => 'HTTP/1.1 407 Proxy Authentication Required',
- 408 => 'HTTP/1.1 408 Request Timeout',
- 409 => 'HTTP/1.1 409 Conflict',
- 410 => 'HTTP/1.1 410 Gone',
- 411 => 'HTTP/1.1 411 Length Required',
- 412 => 'HTTP/1.1 412 Precondition Failed',
- 413 => 'HTTP/1.1 413 Payload Too Large',
- 414 => 'HTTP/1.1 414 URI Too Long',
- 415 => 'HTTP/1.1 415 Unsupported Media Type',
- 416 => 'HTTP/1.1 416 Requested Range Not Satisfiable',
- 417 => 'HTTP/1.1 417 Expectation Failed',
- 418 => 'HTTP/1.1 418 I\'m a teapot',
- 421 => 'HTTP/1.1 421 Misdirected Request',
- 422 => 'HTTP/1.1 422 Unprocessable Entity',
- 423 => 'HTTP/1.1 423 Locked',
- 424 => 'HTTP/1.1 424 Failed Dependency',
- 425 => 'HTTP/1.1 425 Reserved for WebDAV advanced collections expired proposal',
- 426 => 'HTTP/1.1 426 Upgrade Required',
- 428 => 'HTTP/1.1 428 Precondition Required',
- 429 => 'HTTP/1.1 429 Too Many Requests',
- 431 => 'HTTP/1.1 431 Request Header Fields Too Large',
- 451 => 'HTTP/1.1 451 Unavailable For Legal Reasons',
- 500 => 'HTTP/1.1 500 Internal Server Error',
- 501 => 'HTTP/1.1 501 Not Implemented',
- 502 => 'HTTP/1.1 502 Bad Gateway',
- 503 => 'HTTP/1.1 503 Service Unavailable',
- 504 => 'HTTP/1.1 504 Gateway Timeout',
- 505 => 'HTTP/1.1 505 HTTP Version Not Supported',
- 506 => 'HTTP/1.1 506 Variant Also Negotiates (Experimental)',
- 507 => 'HTTP/1.1 507 Insufficient Storage',
- 508 => 'HTTP/1.1 508 Loop Detected',
- 510 => 'HTTP/1.1 510 Not Extended',
- 511 => 'HTTP/1.1 511 Network Authentication Required',
- );
+ /**
+ * A map of integer HTTP 1.1 response codes to the full HTTP Status for the headers.
+ *
+ * @var object
+ * @since 3.4
+ * @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ */
+ protected $responseMap = array(
+ 100 => 'HTTP/1.1 100 Continue',
+ 101 => 'HTTP/1.1 101 Switching Protocols',
+ 102 => 'HTTP/1.1 102 Processing',
+ 103 => 'HTTP/1.1 103 Early Hints',
+ 200 => 'HTTP/1.1 200 OK',
+ 201 => 'HTTP/1.1 201 Created',
+ 202 => 'HTTP/1.1 202 Accepted',
+ 203 => 'HTTP/1.1 203 Non-Authoritative Information',
+ 204 => 'HTTP/1.1 204 No Content',
+ 205 => 'HTTP/1.1 205 Reset Content',
+ 206 => 'HTTP/1.1 206 Partial Content',
+ 207 => 'HTTP/1.1 207 Multi-Status',
+ 208 => 'HTTP/1.1 208 Already Reported',
+ 226 => 'HTTP/1.1 226 IM Used',
+ 300 => 'HTTP/1.1 300 Multiple Choices',
+ 301 => 'HTTP/1.1 301 Moved Permanently',
+ 302 => 'HTTP/1.1 302 Found',
+ 303 => 'HTTP/1.1 303 See other',
+ 304 => 'HTTP/1.1 304 Not Modified',
+ 305 => 'HTTP/1.1 305 Use Proxy',
+ 306 => 'HTTP/1.1 306 (Unused)',
+ 307 => 'HTTP/1.1 307 Temporary Redirect',
+ 308 => 'HTTP/1.1 308 Permanent Redirect',
+ 400 => 'HTTP/1.1 400 Bad Request',
+ 401 => 'HTTP/1.1 401 Unauthorized',
+ 402 => 'HTTP/1.1 402 Payment Required',
+ 403 => 'HTTP/1.1 403 Forbidden',
+ 404 => 'HTTP/1.1 404 Not Found',
+ 405 => 'HTTP/1.1 405 Method Not Allowed',
+ 406 => 'HTTP/1.1 406 Not Acceptable',
+ 407 => 'HTTP/1.1 407 Proxy Authentication Required',
+ 408 => 'HTTP/1.1 408 Request Timeout',
+ 409 => 'HTTP/1.1 409 Conflict',
+ 410 => 'HTTP/1.1 410 Gone',
+ 411 => 'HTTP/1.1 411 Length Required',
+ 412 => 'HTTP/1.1 412 Precondition Failed',
+ 413 => 'HTTP/1.1 413 Payload Too Large',
+ 414 => 'HTTP/1.1 414 URI Too Long',
+ 415 => 'HTTP/1.1 415 Unsupported Media Type',
+ 416 => 'HTTP/1.1 416 Requested Range Not Satisfiable',
+ 417 => 'HTTP/1.1 417 Expectation Failed',
+ 418 => 'HTTP/1.1 418 I\'m a teapot',
+ 421 => 'HTTP/1.1 421 Misdirected Request',
+ 422 => 'HTTP/1.1 422 Unprocessable Entity',
+ 423 => 'HTTP/1.1 423 Locked',
+ 424 => 'HTTP/1.1 424 Failed Dependency',
+ 425 => 'HTTP/1.1 425 Reserved for WebDAV advanced collections expired proposal',
+ 426 => 'HTTP/1.1 426 Upgrade Required',
+ 428 => 'HTTP/1.1 428 Precondition Required',
+ 429 => 'HTTP/1.1 429 Too Many Requests',
+ 431 => 'HTTP/1.1 431 Request Header Fields Too Large',
+ 451 => 'HTTP/1.1 451 Unavailable For Legal Reasons',
+ 500 => 'HTTP/1.1 500 Internal Server Error',
+ 501 => 'HTTP/1.1 501 Not Implemented',
+ 502 => 'HTTP/1.1 502 Bad Gateway',
+ 503 => 'HTTP/1.1 503 Service Unavailable',
+ 504 => 'HTTP/1.1 504 Gateway Timeout',
+ 505 => 'HTTP/1.1 505 HTTP Version Not Supported',
+ 506 => 'HTTP/1.1 506 Variant Also Negotiates (Experimental)',
+ 507 => 'HTTP/1.1 507 Insufficient Storage',
+ 508 => 'HTTP/1.1 508 Loop Detected',
+ 510 => 'HTTP/1.1 510 Not Extended',
+ 511 => 'HTTP/1.1 511 Network Authentication Required',
+ );
- /**
- * Method to get the field input markup.
- *
- * @return array The field input markup.
- *
- * @since 3.4
- */
- protected function getOptions()
- {
- $options = array();
+ /**
+ * Method to get the field input markup.
+ *
+ * @return array The field input markup.
+ *
+ * @since 3.4
+ */
+ protected function getOptions()
+ {
+ $options = array();
- foreach ($this->responseMap as $key => $value)
- {
- $options[] = HTMLHelper::_('select.option', $key, $value);
- }
+ foreach ($this->responseMap as $key => $value) {
+ $options[] = HTMLHelper::_('select.option', $key, $value);
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_redirect/src/Helper/RedirectHelper.php b/administrator/components/com_redirect/src/Helper/RedirectHelper.php
index f09310abd0b4d..a7f08454778bc 100644
--- a/administrator/components/com_redirect/src/Helper/RedirectHelper.php
+++ b/administrator/components/com_redirect/src/Helper/RedirectHelper.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
- ->where($db->quoteName('element') . ' = ' . $db->quote('redirect'));
- $db->setQuery($query);
+ /**
+ * Gets the redirect system plugin extension id.
+ *
+ * @return integer The redirect system plugin extension id.
+ *
+ * @since 3.6.0
+ */
+ public static function getRedirectPluginId()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
+ ->where($db->quoteName('element') . ' = ' . $db->quote('redirect'));
+ $db->setQuery($query);
- try
- {
- $result = (int) $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
+ try {
+ $result = (int) $db->loadResult();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
- return $result;
- }
+ return $result;
+ }
- /**
- * Checks whether the option "Collect URLs" is enabled for the output message
- *
- * @return boolean
- *
- * @since 3.4
- */
- public static function collectUrlsEnabled()
- {
- $collect_urls = false;
+ /**
+ * Checks whether the option "Collect URLs" is enabled for the output message
+ *
+ * @return boolean
+ *
+ * @since 3.4
+ */
+ public static function collectUrlsEnabled()
+ {
+ $collect_urls = false;
- if (PluginHelper::isEnabled('system', 'redirect'))
- {
- $params = new Registry(PluginHelper::getPlugin('system', 'redirect')->params);
- $collect_urls = (bool) $params->get('collect_urls', 1);
- }
+ if (PluginHelper::isEnabled('system', 'redirect')) {
+ $params = new Registry(PluginHelper::getPlugin('system', 'redirect')->params);
+ $collect_urls = (bool) $params->get('collect_urls', 1);
+ }
- return $collect_urls;
- }
+ return $collect_urls;
+ }
}
diff --git a/administrator/components/com_redirect/src/Model/LinkModel.php b/administrator/components/com_redirect/src/Model/LinkModel.php
index 7eff8659f7aa6..62c3d5531d80e 100644
--- a/administrator/components/com_redirect/src/Model/LinkModel.php
+++ b/administrator/components/com_redirect/src/Model/LinkModel.php
@@ -1,4 +1,5 @@
published != -2)
- {
- return false;
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form A JForm object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_redirect.link', 'link', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if ($this->canEditState((object) $data) != true)
- {
- // Disable fields for display.
- $form->setFieldAttribute('published', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- // If in advanced mode then we make sure the new URL field is not compulsory and the header
- // field compulsory in case people select non-3xx redirects
- if (ComponentHelper::getParams('com_redirect')->get('mode', 0) == true)
- {
- $form->setFieldAttribute('new_url', 'required', 'false');
- $form->setFieldAttribute('header', 'required', 'true');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_redirect.edit.link.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_redirect.link', $data);
-
- return $data;
- }
-
- /**
- * Method to activate links.
- *
- * @param array &$pks An array of link ids.
- * @param string $url The new URL to set for the redirect.
- * @param string $comment A comment for the redirect links.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- */
- public function activate(&$pks, $url, $comment = null)
- {
- $user = Factory::getUser();
- $db = $this->getDatabase();
-
- // Sanitize the ids.
- $pks = (array) $pks;
- $pks = ArrayHelper::toInteger($pks);
-
- // Populate default comment if necessary.
- $comment = (!empty($comment)) ? $comment : Text::sprintf('COM_REDIRECT_REDIRECTED_ON', HTMLHelper::_('date', time()));
-
- // Access checks.
- if (!$user->authorise('core.edit', 'com_redirect'))
- {
- $pks = array();
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
-
- return false;
- }
-
- if (!empty($pks))
- {
- // Update the link rows.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__redirect_links'))
- ->set($db->quoteName('new_url') . ' = :url')
- ->set($db->quoteName('published') . ' = 1')
- ->set($db->quoteName('comment') . ' = :comment')
- ->whereIn($db->quoteName('id'), $pks)
- ->bind(':url', $url)
- ->bind(':comment', $comment);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Method to batch update URLs to have new redirect urls and comments. Note will publish any unpublished URLs.
- *
- * @param array &$pks An array of link ids.
- * @param string $url The new URL to set for the redirect.
- * @param string $comment A comment for the redirect links.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 3.6.0
- */
- public function duplicateUrls(&$pks, $url, $comment = null)
- {
- $user = Factory::getUser();
- $db = $this->getDatabase();
-
- // Sanitize the ids.
- $pks = (array) $pks;
- $pks = ArrayHelper::toInteger($pks);
-
- // Access checks.
- if (!$user->authorise('core.edit', 'com_redirect'))
- {
- $pks = array();
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
-
- return false;
- }
-
- if (!empty($pks))
- {
- $date = Factory::getDate()->toSql();
-
- // Update the link rows.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__redirect_links'))
- ->set($db->quoteName('new_url') . ' = :url')
- ->set($db->quoteName('modified_date') . ' = :date')
- ->set($db->quoteName('published') . ' = 1')
- ->whereIn($db->quoteName('id'), $pks)
- ->bind(':url', $url)
- ->bind(':date', $date);
-
- if (!empty($comment))
- {
- $query->set($db->quoteName('comment') . ' = ' . $db->quote($comment));
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- return true;
- }
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_REDIRECT';
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ if ($record->published != -2) {
+ return false;
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form A JForm object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_redirect.link', 'link', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if ($this->canEditState((object) $data) != true) {
+ // Disable fields for display.
+ $form->setFieldAttribute('published', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ // If in advanced mode then we make sure the new URL field is not compulsory and the header
+ // field compulsory in case people select non-3xx redirects
+ if (ComponentHelper::getParams('com_redirect')->get('mode', 0) == true) {
+ $form->setFieldAttribute('new_url', 'required', 'false');
+ $form->setFieldAttribute('header', 'required', 'true');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_redirect.edit.link.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_redirect.link', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to activate links.
+ *
+ * @param array &$pks An array of link ids.
+ * @param string $url The new URL to set for the redirect.
+ * @param string $comment A comment for the redirect links.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function activate(&$pks, $url, $comment = null)
+ {
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+
+ // Sanitize the ids.
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+
+ // Populate default comment if necessary.
+ $comment = (!empty($comment)) ? $comment : Text::sprintf('COM_REDIRECT_REDIRECTED_ON', HTMLHelper::_('date', time()));
+
+ // Access checks.
+ if (!$user->authorise('core.edit', 'com_redirect')) {
+ $pks = array();
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
+
+ return false;
+ }
+
+ if (!empty($pks)) {
+ // Update the link rows.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__redirect_links'))
+ ->set($db->quoteName('new_url') . ' = :url')
+ ->set($db->quoteName('published') . ' = 1')
+ ->set($db->quoteName('comment') . ' = :comment')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->bind(':url', $url)
+ ->bind(':comment', $comment);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to batch update URLs to have new redirect urls and comments. Note will publish any unpublished URLs.
+ *
+ * @param array &$pks An array of link ids.
+ * @param string $url The new URL to set for the redirect.
+ * @param string $comment A comment for the redirect links.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 3.6.0
+ */
+ public function duplicateUrls(&$pks, $url, $comment = null)
+ {
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+
+ // Sanitize the ids.
+ $pks = (array) $pks;
+ $pks = ArrayHelper::toInteger($pks);
+
+ // Access checks.
+ if (!$user->authorise('core.edit', 'com_redirect')) {
+ $pks = array();
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
+
+ return false;
+ }
+
+ if (!empty($pks)) {
+ $date = Factory::getDate()->toSql();
+
+ // Update the link rows.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__redirect_links'))
+ ->set($db->quoteName('new_url') . ' = :url')
+ ->set($db->quoteName('modified_date') . ' = :date')
+ ->set($db->quoteName('published') . ' = 1')
+ ->whereIn($db->quoteName('id'), $pks)
+ ->bind(':url', $url)
+ ->bind(':date', $date);
+
+ if (!empty($comment)) {
+ $query->set($db->quoteName('comment') . ' = ' . $db->quote($comment));
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_redirect/src/Model/LinksModel.php b/administrator/components/com_redirect/src/Model/LinksModel.php
index de012229f9b7d..eaa2b1a392b2f 100644
--- a/administrator/components/com_redirect/src/Model/LinksModel.php
+++ b/administrator/components/com_redirect/src/Model/LinksModel.php
@@ -1,4 +1,5 @@
getDatabase();
-
- $query = $db->getQuery(true);
-
- $query->delete('#__redirect_links')->where($db->quoteName('published') . '= 0');
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\Exception $e)
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.old_url', $direction = 'asc')
- {
- // Load the parameters.
- $params = ComponentHelper::getParams('com_redirect');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.http_status');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.*'
- )
- );
- $query->from($db->quoteName('#__redirect_links', 'a'));
-
- // Filter by published state
- $state = (string) $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $state = (int) $state;
- $query->where($db->quoteName('a.published') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- elseif ($state === '')
- {
- $query->whereIn($db->quoteName('a.published'), [0,1]);
- }
-
- // Filter the items over the HTTP status code header.
- if ($httpStatusCode = $this->getState('filter.http_status'))
- {
- $httpStatusCode = (int) $httpStatusCode;
- $query->where($db->quoteName('a.header') . ' = :header')
- ->bind(':header', $httpStatusCode, ParameterType::INTEGER);
- }
-
- // Filter the items over the search string if set.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%');
- $query->where(
- '(' . $db->quoteName('old_url') . ' LIKE :oldurl'
- . ' OR ' . $db->quoteName('new_url') . ' LIKE :newurl'
- . ' OR ' . $db->quoteName('comment') . ' LIKE :comment'
- . ' OR ' . $db->quoteName('referer') . ' LIKE :referer)'
- )
- ->bind(':oldurl', $search)
- ->bind(':newurl', $search)
- ->bind(':comment', $search)
- ->bind(':referer', $search);
- }
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.old_url')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Add the entered URLs into the database
- *
- * @param array $batchUrls Array of URLs to enter into the database
- *
- * @return boolean
- */
- public function batchProcess($batchUrls)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $params = ComponentHelper::getParams('com_redirect');
- $state = (int) $params->get('defaultImportState', 0);
- $created = Factory::getDate()->toSql();
-
- $columns = [
- 'old_url',
- 'new_url',
- 'referer',
- 'comment',
- 'hits',
- 'published',
- 'created_date',
- 'modified_date',
- ];
-
- $values = [
- ':oldurl',
- ':newurl',
- $db->quote(''),
- $db->quote(''),
- 0,
- ':state',
- ':created',
- ':modified',
- ];
-
- $query
- ->insert($db->quoteName('#__redirect_links'), false)
- ->columns($db->quoteName($columns))
- ->values(implode(', ', $values))
- ->bind(':oldurl', $old_url)
- ->bind(':newurl', $new_url)
- ->bind(':state', $state, ParameterType::INTEGER)
- ->bind(':created', $created)
- ->bind(':modified', $created);
-
- $db->setQuery($query);
-
- foreach ($batchUrls as $batch_url)
- {
- $old_url = $batch_url[0];
-
- // Destination URL can also be an external URL
- if (!empty($batch_url[1]))
- {
- $new_url = $batch_url[1];
- }
- else
- {
- $new_url = '';
- }
-
- $db->execute();
- }
-
- return true;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'state', 'a.state',
+ 'old_url', 'a.old_url',
+ 'new_url', 'a.new_url',
+ 'referer', 'a.referer',
+ 'hits', 'a.hits',
+ 'created_date', 'a.created_date',
+ 'published', 'a.published',
+ 'header', 'a.header', 'http_status',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+ /**
+ * Removes all of the unpublished redirects from the table.
+ *
+ * @return boolean result of operation
+ *
+ * @since 3.5
+ */
+ public function purge()
+ {
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true);
+
+ $query->delete('#__redirect_links')->where($db->quoteName('published') . '= 0');
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.old_url', $direction = 'asc')
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_redirect');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.http_status');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.*'
+ )
+ );
+ $query->from($db->quoteName('#__redirect_links', 'a'));
+
+ // Filter by published state
+ $state = (string) $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+ $query->where($db->quoteName('a.published') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } elseif ($state === '') {
+ $query->whereIn($db->quoteName('a.published'), [0,1]);
+ }
+
+ // Filter the items over the HTTP status code header.
+ if ($httpStatusCode = $this->getState('filter.http_status')) {
+ $httpStatusCode = (int) $httpStatusCode;
+ $query->where($db->quoteName('a.header') . ' = :header')
+ ->bind(':header', $httpStatusCode, ParameterType::INTEGER);
+ }
+
+ // Filter the items over the search string if set.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%');
+ $query->where(
+ '(' . $db->quoteName('old_url') . ' LIKE :oldurl'
+ . ' OR ' . $db->quoteName('new_url') . ' LIKE :newurl'
+ . ' OR ' . $db->quoteName('comment') . ' LIKE :comment'
+ . ' OR ' . $db->quoteName('referer') . ' LIKE :referer)'
+ )
+ ->bind(':oldurl', $search)
+ ->bind(':newurl', $search)
+ ->bind(':comment', $search)
+ ->bind(':referer', $search);
+ }
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.old_url')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Add the entered URLs into the database
+ *
+ * @param array $batchUrls Array of URLs to enter into the database
+ *
+ * @return boolean
+ */
+ public function batchProcess($batchUrls)
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $params = ComponentHelper::getParams('com_redirect');
+ $state = (int) $params->get('defaultImportState', 0);
+ $created = Factory::getDate()->toSql();
+
+ $columns = [
+ 'old_url',
+ 'new_url',
+ 'referer',
+ 'comment',
+ 'hits',
+ 'published',
+ 'created_date',
+ 'modified_date',
+ ];
+
+ $values = [
+ ':oldurl',
+ ':newurl',
+ $db->quote(''),
+ $db->quote(''),
+ 0,
+ ':state',
+ ':created',
+ ':modified',
+ ];
+
+ $query
+ ->insert($db->quoteName('#__redirect_links'), false)
+ ->columns($db->quoteName($columns))
+ ->values(implode(', ', $values))
+ ->bind(':oldurl', $old_url)
+ ->bind(':newurl', $new_url)
+ ->bind(':state', $state, ParameterType::INTEGER)
+ ->bind(':created', $created)
+ ->bind(':modified', $created);
+
+ $db->setQuery($query);
+
+ foreach ($batchUrls as $batch_url) {
+ $old_url = $batch_url[0];
+
+ // Destination URL can also be an external URL
+ if (!empty($batch_url[1])) {
+ $new_url = $batch_url[1];
+ } else {
+ $new_url = '';
+ }
+
+ $db->execute();
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_redirect/src/Service/HTML/Redirect.php b/administrator/components/com_redirect/src/Service/HTML/Redirect.php
index c7dfcb0da4690..acb733cca42ca 100644
--- a/administrator/components/com_redirect/src/Service/HTML/Redirect.php
+++ b/administrator/components/com_redirect/src/Service/HTML/Redirect.php
@@ -1,4 +1,5 @@
array('publish', 'links.unpublish', 'JENABLED', 'COM_REDIRECT_DISABLE_LINK'),
- 0 => array('unpublish', 'links.publish', 'JDISABLED', 'COM_REDIRECT_ENABLE_LINK'),
- 2 => array('archive', 'links.unpublish', 'JARCHIVED', 'JUNARCHIVE'),
- -2 => array('trash', 'links.publish', 'JTRASHED', 'COM_REDIRECT_ENABLE_LINK'),
- );
-
- $state = ArrayHelper::getValue($states, (int) $value, $states[0]);
- $icon = $state[0];
-
- if ($canChange)
- {
- $html = ' '
- . '' . Text::_($state[3]) . '
';
- }
-
- return $html;
- }
+ /**
+ * Display the published or unpublished state of an item.
+ *
+ * @param int $value The state value.
+ * @param int $i The ID of the item.
+ * @param boolean $canChange An optional prefix for the task.
+ *
+ * @return string
+ *
+ * @since 1.6
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function published($value = 0, $i = null, $canChange = true)
+ {
+ // Note: $i is required but has to be an optional argument in the function call due to argument order
+ if (null === $i) {
+ throw new \InvalidArgumentException('$i is a required argument in JHtmlRedirect::published');
+ }
+
+ // Array of image, task, title, action
+ $states = array(
+ 1 => array('publish', 'links.unpublish', 'JENABLED', 'COM_REDIRECT_DISABLE_LINK'),
+ 0 => array('unpublish', 'links.publish', 'JDISABLED', 'COM_REDIRECT_ENABLE_LINK'),
+ 2 => array('archive', 'links.unpublish', 'JARCHIVED', 'JUNARCHIVE'),
+ -2 => array('trash', 'links.publish', 'JTRASHED', 'COM_REDIRECT_ENABLE_LINK'),
+ );
+
+ $state = ArrayHelper::getValue($states, (int) $value, $states[0]);
+ $icon = $state[0];
+
+ if ($canChange) {
+ $html = ' '
+ . '' . Text::_($state[3]) . '
';
+ }
+
+ return $html;
+ }
}
diff --git a/administrator/components/com_redirect/src/Table/LinkTable.php b/administrator/components/com_redirect/src/Table/LinkTable.php
index fdc4ced7c4015..78bea988abf01 100644
--- a/administrator/components/com_redirect/src/Table/LinkTable.php
+++ b/administrator/components/com_redirect/src/Table/LinkTable.php
@@ -1,4 +1,5 @@
setError($e->getMessage());
-
- return false;
- }
-
- $this->old_url = trim(rawurldecode($this->old_url));
- $this->new_url = trim(rawurldecode($this->new_url));
-
- // Check for valid name.
- if (empty($this->old_url))
- {
- $this->setError(Text::_('COM_REDIRECT_ERROR_SOURCE_URL_REQUIRED'));
-
- return false;
- }
-
- // Check for NOT NULL.
- if (empty($this->referer))
- {
- $this->referer = '';
- }
-
- // Check for valid name if not in advanced mode.
- if (empty($this->new_url) && ComponentHelper::getParams('com_redirect')->get('mode', 0) == false)
- {
- $this->setError(Text::_('COM_REDIRECT_ERROR_DESTINATION_URL_REQUIRED'));
-
- return false;
- }
- elseif (empty($this->new_url) && ComponentHelper::getParams('com_redirect')->get('mode', 0) == true)
- {
- // Else if an empty URL and in redirect mode only throw the same error if the code is a 3xx status code
- if ($this->header < 400 && $this->header >= 300)
- {
- $this->setError(Text::_('COM_REDIRECT_ERROR_DESTINATION_URL_REQUIRED'));
-
- return false;
- }
- }
-
- // Check for duplicates
- if ($this->old_url == $this->new_url)
- {
- $this->setError(Text::_('COM_REDIRECT_ERROR_DUPLICATE_URLS'));
-
- return false;
- }
-
- $db = $this->getDbo();
-
- // Check for existing name
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->select($db->quoteName('old_url'))
- ->from($db->quoteName('#__redirect_links'))
- ->where($db->quoteName('old_url') . ' = :url')
- ->bind(':url', $this->old_url);
- $db->setQuery($query);
- $urls = $db->loadAssocList();
-
- foreach ($urls as $url)
- {
- if ($url['old_url'] === $this->old_url && (int) $url['id'] != (int) $this->id)
- {
- $this->setError(Text::_('COM_REDIRECT_ERROR_DUPLICATE_OLD_URL'));
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Overridden store method to set dates.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function store($updateNulls = false)
- {
- $date = Factory::getDate()->toSql();
-
- if (!$this->id)
- {
- // New record.
- $this->created_date = $date;
- $this->modified_date = $date;
- }
-
- if (empty($this->modified_date))
- {
- $this->modified_date = $this->created_date;
- }
-
- return parent::store($updateNulls);
- }
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database object.
+ *
+ * @since 1.6
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ parent::__construct('#__redirect_links', 'id', $db);
+ }
+
+ /**
+ * Overloaded check function
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $this->old_url = trim(rawurldecode($this->old_url));
+ $this->new_url = trim(rawurldecode($this->new_url));
+
+ // Check for valid name.
+ if (empty($this->old_url)) {
+ $this->setError(Text::_('COM_REDIRECT_ERROR_SOURCE_URL_REQUIRED'));
+
+ return false;
+ }
+
+ // Check for NOT NULL.
+ if (empty($this->referer)) {
+ $this->referer = '';
+ }
+
+ // Check for valid name if not in advanced mode.
+ if (empty($this->new_url) && ComponentHelper::getParams('com_redirect')->get('mode', 0) == false) {
+ $this->setError(Text::_('COM_REDIRECT_ERROR_DESTINATION_URL_REQUIRED'));
+
+ return false;
+ } elseif (empty($this->new_url) && ComponentHelper::getParams('com_redirect')->get('mode', 0) == true) {
+ // Else if an empty URL and in redirect mode only throw the same error if the code is a 3xx status code
+ if ($this->header < 400 && $this->header >= 300) {
+ $this->setError(Text::_('COM_REDIRECT_ERROR_DESTINATION_URL_REQUIRED'));
+
+ return false;
+ }
+ }
+
+ // Check for duplicates
+ if ($this->old_url == $this->new_url) {
+ $this->setError(Text::_('COM_REDIRECT_ERROR_DUPLICATE_URLS'));
+
+ return false;
+ }
+
+ $db = $this->getDbo();
+
+ // Check for existing name
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->select($db->quoteName('old_url'))
+ ->from($db->quoteName('#__redirect_links'))
+ ->where($db->quoteName('old_url') . ' = :url')
+ ->bind(':url', $this->old_url);
+ $db->setQuery($query);
+ $urls = $db->loadAssocList();
+
+ foreach ($urls as $url) {
+ if ($url['old_url'] === $this->old_url && (int) $url['id'] != (int) $this->id) {
+ $this->setError(Text::_('COM_REDIRECT_ERROR_DUPLICATE_OLD_URL'));
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Overridden store method to set dates.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function store($updateNulls = false)
+ {
+ $date = Factory::getDate()->toSql();
+
+ if (!$this->id) {
+ // New record.
+ $this->created_date = $date;
+ $this->modified_date = $date;
+ }
+
+ if (empty($this->modified_date)) {
+ $this->modified_date = $this->created_date;
+ }
+
+ return parent::store($updateNulls);
+ }
}
diff --git a/administrator/components/com_redirect/src/View/Link/HtmlView.php b/administrator/components/com_redirect/src/View/Link/HtmlView.php
index c20f83e059c50..927c5daeecd60 100644
--- a/administrator/components/com_redirect/src/View/Link/HtmlView.php
+++ b/administrator/components/com_redirect/src/View/Link/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $isNew = ($this->item->id == 0);
- $canDo = ContentHelper::getActions('com_redirect');
-
- ToolbarHelper::title($isNew ? Text::_('COM_REDIRECT_MANAGER_LINK_NEW') : Text::_('COM_REDIRECT_MANAGER_LINK_EDIT'), 'map-signs redirect');
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if ($canDo->get('core.edit'))
- {
- ToolbarHelper::apply('link.apply');
- $toolbarButtons[] = ['save', 'link.save'];
- }
-
- /**
- * This component does not support Save as Copy due to uniqueness checks.
- * While it can be done, it causes too much confusion if the user does
- * not change the Old URL.
- */
- if ($canDo->get('core.edit') && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'link.save2new'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('link.cancel');
- }
- else
- {
- ToolbarHelper::cancel('link.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::help('Redirects:_New_or_Edit');
- }
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return mixed False if unsuccessful, otherwise void.
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $isNew = ($this->item->id == 0);
+ $canDo = ContentHelper::getActions('com_redirect');
+
+ ToolbarHelper::title($isNew ? Text::_('COM_REDIRECT_MANAGER_LINK_NEW') : Text::_('COM_REDIRECT_MANAGER_LINK_EDIT'), 'map-signs redirect');
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if ($canDo->get('core.edit')) {
+ ToolbarHelper::apply('link.apply');
+ $toolbarButtons[] = ['save', 'link.save'];
+ }
+
+ /**
+ * This component does not support Save as Copy due to uniqueness checks.
+ * While it can be done, it causes too much confusion if the user does
+ * not change the Old URL.
+ */
+ if ($canDo->get('core.edit') && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'link.save2new'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('link.cancel');
+ } else {
+ ToolbarHelper::cancel('link.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::help('Redirects:_New_or_Edit');
+ }
}
diff --git a/administrator/components/com_redirect/src/View/Links/HtmlView.php b/administrator/components/com_redirect/src/View/Links/HtmlView.php
index f04a0b8c8f45d..15db461f3d682 100644
--- a/administrator/components/com_redirect/src/View/Links/HtmlView.php
+++ b/administrator/components/com_redirect/src/View/Links/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->params = ComponentHelper::getParams('com_redirect');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- if (!(PluginHelper::isEnabled('system', 'redirect') && RedirectHelper::collectUrlsEnabled()))
- {
- $this->redirectPluginId = RedirectHelper::getRedirectPluginId();
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $state = $this->get('State');
- $canDo = ContentHelper::getActions('com_redirect');
-
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_REDIRECT_MANAGER_LINKS'), 'map-signs redirect');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('link.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($state->get('filter.state') != 2)
- {
- $childBar->publish('links.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
- $childBar->unpublish('links.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
- }
-
- if ($state->get('filter.state') != -1)
- {
- if ($state->get('filter.state') != 2)
- {
- $childBar->archive('links.archive')->listCheck(true);
- }
- elseif ($state->get('filter.state') == 2)
- {
- $childBar->unarchive('links.unarchive')->listCheck(true);
- }
- }
-
- if (!$state->get('filter.state') == -2)
- {
- $childBar->trash('links.trash')->listCheck(true);
- }
- }
-
- if ($state->get('filter.state') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('links.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if (!$this->isEmptyState && (!$state->get('filter.state') == -2 && $canDo->get('core.delete')))
- {
- $toolbar->confirmButton('delete')
- ->text('COM_REDIRECT_TOOLBAR_PURGE')
- ->message('COM_REDIRECT_CONFIRM_PURGE')
- ->task('links.purge');
- }
-
- if ($canDo->get('core.create'))
- {
- $toolbar->popupButton('batch')
- ->text('JTOOLBAR_BULK_IMPORT')
- ->selector('collapseModal')
- ->listCheck(false);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_redirect');
- }
-
- $toolbar->help('Redirects:_Links');
- }
+ /**
+ * True if "System - Redirect Plugin" is enabled
+ *
+ * @var boolean
+ */
+ protected $enabled;
+
+ /**
+ * True if "Collect URLs" is enabled
+ *
+ * @var boolean
+ */
+ protected $collect_urls_enabled;
+
+ /**
+ * The id of the redirect plugin in mysql
+ *
+ * @var integer
+ * @since 3.8.0
+ */
+ protected $redirectPluginId = 0;
+
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\Registry\Registry
+ */
+ protected $params;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Display the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @throws GenericDataException
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ // Set variables
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->params = ComponentHelper::getParams('com_redirect');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ if (!(PluginHelper::isEnabled('system', 'redirect') && RedirectHelper::collectUrlsEnabled())) {
+ $this->redirectPluginId = RedirectHelper::getRedirectPluginId();
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $state = $this->get('State');
+ $canDo = ContentHelper::getActions('com_redirect');
+
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_REDIRECT_MANAGER_LINKS'), 'map-signs redirect');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('link.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($state->get('filter.state') != 2) {
+ $childBar->publish('links.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
+ $childBar->unpublish('links.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
+ }
+
+ if ($state->get('filter.state') != -1) {
+ if ($state->get('filter.state') != 2) {
+ $childBar->archive('links.archive')->listCheck(true);
+ } elseif ($state->get('filter.state') == 2) {
+ $childBar->unarchive('links.unarchive')->listCheck(true);
+ }
+ }
+
+ if (!$state->get('filter.state') == -2) {
+ $childBar->trash('links.trash')->listCheck(true);
+ }
+ }
+
+ if ($state->get('filter.state') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('links.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if (!$this->isEmptyState && (!$state->get('filter.state') == -2 && $canDo->get('core.delete'))) {
+ $toolbar->confirmButton('delete')
+ ->text('COM_REDIRECT_TOOLBAR_PURGE')
+ ->message('COM_REDIRECT_CONFIRM_PURGE')
+ ->task('links.purge');
+ }
+
+ if ($canDo->get('core.create')) {
+ $toolbar->popupButton('batch')
+ ->text('JTOOLBAR_BULK_IMPORT')
+ ->selector('collapseModal')
+ ->listCheck(false);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_redirect');
+ }
+
+ $toolbar->help('Redirects:_Links');
+ }
}
diff --git a/administrator/components/com_redirect/tmpl/link/edit.php b/administrator/components/com_redirect/tmpl/link/edit.php
index 9f3320aabdcb1..9117e79dbd40e 100644
--- a/administrator/components/com_redirect/tmpl/link/edit.php
+++ b/administrator/components/com_redirect/tmpl/link/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_redirect/tmpl/links/default.php b/administrator/components/com_redirect/tmpl/links/default.php
index 8be568281c1e4..c681dae183adb 100644
--- a/administrator/components/com_redirect/tmpl/links/default.php
+++ b/administrator/components/com_redirect/tmpl/links/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_redirect/tmpl/links/default_addform.php b/administrator/components/com_redirect/tmpl/links/default_addform.php
index 186b46583bd8d..6510ef5016108 100644
--- a/administrator/components/com_redirect/tmpl/links/default_addform.php
+++ b/administrator/components/com_redirect/tmpl/links/default_addform.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_redirect/tmpl/links/default_batch_body.php b/administrator/components/com_redirect/tmpl/links/default_batch_body.php
index 58ff121d215a0..052eef41afe42 100644
--- a/administrator/components/com_redirect/tmpl/links/default_batch_body.php
+++ b/administrator/components/com_redirect/tmpl/links/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
@@ -16,12 +18,12 @@
?>
diff --git a/administrator/components/com_redirect/tmpl/links/default_batch_footer.php b/administrator/components/com_redirect/tmpl/links/default_batch_footer.php
index 08b76e82053da..acd5826500778 100644
--- a/administrator/components/com_redirect/tmpl/links/default_batch_footer.php
+++ b/administrator/components/com_redirect/tmpl/links/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_redirect/tmpl/links/emptystate.php b/administrator/components/com_redirect/tmpl/links/emptystate.php
index 705387dd82823..752d956a46dc2 100644
--- a/administrator/components/com_redirect/tmpl/links/emptystate.php
+++ b/administrator/components/com_redirect/tmpl/links/emptystate.php
@@ -1,4 +1,5 @@
'COM_REDIRECT',
- 'formURL' => 'index.php?option=com_redirect&view=links',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Redirects:_Links',
- 'icon' => 'icon-map-signs redirect',
+ 'textPrefix' => 'COM_REDIRECT',
+ 'formURL' => 'index.php?option=com_redirect&view=links',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help4.x:Redirects:_Links',
+ 'icon' => 'icon-map-signs redirect',
];
$user = Factory::getApplication()->getIdentity();
-if ($user->authorise('core.create', 'com_redirect'))
-{
- $displayData['createURL'] = 'index.php?option=com_redirect&task=link.add';
+if ($user->authorise('core.create', 'com_redirect')) {
+ $displayData['createURL'] = 'index.php?option=com_redirect&task=link.add';
}
-if ($user->authorise('core.create', 'com_redirect')
- && $user->authorise('core.edit', 'com_redirect')
- && $user->authorise('core.edit.state', 'com_redirect'))
-{
- $displayData['formAppend'] = HTMLHelper::_(
- 'bootstrap.renderModal',
- 'collapseModal',
- [
- 'title' => Text::_('COM_REDIRECT_BATCH_OPTIONS'),
- 'footer' => $this->loadTemplate('batch_footer'),
- ],
- $this->loadTemplate('batch_body')
- );
+if (
+ $user->authorise('core.create', 'com_redirect')
+ && $user->authorise('core.edit', 'com_redirect')
+ && $user->authorise('core.edit.state', 'com_redirect')
+) {
+ $displayData['formAppend'] = HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'collapseModal',
+ [
+ 'title' => Text::_('COM_REDIRECT_BATCH_OPTIONS'),
+ 'footer' => $this->loadTemplate('batch_footer'),
+ ],
+ $this->loadTemplate('batch_body')
+ );
} ?>
redirectPluginId) : ?>
- redirectPluginId . '&tmpl=component&layout=modal'); ?>
- redirectPluginId . 'Modal',
- array(
- 'url' => $link,
- 'title' => Text::_('COM_REDIRECT_EDIT_PLUGIN_SETTINGS'),
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => '70',
- 'modalWidth' => '80',
- 'closeButton' => false,
- 'backdrop' => 'static',
- 'keyboard' => false,
- 'footer' => ''
- . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
- . ''
- . Text::_('JSAVE') . ' '
- . ''
- . Text::_('JAPPLY') . ' '
- )
- ); ?>
+ redirectPluginId . '&tmpl=component&layout=modal'); ?>
+ redirectPluginId . 'Modal',
+ array(
+ 'url' => $link,
+ 'title' => Text::_('COM_REDIRECT_EDIT_PLUGIN_SETTINGS'),
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => '70',
+ 'modalWidth' => '80',
+ 'closeButton' => false,
+ 'backdrop' => 'static',
+ 'keyboard' => false,
+ 'footer' => ''
+ . Text::_('JLIB_HTML_BEHAVIOR_CLOSE') . ' '
+ . ''
+ . Text::_('JSAVE') . ' '
+ . ''
+ . Text::_('JAPPLY') . ' '
+ )
+ ); ?>
-
-
-
+
+
+
diff --git a/administrator/components/com_scheduler/services/provider.php b/administrator/components/com_scheduler/services/provider.php
index 4511f0be44ed0..ded0221067825 100644
--- a/administrator/components/com_scheduler/services/provider.php
+++ b/administrator/components/com_scheduler/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Scheduler'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Scheduler'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function register(Container $container)
+ {
+ /**
+ * Register the MVCFactory and ComponentDispatcherFactory providers to map
+ * 'MVCFactoryInterface' and 'ComponentDispatcherFactoryInterface' to their
+ * initializers and register them with the component's DI container.
+ */
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Scheduler'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Scheduler'));
- $container->set(
- ComponentInterface::class,
- function (Container $container) {
- $component = new SchedulerComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new SchedulerComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_scheduler/src/Controller/DisplayController.php b/administrator/components/com_scheduler/src/Controller/DisplayController.php
index eb79b7f7837a9..4cc6a36e13074 100644
--- a/administrator/components/com_scheduler/src/Controller/DisplayController.php
+++ b/administrator/components/com_scheduler/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('layout', 'default');
+ /**
+ * @var string
+ * @since 4.1.0
+ */
+ protected $default_view = 'tasks';
- // Check for edit form.
- if ($layout === 'edit')
- {
- if (!$this->validateEntry())
- {
- $tasksViewUrl = Route::_('index.php?option=com_scheduler&view=tasks', false);
- $this->setRedirect($tasksViewUrl);
+ /**
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
+ * {@link InputFilter::clean()}.
+ *
+ * @return BaseController|boolean Returns either a BaseController object to support chaining, or false on failure
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $layout = $this->input->get('layout', 'default');
- return false;
- }
- }
+ // Check for edit form.
+ if ($layout === 'edit') {
+ if (!$this->validateEntry()) {
+ $tasksViewUrl = Route::_('index.php?option=com_scheduler&view=tasks', false);
+ $this->setRedirect($tasksViewUrl);
- // Let the parent method take over
- return parent::display($cachable, $urlparams);
- }
+ return false;
+ }
+ }
- /**
- * Validates entry to the view
- *
- * @param string $layout The layout to validate entry for (defaults to 'edit')
- *
- * @return boolean True is entry is valid
- *
- * @since 4.1.0
- */
- private function validateEntry(string $layout = 'edit'): bool
- {
- $context = 'com_scheduler';
- $id = $this->input->getInt('id');
- $isValid = true;
+ // Let the parent method take over
+ return parent::display($cachable, $urlparams);
+ }
- switch ($layout)
- {
- case 'edit':
+ /**
+ * Validates entry to the view
+ *
+ * @param string $layout The layout to validate entry for (defaults to 'edit')
+ *
+ * @return boolean True is entry is valid
+ *
+ * @since 4.1.0
+ */
+ private function validateEntry(string $layout = 'edit'): bool
+ {
+ $context = 'com_scheduler';
+ $id = $this->input->getInt('id');
+ $isValid = true;
- // True if controller was called and verified permissions
- $inEditList = $this->checkEditId("$context.edit.task", $id);
- $isNew = ($id == 0);
+ switch ($layout) {
+ case 'edit':
+ // True if controller was called and verified permissions
+ $inEditList = $this->checkEditId("$context.edit.task", $id);
+ $isNew = ($id == 0);
- // For new item, entry is invalid if task type was not selected through SelectView
- if ($isNew && !$this->app->getUserState("$context.add.task.task_type"))
- {
- $this->setMessage((Text::_('COM_SCHEDULER_ERROR_FORBIDDEN_JUMP_TO_ADD_VIEW')), 'error');
- $isValid = false;
- }
- // For existing item, entry is invalid if TaskController has not granted access
- elseif (!$inEditList)
- {
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // For new item, entry is invalid if task type was not selected through SelectView
+ if ($isNew && !$this->app->getUserState("$context.add.task.task_type")) {
+ $this->setMessage((Text::_('COM_SCHEDULER_ERROR_FORBIDDEN_JUMP_TO_ADD_VIEW')), 'error');
+ $isValid = false;
+ } elseif (!$inEditList) {
+ // For existing item, entry is invalid if TaskController has not granted access
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $isValid = false;
- }
- break;
- default:
- break;
- }
+ $isValid = false;
+ }
+ break;
+ default:
+ break;
+ }
- return $isValid;
- }
+ return $isValid;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Controller/TaskController.php b/administrator/components/com_scheduler/src/Controller/TaskController.php
index 50e3409abaf76..13230493bf1af 100644
--- a/administrator/components/com_scheduler/src/Controller/TaskController.php
+++ b/administrator/components/com_scheduler/src/Controller/TaskController.php
@@ -1,4 +1,5 @@
app;
- $input = $app->getInput();
- $validTaskOptions = SchedulerHelper::getTaskOptions();
-
- $canAdd = parent::add();
-
- if ($canAdd !== true)
- {
- return false;
- }
-
- $taskType = $input->get('type');
- $taskOption = $validTaskOptions->findOption($taskType) ?: null;
-
- if (!$taskOption)
- {
- // ? : Is this the right redirect [review]
- $redirectUrl = 'index.php?option=' . $this->option . '&view=select&layout=edit';
- $this->setRedirect(Route::_($redirectUrl, false));
- $app->enqueueMessage(Text::_('COM_SCHEDULER_ERROR_INVALID_TASK_TYPE'), 'warning');
- $canAdd = false;
- }
-
- $app->setUserState('com_scheduler.add.task.task_type', $taskType);
- $app->setUserState('com_scheduler.add.task.task_option', $taskOption);
-
- // @todo : Parameter array handling below?
-
- return $canAdd;
- }
-
- /**
- * Override parent cancel method to reset the add task state
- *
- * @param ?string $key Primary key from the URL param
- *
- * @return boolean True if access level checks pass
- *
- * @since 4.1.0
- */
- public function cancel($key = null): bool
- {
- $result = parent::cancel($key);
-
- $this->app->setUserState('com_scheduler.add.task.task_type', null);
- $this->app->setUserState('com_scheduler.add.task.task_option', null);
-
- // ? Do we need to redirect based on URL's 'return' param? {@see ModuleController}
-
- return $result;
- }
-
- /**
- * Check if user has the authority to edit an asset
- *
- * @param array $data Array of input data
- * @param string $key Name of key for primary key, defaults to 'id'
- *
- * @return boolean True if user is allowed to edit record
- *
- * @since 4.1.0
- */
- protected function allowEdit($data = array(), $key = 'id'): bool
- {
- // Extract the recordId from $data, will come in handy
- $recordId = (int) $data[$key] ?? 0;
-
- /**
- * Zero record (id:0), return component edit permission by calling parent controller method
- * ?: Is this the right way to do this?
- */
- if ($recordId === 0)
- {
- return parent::allowEdit($data, $key);
- }
-
- // @todo : Check if this works as expected
- return $this->app->getIdentity()->authorise('core.edit', 'com_scheduler.task.' . $recordId);
-
- }
+ /**
+ * Add a new record
+ *
+ * @return boolean
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function add(): bool
+ {
+ /** @var AdministratorApplication $app */
+ $app = $this->app;
+ $input = $app->getInput();
+ $validTaskOptions = SchedulerHelper::getTaskOptions();
+
+ $canAdd = parent::add();
+
+ if ($canAdd !== true) {
+ return false;
+ }
+
+ $taskType = $input->get('type');
+ $taskOption = $validTaskOptions->findOption($taskType) ?: null;
+
+ if (!$taskOption) {
+ // ? : Is this the right redirect [review]
+ $redirectUrl = 'index.php?option=' . $this->option . '&view=select&layout=edit';
+ $this->setRedirect(Route::_($redirectUrl, false));
+ $app->enqueueMessage(Text::_('COM_SCHEDULER_ERROR_INVALID_TASK_TYPE'), 'warning');
+ $canAdd = false;
+ }
+
+ $app->setUserState('com_scheduler.add.task.task_type', $taskType);
+ $app->setUserState('com_scheduler.add.task.task_option', $taskOption);
+
+ // @todo : Parameter array handling below?
+
+ return $canAdd;
+ }
+
+ /**
+ * Override parent cancel method to reset the add task state
+ *
+ * @param ?string $key Primary key from the URL param
+ *
+ * @return boolean True if access level checks pass
+ *
+ * @since 4.1.0
+ */
+ public function cancel($key = null): bool
+ {
+ $result = parent::cancel($key);
+
+ $this->app->setUserState('com_scheduler.add.task.task_type', null);
+ $this->app->setUserState('com_scheduler.add.task.task_option', null);
+
+ // ? Do we need to redirect based on URL's 'return' param? {@see ModuleController}
+
+ return $result;
+ }
+
+ /**
+ * Check if user has the authority to edit an asset
+ *
+ * @param array $data Array of input data
+ * @param string $key Name of key for primary key, defaults to 'id'
+ *
+ * @return boolean True if user is allowed to edit record
+ *
+ * @since 4.1.0
+ */
+ protected function allowEdit($data = array(), $key = 'id'): bool
+ {
+ // Extract the recordId from $data, will come in handy
+ $recordId = (int) $data[$key] ?? 0;
+
+ /**
+ * Zero record (id:0), return component edit permission by calling parent controller method
+ * ?: Is this the right way to do this?
+ */
+ if ($recordId === 0) {
+ return parent::allowEdit($data, $key);
+ }
+
+ // @todo : Check if this works as expected
+ return $this->app->getIdentity()->authorise('core.edit', 'com_scheduler.task.' . $recordId);
+ }
}
diff --git a/administrator/components/com_scheduler/src/Controller/TasksController.php b/administrator/components/com_scheduler/src/Controller/TasksController.php
index ba2252dddf16c..ad6e846c14afd 100644
--- a/administrator/components/com_scheduler/src/Controller/TasksController.php
+++ b/administrator/components/com_scheduler/src/Controller/TasksController.php
@@ -1,4 +1,5 @@
true]): BaseDatabaseModel
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for the parent method.
+ *
+ * @param string $name The name of the model.
+ * @param string $prefix The prefix for the PHP class name.
+ * @param array $config Array of configuration parameters.
+ *
+ * @return BaseDatabaseModel
+ *
+ * @since 4.1.0
+ */
+ public function getModel($name = 'Task', $prefix = 'Administrator', $config = ['ignore_request' => true]): BaseDatabaseModel
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
- /**
- * Unlock a locked task, i.e., a task that is presumably still running but might have crashed and got stuck in the
- * "locked" state.
- *
- * @return void
- *
- * @since 4.1.0
- */
- public function unlock(): void
- {
- // Check for request forgeries
- $this->checkToken();
+ /**
+ * Unlock a locked task, i.e., a task that is presumably still running but might have crashed and got stuck in the
+ * "locked" state.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function unlock(): void
+ {
+ // Check for request forgeries
+ $this->checkToken();
- /** @var integer[] $cid Items to publish (from request parameters). */
- $cid = (array) $this->input->get('cid', [], 'int');
+ /** @var integer[] $cid Items to publish (from request parameters). */
+ $cid = (array) $this->input->get('cid', [], 'int');
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
- if (empty($cid))
- {
- $this->app->getLogger()
- ->warning(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), array('category' => 'jerror'));
- }
- else
- {
- /** @var TaskModel $model */
- $model = $this->getModel();
+ if (empty($cid)) {
+ $this->app->getLogger()
+ ->warning(Text::_($this->text_prefix . '_NO_ITEM_SELECTED'), array('category' => 'jerror'));
+ } else {
+ /** @var TaskModel $model */
+ $model = $this->getModel();
- // Make sure the item IDs are integers
- $cid = ArrayHelper::toInteger($cid);
+ // Make sure the item IDs are integers
+ $cid = ArrayHelper::toInteger($cid);
- // Unlock the items.
- try
- {
- $model->unlock($cid);
- $errors = $model->getErrors();
- $noticeText = null;
+ // Unlock the items.
+ try {
+ $model->unlock($cid);
+ $errors = $model->getErrors();
+ $noticeText = null;
- if ($errors)
- {
- $this->app->enqueueMessage(Text::plural($this->text_prefix . '_N_ITEMS_FAILED_UNLOCKING', \count($cid)), 'error');
- }
- else
- {
- $noticeText = $this->text_prefix . '_N_ITEMS_UNLOCKED';
- }
+ if ($errors) {
+ $this->app->enqueueMessage(Text::plural($this->text_prefix . '_N_ITEMS_FAILED_UNLOCKING', \count($cid)), 'error');
+ } else {
+ $noticeText = $this->text_prefix . '_N_ITEMS_UNLOCKED';
+ }
- if (\count($cid))
- {
- $this->setMessage(Text::plural($noticeText, \count($cid)));
- }
- }
- catch (\Exception $e)
- {
- $this->setMessage($e->getMessage(), 'error');
- }
- }
+ if (\count($cid)) {
+ $this->setMessage(Text::plural($noticeText, \count($cid)));
+ }
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'error');
+ }
+ }
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . $this->getRedirectToListAppend(),
- false
- )
- );
- }
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . $this->getRedirectToListAppend(),
+ false
+ )
+ );
+ }
}
diff --git a/administrator/components/com_scheduler/src/Event/ExecuteTaskEvent.php b/administrator/components/com_scheduler/src/Event/ExecuteTaskEvent.php
index 31fb65508cd09..7d6d3d7c70035 100644
--- a/administrator/components/com_scheduler/src/Event/ExecuteTaskEvent.php
+++ b/administrator/components/com_scheduler/src/Event/ExecuteTaskEvent.php
@@ -1,4 +1,5 @@
arguments['resultSnapshot'] = $snapshot;
+ /**
+ * Sets the task result snapshot and stops event propagation.
+ *
+ * @param array $snapshot The task snapshot.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function setResult(array $snapshot = []): void
+ {
+ $this->arguments['resultSnapshot'] = $snapshot;
- if (!empty($snapshot))
- {
- $this->stopPropagation();
- }
- }
+ if (!empty($snapshot)) {
+ $this->stopPropagation();
+ }
+ }
- /**
- * @return integer The task's taskId.
- *
- * @since 4.1.0
- */
- public function getTaskId(): int
- {
- return $this->arguments['subject']->get('id');
- }
+ /**
+ * @return integer The task's taskId.
+ *
+ * @since 4.1.0
+ */
+ public function getTaskId(): int
+ {
+ return $this->arguments['subject']->get('id');
+ }
- /**
- * @return string The task's 'type'.
- *
- * @since 4.1.0
- */
- public function getRoutineId(): string
- {
- return $this->arguments['subject']->get('type');
- }
+ /**
+ * @return string The task's 'type'.
+ *
+ * @since 4.1.0
+ */
+ public function getRoutineId(): string
+ {
+ return $this->arguments['subject']->get('type');
+ }
- /**
- * Returns the snapshot of the triggered task if available, else an empty array
- *
- * @return array The task snapshot if available, else null
- *
- * @since 4.1.0
- */
- public function getResultSnapshot(): array
- {
- return $this->arguments['resultSnapshot'] ?? [];
- }
+ /**
+ * Returns the snapshot of the triggered task if available, else an empty array
+ *
+ * @return array The task snapshot if available, else null
+ *
+ * @since 4.1.0
+ */
+ public function getResultSnapshot(): array
+ {
+ return $this->arguments['resultSnapshot'] ?? [];
+ }
}
diff --git a/administrator/components/com_scheduler/src/Extension/SchedulerComponent.php b/administrator/components/com_scheduler/src/Extension/SchedulerComponent.php
index 9f0b58278f3be..ec411dca38d8e 100644
--- a/administrator/components/com_scheduler/src/Extension/SchedulerComponent.php
+++ b/administrator/components/com_scheduler/src/Extension/SchedulerComponent.php
@@ -1,4 +1,5 @@
[0, 59],
- 'hours' => [0, 23],
- 'days_week' => [1, 7],
- 'days_month' => [1, 31],
- 'months' => [1, 12],
- ];
-
- /**
- * Response labels for the 'month' and 'days_week' subtypes.
- * The labels are language constants translated when needed.
- *
- * @var string[][]
- * @since 4.1.0
- */
- private const PREPARED_RESPONSE_LABELS = [
- 'months' => [
- 'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE',
- 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER',
- ],
- 'days_week' => [
- 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY',
- 'FRIDAY', 'SATURDAY', 'SUNDAY',
- ],
- ];
-
- /**
- * The form field type.
- *
- * @var string
- *
- * @since 4.1.0
- */
- protected $type = 'cronIntervals';
-
- /**
- * The subtype of the CronIntervals field
- *
- * @var string
- * @since 4.1.0
- */
- private $subtype;
-
- /**
- * If true, field options will include a wildcard
- *
- * @var boolean
- * @since 4.1.0
- */
- private $wildcard;
-
- /**
- * If true, field will only have numeric labels (for days_week and months)
- *
- * @var boolean
- * @since 4.1.0
- */
- private $onlyNumericLabels;
-
- /**
- * Override the parent method to set deal with subtypes.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form
- * field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for
- * the field. For example if the field has `name="foo"` and the group value is
- * set to "bar" then the full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.1.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null): bool
- {
- $parentResult = parent::setup($element, $value, $group);
-
- $subtype = ((string) $element['subtype'] ?? '') ?: null;
- $wildcard = ((string) $element['wildcard'] ?? '') === 'true';
- $onlyNumericLabels = ((string) $element['onlyNumericLabels']) === 'true';
-
- if (!($subtype && \in_array($subtype, self::SUBTYPES)))
- {
- return false;
- }
-
- $this->subtype = $subtype;
- $this->wildcard = $wildcard;
- $this->onlyNumericLabels = $onlyNumericLabels;
-
- return $parentResult;
- }
-
- /**
- * Method to get field options
- *
- * @return array Array of objects representing options in the options list
- *
- * @since 4.1.0
- */
- protected function getOptions(): array
- {
- $subtype = $this->subtype;
- $options = parent::getOptions();
-
- if (!\in_array($subtype, self::SUBTYPES))
- {
- return $options;
- }
-
- if ($this->wildcard)
- {
- try
- {
- $options[] = HTMLHelper::_('select.option', '*', '*');
- }
- catch (\InvalidArgumentException $e)
- {
- }
- }
-
- [$optionLower, $optionUpper] = self::OPTIONS_RANGE[$subtype];
-
- // If we need text labels, we translate them first
- if (\array_key_exists($subtype, self::PREPARED_RESPONSE_LABELS) && !$this->onlyNumericLabels)
- {
- $labels = array_map(
- static function (string $string): string {
- return Text::_($string);
- },
- self::PREPARED_RESPONSE_LABELS[$subtype]
- );
- }
- else
- {
- $labels = range(...self::OPTIONS_RANGE[$subtype]);
- }
-
- for ([$i, $l] = [$optionLower, 0]; $i <= $optionUpper; $i++, $l++)
- {
- try
- {
- $options[] = HTMLHelper::_('select.option', (string) ($i), $labels[$l]);
- }
- catch (\InvalidArgumentException $e)
- {
- }
- }
-
- return $options;
- }
+ /**
+ * The subtypes supported by this field type.
+ *
+ * @var string[]
+ *
+ * @since 4.1.0
+ */
+ private const SUBTYPES = [
+ 'minutes',
+ 'hours',
+ 'days_month',
+ 'months',
+ 'days_week',
+ ];
+
+ /**
+ * Count of predefined options for each subtype
+ *
+ * @var int[][]
+ *
+ * @since 4.1.0
+ */
+ private const OPTIONS_RANGE = [
+ 'minutes' => [0, 59],
+ 'hours' => [0, 23],
+ 'days_week' => [1, 7],
+ 'days_month' => [1, 31],
+ 'months' => [1, 12],
+ ];
+
+ /**
+ * Response labels for the 'month' and 'days_week' subtypes.
+ * The labels are language constants translated when needed.
+ *
+ * @var string[][]
+ * @since 4.1.0
+ */
+ private const PREPARED_RESPONSE_LABELS = [
+ 'months' => [
+ 'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE',
+ 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER',
+ ],
+ 'days_week' => [
+ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY',
+ 'FRIDAY', 'SATURDAY', 'SUNDAY',
+ ],
+ ];
+
+ /**
+ * The form field type.
+ *
+ * @var string
+ *
+ * @since 4.1.0
+ */
+ protected $type = 'cronIntervals';
+
+ /**
+ * The subtype of the CronIntervals field
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ private $subtype;
+
+ /**
+ * If true, field options will include a wildcard
+ *
+ * @var boolean
+ * @since 4.1.0
+ */
+ private $wildcard;
+
+ /**
+ * If true, field will only have numeric labels (for days_week and months)
+ *
+ * @var boolean
+ * @since 4.1.0
+ */
+ private $onlyNumericLabels;
+
+ /**
+ * Override the parent method to set deal with subtypes.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form
+ * field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for
+ * the field. For example if the field has `name="foo"` and the group value is
+ * set to "bar" then the full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.1.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null): bool
+ {
+ $parentResult = parent::setup($element, $value, $group);
+
+ $subtype = ((string) $element['subtype'] ?? '') ?: null;
+ $wildcard = ((string) $element['wildcard'] ?? '') === 'true';
+ $onlyNumericLabels = ((string) $element['onlyNumericLabels']) === 'true';
+
+ if (!($subtype && \in_array($subtype, self::SUBTYPES))) {
+ return false;
+ }
+
+ $this->subtype = $subtype;
+ $this->wildcard = $wildcard;
+ $this->onlyNumericLabels = $onlyNumericLabels;
+
+ return $parentResult;
+ }
+
+ /**
+ * Method to get field options
+ *
+ * @return array Array of objects representing options in the options list
+ *
+ * @since 4.1.0
+ */
+ protected function getOptions(): array
+ {
+ $subtype = $this->subtype;
+ $options = parent::getOptions();
+
+ if (!\in_array($subtype, self::SUBTYPES)) {
+ return $options;
+ }
+
+ if ($this->wildcard) {
+ try {
+ $options[] = HTMLHelper::_('select.option', '*', '*');
+ } catch (\InvalidArgumentException $e) {
+ }
+ }
+
+ [$optionLower, $optionUpper] = self::OPTIONS_RANGE[$subtype];
+
+ // If we need text labels, we translate them first
+ if (\array_key_exists($subtype, self::PREPARED_RESPONSE_LABELS) && !$this->onlyNumericLabels) {
+ $labels = array_map(
+ static function (string $string): string {
+ return Text::_($string);
+ },
+ self::PREPARED_RESPONSE_LABELS[$subtype]
+ );
+ } else {
+ $labels = range(...self::OPTIONS_RANGE[$subtype]);
+ }
+
+ for ([$i, $l] = [$optionLower, 0]; $i <= $optionUpper; $i++, $l++) {
+ try {
+ $options[] = HTMLHelper::_('select.option', (string) ($i), $labels[$l]);
+ } catch (\InvalidArgumentException $e) {
+ }
+ }
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Field/ExecutionRuleField.php b/administrator/components/com_scheduler/src/Field/ExecutionRuleField.php
index 6f877357d2099..bacb4750ec754 100644
--- a/administrator/components/com_scheduler/src/Field/ExecutionRuleField.php
+++ b/administrator/components/com_scheduler/src/Field/ExecutionRuleField.php
@@ -1,4 +1,5 @@
'COM_SCHEDULER_EXECUTION_INTERVAL_MINUTES',
- 'interval-hours' => 'COM_SCHEDULER_EXECUTION_INTERVAL_HOURS',
- 'interval-days' => 'COM_SCHEDULER_EXECUTION_INTERVAL_DAYS',
- 'interval-months' => 'COM_SCHEDULER_EXECUTION_INTERVAL_MONTHS',
- 'cron-expression' => 'COM_SCHEDULER_EXECUTION_CRON_EXPRESSION',
- 'manual' => 'COM_SCHEDULER_OPTION_EXECUTION_MANUAL_LABEL',
- ];
+ /**
+ * Available execution rules.
+ *
+ * @var string[]
+ * @since 4.1.0
+ */
+ protected $predefinedOptions = [
+ 'interval-minutes' => 'COM_SCHEDULER_EXECUTION_INTERVAL_MINUTES',
+ 'interval-hours' => 'COM_SCHEDULER_EXECUTION_INTERVAL_HOURS',
+ 'interval-days' => 'COM_SCHEDULER_EXECUTION_INTERVAL_DAYS',
+ 'interval-months' => 'COM_SCHEDULER_EXECUTION_INTERVAL_MONTHS',
+ 'cron-expression' => 'COM_SCHEDULER_EXECUTION_CRON_EXPRESSION',
+ 'manual' => 'COM_SCHEDULER_OPTION_EXECUTION_MANUAL_LABEL',
+ ];
}
diff --git a/administrator/components/com_scheduler/src/Field/IntervalField.php b/administrator/components/com_scheduler/src/Field/IntervalField.php
index 7a7202e66f33d..1ca63a405f0aa 100644
--- a/administrator/components/com_scheduler/src/Field/IntervalField.php
+++ b/administrator/components/com_scheduler/src/Field/IntervalField.php
@@ -1,4 +1,5 @@
[minVal, maxVal]
- *
- * @var string[]
- * @since 4.1.0
- */
- private const SUBTYPES = [
- 'minutes' => [1, 59],
- 'hours' => [1, 23],
- 'days' => [1, 30],
- 'months' => [1, 12],
- ];
+ /**
+ * The subtypes supported by this field type => [minVal, maxVal]
+ *
+ * @var string[]
+ * @since 4.1.0
+ */
+ private const SUBTYPES = [
+ 'minutes' => [1, 59],
+ 'hours' => [1, 23],
+ 'days' => [1, 30],
+ 'months' => [1, 12],
+ ];
- /**
- * The allowable maximum value of the field.
- *
- * @var float
- * @since 4.1.0
- */
- protected $max;
+ /**
+ * The allowable maximum value of the field.
+ *
+ * @var float
+ * @since 4.1.0
+ */
+ protected $max;
- /**
- * The allowable minimum value of the field.
- *
- * @var float
- * @since 4.1.0
- */
- protected $min;
+ /**
+ * The allowable minimum value of the field.
+ *
+ * @var float
+ * @since 4.1.0
+ */
+ protected $min;
- /**
- * The step by which value of the field increased or decreased.
- *
- * @var float
- * @since 4.1.0
- */
- protected $step = 1;
+ /**
+ * The step by which value of the field increased or decreased.
+ *
+ * @var float
+ * @since 4.1.0
+ */
+ protected $step = 1;
- /**
- * Override the parent method to set deal with subtypes.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form
- * field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for
- * the field. For example if the field has `name="foo"` and the group value is
- * set to "bar" then the full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.1.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null): bool
- {
- $parentResult = FormField::setup($element, $value, $group);
- $subtype = ((string) $element['subtype'] ?? '') ?: null;
+ /**
+ * Override the parent method to set deal with subtypes.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form
+ * field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for
+ * the field. For example if the field has `name="foo"` and the group value is
+ * set to "bar" then the full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.1.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null): bool
+ {
+ $parentResult = FormField::setup($element, $value, $group);
+ $subtype = ((string) $element['subtype'] ?? '') ?: null;
- if (empty($subtype) || !\array_key_exists($subtype, self::SUBTYPES))
- {
- return false;
- }
+ if (empty($subtype) || !\array_key_exists($subtype, self::SUBTYPES)) {
+ return false;
+ }
- [$this->min, $this->max] = self::SUBTYPES[$subtype];
+ [$this->min, $this->max] = self::SUBTYPES[$subtype];
- return $parentResult;
- }
+ return $parentResult;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Field/TaskStateField.php b/administrator/components/com_scheduler/src/Field/TaskStateField.php
index 4ea8ba93d8a6e..a9bca3e880a15 100644
--- a/administrator/components/com_scheduler/src/Field/TaskStateField.php
+++ b/administrator/components/com_scheduler/src/Field/TaskStateField.php
@@ -1,4 +1,5 @@
'JTRASHED',
- 0 => 'JDISABLED',
- 1 => 'JENABLED',
- '*' => 'JALL',
- ];
+ /**
+ * Available states
+ *
+ * @var string[]
+ * @since 4.1.0
+ */
+ protected $predefinedOptions = [
+ -2 => 'JTRASHED',
+ 0 => 'JDISABLED',
+ 1 => 'JENABLED',
+ '*' => 'JALL',
+ ];
}
diff --git a/administrator/components/com_scheduler/src/Field/TaskTypeField.php b/administrator/components/com_scheduler/src/Field/TaskTypeField.php
index 86d2e23702a0a..a0332f25d7319 100644
--- a/administrator/components/com_scheduler/src/Field/TaskTypeField.php
+++ b/administrator/components/com_scheduler/src/Field/TaskTypeField.php
@@ -1,4 +1,5 @@
options,
- 'title',
- 1
- );
+ // Get all available task types and sort by title
+ $types = ArrayHelper::sortObjects(
+ SchedulerHelper::getTaskOptions()->options,
+ 'title',
+ 1
+ );
- // Closure to add a TaskOption as a option in $options: array
- $addTypeAsOption = function (TaskOption $type) use (&$options) {
- try
- {
- $options[] = HTMLHelper::_('select.option', $type->id, $type->title);
- }
- catch (\InvalidArgumentException $e)
- {
- }
- };
+ // Closure to add a TaskOption as a option in $options: array
+ $addTypeAsOption = function (TaskOption $type) use (&$options) {
+ try {
+ $options[] = HTMLHelper::_('select.option', $type->id, $type->title);
+ } catch (\InvalidArgumentException $e) {
+ }
+ };
- // Call $addTypeAsOption on each type
- array_map($addTypeAsOption, $types);
+ // Call $addTypeAsOption on each type
+ array_map($addTypeAsOption, $types);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Field/WebcronLinkField.php b/administrator/components/com_scheduler/src/Field/WebcronLinkField.php
index 597afee3e6428..0b46ff7ada0b2 100644
--- a/administrator/components/com_scheduler/src/Field/WebcronLinkField.php
+++ b/administrator/components/com_scheduler/src/Field/WebcronLinkField.php
@@ -1,4 +1,5 @@
task = \is_array($task) ? $task : ArrayHelper::fromObject($task);
- $rule = $this->getFromTask('cron_rules');
- $this->rule = \is_string($rule)
- ? (object) json_decode($rule)
- : (\is_array($rule) ? (object) $rule : $rule);
- $this->type = $this->rule->type;
- }
+ /**
+ * @param array|object $task A task entry
+ *
+ * @since 4.1.0
+ */
+ public function __construct($task)
+ {
+ $this->task = \is_array($task) ? $task : ArrayHelper::fromObject($task);
+ $rule = $this->getFromTask('cron_rules');
+ $this->rule = \is_string($rule)
+ ? (object) json_decode($rule)
+ : (\is_array($rule) ? (object) $rule : $rule);
+ $this->type = $this->rule->type;
+ }
- /**
- * Get a property from the task array
- *
- * @param string $property The property to get
- * @param mixed $default The default value returned if property does not exist
- *
- * @return mixed
- *
- * @since 4.1.0
- */
- private function getFromTask(string $property, $default = null)
- {
- $property = ArrayHelper::getValue($this->task, $property);
+ /**
+ * Get a property from the task array
+ *
+ * @param string $property The property to get
+ * @param mixed $default The default value returned if property does not exist
+ *
+ * @return mixed
+ *
+ * @since 4.1.0
+ */
+ private function getFromTask(string $property, $default = null)
+ {
+ $property = ArrayHelper::getValue($this->task, $property);
- return $property ?? $default;
- }
+ return $property ?? $default;
+ }
- /**
- * @param boolean $string If true, an SQL formatted string is returned.
- * @param boolean $basisNow If true, the current date-time is used as the basis for projecting the next
- * execution.
- *
- * @return ?Date|string
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function nextExec(bool $string = true, bool $basisNow = false)
- {
- // Exception handling here
- switch ($this->type)
- {
- case 'interval':
- $lastExec = Factory::getDate($basisNow ? 'now' : $this->getFromTask('last_execution'), 'UTC');
- $interval = new \DateInterval($this->rule->exp);
- $nextExec = $lastExec->add($interval);
- $nextExec = $string ? $nextExec->toSql() : $nextExec;
- break;
- case 'cron-expression':
- // @todo: testing
- $cExp = new CronExpression((string) $this->rule->exp);
- $nextExec = $cExp->getNextRunDate('now', 0, false, 'UTC');
- $nextExec = $string ? $this->dateTimeToSql($nextExec) : $nextExec;
- break;
- default:
- // 'manual' execution is handled here.
- $nextExec = null;
- }
+ /**
+ * @param boolean $string If true, an SQL formatted string is returned.
+ * @param boolean $basisNow If true, the current date-time is used as the basis for projecting the next
+ * execution.
+ *
+ * @return ?Date|string
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function nextExec(bool $string = true, bool $basisNow = false)
+ {
+ // Exception handling here
+ switch ($this->type) {
+ case 'interval':
+ $lastExec = Factory::getDate($basisNow ? 'now' : $this->getFromTask('last_execution'), 'UTC');
+ $interval = new \DateInterval($this->rule->exp);
+ $nextExec = $lastExec->add($interval);
+ $nextExec = $string ? $nextExec->toSql() : $nextExec;
+ break;
+ case 'cron-expression':
+ // @todo: testing
+ $cExp = new CronExpression((string) $this->rule->exp);
+ $nextExec = $cExp->getNextRunDate('now', 0, false, 'UTC');
+ $nextExec = $string ? $this->dateTimeToSql($nextExec) : $nextExec;
+ break;
+ default:
+ // 'manual' execution is handled here.
+ $nextExec = null;
+ }
- return $nextExec;
- }
+ return $nextExec;
+ }
- /**
- * Returns a sql-formatted string for a DateTime object.
- * Only needed for DateTime objects returned by CronExpression, JDate supports this as class method.
- *
- * @param \DateTime $dateTime A DateTime object to format
- *
- * @return string
- *
- * @since 4.1.0
- */
- private function dateTimeToSql(\DateTime $dateTime): string
- {
- static $db;
- $db = $db ?? Factory::getContainer()->get(DatabaseDriver::class);
+ /**
+ * Returns a sql-formatted string for a DateTime object.
+ * Only needed for DateTime objects returned by CronExpression, JDate supports this as class method.
+ *
+ * @param \DateTime $dateTime A DateTime object to format
+ *
+ * @return string
+ *
+ * @since 4.1.0
+ */
+ private function dateTimeToSql(\DateTime $dateTime): string
+ {
+ static $db;
+ $db = $db ?? Factory::getContainer()->get(DatabaseDriver::class);
- return $dateTime->format($db->getDateFormat());
- }
+ return $dateTime->format($db->getDateFormat());
+ }
}
diff --git a/administrator/components/com_scheduler/src/Helper/SchedulerHelper.php b/administrator/components/com_scheduler/src/Helper/SchedulerHelper.php
index 3e0f13a2cefe2..0726e1f9f7922 100644
--- a/administrator/components/com_scheduler/src/Helper/SchedulerHelper.php
+++ b/administrator/components/com_scheduler/src/Helper/SchedulerHelper.php
@@ -1,4 +1,5 @@
$options,
- ]
- );
+ /** @var AdministratorApplication $app */
+ $app = Factory::getApplication();
+ $options = new TaskOptions();
+ $event = AbstractEvent::create(
+ 'onTaskOptionsList',
+ [
+ 'subject' => $options,
+ ]
+ );
- PluginHelper::importPlugin('task');
- $app->getDispatcher()->dispatch('onTaskOptionsList', $event);
+ PluginHelper::importPlugin('task');
+ $app->getDispatcher()->dispatch('onTaskOptionsList', $event);
- self::$taskOptionsCache = $options;
+ self::$taskOptionsCache = $options;
- return $options;
- }
+ return $options;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Model/SelectModel.php b/administrator/components/com_scheduler/src/Model/SelectModel.php
index ea92d6c816e90..8d0365bcf9f67 100644
--- a/administrator/components/com_scheduler/src/Model/SelectModel.php
+++ b/administrator/components/com_scheduler/src/Model/SelectModel.php
@@ -1,4 +1,5 @@
app = Factory::getApplication();
+ /**
+ * SelectModel constructor.
+ *
+ * @param array $config An array of configuration options (name, state, dbo, table_path, ignore_request).
+ * @param ?MVCFactoryInterface $factory The factory.
+ *
+ * @throws \Exception
+ * @since 4.1.0
+ */
+ public function __construct($config = array(), ?MVCFactoryInterface $factory = null)
+ {
+ $this->app = Factory::getApplication();
- parent::__construct($config, $factory);
- }
+ parent::__construct($config, $factory);
+ }
- /**
- * @return TaskOption[] An array of TaskOption objects
- *
- * @throws \Exception
- * @since 4.1.0
- */
- public function getItems(): array
- {
- return SchedulerHelper::getTaskOptions()->options;
- }
+ /**
+ * @return TaskOption[] An array of TaskOption objects
+ *
+ * @throws \Exception
+ * @since 4.1.0
+ */
+ public function getItems(): array
+ {
+ return SchedulerHelper::getTaskOptions()->options;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Model/TaskModel.php b/administrator/components/com_scheduler/src/Model/TaskModel.php
index 5bdb8fe9be1fe..d375c2b1444bd 100644
--- a/administrator/components/com_scheduler/src/Model/TaskModel.php
+++ b/administrator/components/com_scheduler/src/Model/TaskModel.php
@@ -1,4 +1,5 @@
1,
- 'disabled' => 0,
- 'trashed' => -2,
- ];
-
- /**
- * The name of the database table with task records.
- *
- * @var string
- * @since 4.1.0
- */
- public const TASK_TABLE = '#__scheduler_tasks';
-
- /**
- * Prefix used with controller messages
- *
- * @var string
- * @since 4.1.0
- */
- protected $text_prefix = 'COM_SCHEDULER';
-
- /**
- * Type alias for content type
- *
- * @var string
- * @since 4.1.0
- */
- public $typeAlias = 'com_scheduler.task';
-
- /**
- * The Application object, for convenience
- *
- * @var AdministratorApplication $app
- * @since 4.1.0
- */
- protected $app;
-
- /**
- * The event to trigger before unlocking the data.
- *
- * @var string
- * @since 4.1.0
- */
- protected $event_before_unlock = null;
-
- /**
- * The event to trigger after unlocking the data.
- *
- * @var string
- * @since 4.1.0
- */
- protected $event_unlock = null;
-
- /**
- * TaskModel constructor. Needed just to set $app
- *
- * @param array $config An array of configuration options
- * @param MVCFactoryInterface|null $factory The factory
- * @param FormFactoryInterface|null $formFactory The form factory
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null)
- {
- $config['events_map'] = $config['events_map'] ?? [];
-
- $config['events_map'] = array_merge(
- [
- 'save' => 'task',
- 'validate' => 'task',
- 'unlock' => 'task',
- ],
- $config['events_map']
- );
-
- if (isset($config['event_before_unlock']))
- {
- $this->event_before_unlock = $config['event_before_unlock'];
- }
- elseif (empty($this->event_before_unlock))
- {
- $this->event_before_unlock = 'onContentBeforeUnlock';
- }
-
- if (isset($config['event_unlock']))
- {
- $this->event_unlock = $config['event_unlock'];
- }
- elseif (empty($this->event_unlock))
- {
- $this->event_unlock = 'onContentUnlock';
- }
-
- $this->app = Factory::getApplication();
-
- parent::__construct($config, $factory, $formFactory);
- }
-
- /**
- * Fetches the form object associated with this model. By default,
- * loads the corresponding data from the DB and binds it with the form.
- *
- * @param array $data Data that needs to go into the form
- * @param bool $loadData Should the form load its data from the DB?
- *
- * @return Form|boolean A JForm object on success, false on failure.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function getForm($data = array(), $loadData = true)
- {
- Form::addFieldPath(JPATH_ADMINISTRATOR . 'components/com_scheduler/src/Field');
-
- /**
- * loadForm() (defined by FormBehaviourTrait) also loads the form data by calling
- * loadFormData() : $data [implemented here] and binds it to the form by calling
- * $form->bind($data).
- */
- $form = $this->loadForm('com_scheduler.task', 'task', ['control' => 'jform', 'load_data' => $loadData]);
-
- if (empty($form))
- {
- return false;
- }
-
- $user = $this->app->getIdentity();
-
- // If new entry, set task type from state
- if ($this->getState('task.id', 0) === 0 && $this->getState('task.type') !== null)
- {
- $form->setValue('type', null, $this->getState('task.type'));
- }
-
- // @todo : Check if this is working as expected for new items (id == 0)
- if (!$user->authorise('core.edit.state', 'com_scheduler.task.' . $this->getState('task.id')))
- {
- // Disable fields
- $form->setFieldAttribute('state', 'disabled', 'true');
-
- // No "hacking" ._.
- $form->setFieldAttribute('state', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Determine whether a record may be deleted taking into consideration
- * the user's permissions over the record.
- *
- * @param object $record The database row/record in question
- *
- * @return boolean True if the record may be deleted
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function canDelete($record): bool
- {
- // Record doesn't exist, can't delete
- if (empty($record->id))
- {
- return false;
- }
-
- return $this->app->getIdentity()->authorise('core.delete', 'com_scheduler.task.' . $record->id);
- }
-
- /**
- * Populate the model state, we use these instead of toying with input or the global state
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function populateState(): void
- {
- $app = $this->app;
-
- $taskId = $app->getInput()->getInt('id');
- $taskType = $app->getUserState('com_scheduler.add.task.task_type');
-
- // @todo: Remove this. Get the option through a helper call.
- $taskOption = $app->getUserState('com_scheduler.add.task.task_option');
-
- $this->setState('task.id', $taskId);
- $this->setState('task.type', $taskType);
- $this->setState('task.option', $taskOption);
-
- // Load component params, though com_scheduler does not (yet) have any params
- $cParams = ComponentHelper::getParams($this->option);
- $this->setState('params', $cParams);
- }
-
- /**
- * Don't need to define this method since the parent getTable()
- * implicitly deduces $name and $prefix anyways. This makes the object
- * more transparent though.
- *
- * @param string $name Name of the table
- * @param string $prefix Class prefix
- * @param array $options Model config array
- *
- * @return Table
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function getTable($name = 'Task', $prefix = 'Table', $options = array()): Table
- {
- return parent::getTable($name, $prefix, $options);
- }
-
- /**
- * Fetches the data to be injected into the form
- *
- * @return object Associative array of form data.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function loadFormData()
- {
- $data = $this->app->getUserState('com_scheduler.edit.task.data', array());
-
- // If the data from UserState is empty, we fetch it with getItem()
- if (empty($data))
- {
- /** @var CMSObject $data */
- $data = $this->getItem();
-
- // @todo : further data processing goes here
-
- // For a fresh object, set exec-day and exec-time
- if (!($data->id ?? 0))
- {
- $data->execution_rules['exec-day'] = gmdate('d');
- $data->execution_rules['exec-time'] = gmdate('H:i');
- }
- }
-
- // Let plugins manipulate the data
- $this->preprocessData('com_scheduler.task', $data, 'task');
-
- return $data;
- }
-
- /**
- * Overloads the parent getItem() method.
- *
- * @param integer $pk Primary key
- *
- * @return object|boolean Object on success, false on failure
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function getItem($pk = null)
- {
- $item = parent::getItem($pk);
-
- if (!\is_object($item))
- {
- return false;
- }
-
- // Parent call leaves `execution_rules` and `cron_rules` JSON encoded
- $item->set('execution_rules', json_decode($item->get('execution_rules', '')));
- $item->set('cron_rules', json_decode($item->get('cron_rules', '')));
-
- $taskOption = SchedulerHelper::getTaskOptions()->findOption(
- ($item->id ?? 0) ? ($item->type ?? 0) : $this->getState('task.type')
- );
-
- $item->set('taskOption', $taskOption);
-
- return $item;
- }
-
- /**
- * Get a task from the database, only if an exclusive "lock" on the task can be acquired.
- * The method supports options to customise the limitations on the fetch.
- *
- * @param array $options Array with options to fetch the task:
- * 1. `id`: Optional id of the task to fetch.
- * 2. `allowDisabled`: If true, disabled tasks can also be fetched.
- * (default: false)
- * 3. `bypassScheduling`: If true, tasks that are not due can also be
- * fetched. Should only be true if an `id` is targeted instead of the
- * task queue. (default: false)
- * 4. `allowConcurrent`: If true, fetches even when another task is
- * running ('locked'). (default: false)
- * 5. `includeCliExclusive`: If true, can also fetch CLI exclusive tasks. (default: true)
- *
- * @return ?\stdClass Task entry as in the database.
- *
- * @since 4.1.0
- * @throws UndefinedOptionsException|InvalidOptionsException
- * @throws \RuntimeException
- */
- public function getTask(array $options = []): ?\stdClass
- {
- $resolver = new OptionsResolver;
-
- try
- {
- $this->configureTaskGetterOptions($resolver);
- }
- catch (\Exception $e)
- {
- }
-
- try
- {
- $options = $resolver->resolve($options);
- }
- catch (\Exception $e)
- {
- if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException)
- {
- throw $e;
- }
- }
-
- $db = $this->getDatabase();
- $now = Factory::getDate()->toSql();
-
- // Get lock on the table to help with concurrency issues
- $db->lockTable(self::TASK_TABLE);
-
- // If concurrency is not allowed, we only get a task if another one does not have a "lock"
- if (!$options['allowConcurrent'])
- {
- // Get count of locked (presumed running) tasks
- $lockCountQuery = $db->getQuery(true)
- ->from($db->quoteName(self::TASK_TABLE))
- ->select('COUNT(id)')
- ->where($db->quoteName('locked') . ' IS NOT NULL');
-
- try
- {
- $runningCount = $db->setQuery($lockCountQuery)->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $db->unlockTables();
-
- return null;
- }
-
- if ($runningCount !== 0)
- {
- $db->unlockTables();
-
- return null;
- }
- }
-
- $lockQuery = $db->getQuery(true);
-
- $lockQuery->update($db->quoteName(self::TASK_TABLE))
- ->set($db->quoteName('locked') . ' = :now1')
- ->bind(':now1', $now);
-
- // Array of all active routine ids
- $activeRoutines = array_map(
- static function (TaskOption $taskOption): string
- {
- return $taskOption->id;
- },
- SchedulerHelper::getTaskOptions()->options
- );
-
- // "Orphaned" tasks are not a part of the task queue!
- $lockQuery->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
-
- // If directed, exclude CLI exclusive tasks
- if (!$options['includeCliExclusive'])
- {
- $lockQuery->where($db->quoteName('cli_exclusive') . ' = 0');
- }
-
- if (!$options['bypassScheduling'])
- {
- $lockQuery->where($db->quoteName('next_execution') . ' <= :now2')
- ->bind(':now2', $now);
- }
-
- if ($options['allowDisabled'])
- {
- $lockQuery->whereIn($db->quoteName('state'), [0, 1]);
- }
- else
- {
- $lockQuery->where($db->quoteName('state') . ' = 1');
- }
-
- if ($options['id'] > 0)
- {
- $lockQuery->where($db->quoteName('id') . ' = :taskId')
- ->bind(':taskId', $options['id'], ParameterType::INTEGER);
- }
- // Pick from the front of the task queue if no 'id' is specified
- else
- {
- // Get the id of the next task in the task queue
- $idQuery = $db->getQuery(true)
- ->from($db->quoteName(self::TASK_TABLE))
- ->select($db->quoteName('id'))
- ->where($db->quoteName('state') . ' = 1')
- ->order($db->quoteName('priority') . ' DESC')
- ->order($db->quoteName('next_execution') . ' ASC')
- ->setLimit(1);
-
- try
- {
- $ids = $db->setQuery($idQuery)->loadColumn();
- }
- catch (\RuntimeException $e)
- {
- $db->unlockTables();
-
- return null;
- }
-
- if (count($ids) === 0)
- {
- $db->unlockTables();
-
- return null;
- }
-
- $lockQuery->whereIn($db->quoteName('id'), $ids);
- }
-
- try
- {
- $db->setQuery($lockQuery)->execute();
- }
- catch (\RuntimeException $e)
- {
- }
- finally
- {
- $affectedRows = $db->getAffectedRows();
-
- $db->unlockTables();
- }
-
- if ($affectedRows != 1)
- {
- /*
- // @todo
- // ? Fatal failure handling here?
- // ! Question is, how? If we check for tasks running beyond there time here, we have no way of
- // ! what's already been notified (since we're not auto-unlocking/recovering tasks anymore).
- // The solution __may__ be in a "last_successful_finish" (or something) column.
- */
-
- return null;
- }
-
- $getQuery = $db->getQuery(true);
-
- $getQuery->select('*')
- ->from($db->quoteName(self::TASK_TABLE))
- ->where($db->quoteName('locked') . ' = :now')
- ->bind(':now', $now);
-
- $task = $db->setQuery($getQuery)->loadObject();
-
- $task->execution_rules = json_decode($task->execution_rules);
- $task->cron_rules = json_decode($task->cron_rules);
-
- $task->taskOption = SchedulerHelper::getTaskOptions()->findOption($task->type);
-
- return $task;
- }
-
- /**
- * Set up an {@see OptionsResolver} to resolve options compatible with the {@see GetTask()} method.
- *
- * @param OptionsResolver $resolver The {@see OptionsResolver} instance to set up.
- *
- * @return OptionsResolver
- *
- * @since 4.1.0
- * @throws AccessException
- */
- public static function configureTaskGetterOptions(OptionsResolver $resolver): OptionsResolver
- {
- $resolver->setDefaults(
- [
- 'id' => 0,
- 'allowDisabled' => false,
- 'bypassScheduling' => false,
- 'allowConcurrent' => false,
- 'includeCliExclusive' => true,
- ]
- )
- ->setAllowedTypes('id', 'numeric')
- ->setAllowedTypes('allowDisabled', 'bool')
- ->setAllowedTypes('bypassScheduling', 'bool')
- ->setAllowedTypes('allowConcurrent', 'bool')
- ->setAllowedTypes('includeCliExclusive', 'bool');
-
- return $resolver;
- }
-
- /**
- * @param array $data The form data
- *
- * @return boolean True on success, false on failure
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function save($data): bool
- {
- $id = (int) ($data['id'] ?? $this->getState('task.id'));
- $isNew = $id === 0;
-
- // Clean up execution rules
- $data['execution_rules'] = $this->processExecutionRules($data['execution_rules']);
-
- // If a new entry, we'll have to put in place a pseudo-last_execution
- if ($isNew)
- {
- $basisDayOfMonth = $data['execution_rules']['exec-day'];
- [$basisHour, $basisMinute] = explode(':', $data['execution_rules']['exec-time']);
-
- $data['last_execution'] = Factory::getDate('now', 'GMT')->format('Y-m')
- . "-$basisDayOfMonth $basisHour:$basisMinute:00";
- }
- else
- {
- $data['last_execution'] = $this->getItem($id)->last_execution;
- }
-
- // Build the `cron_rules` column from `execution_rules`
- $data['cron_rules'] = $this->buildExecutionRules($data['execution_rules']);
-
- // `next_execution` would be null if scheduling is disabled with the "manual" rule!
- $data['next_execution'] = (new ExecRuleHelper($data))->nextExec();
-
- if ($isNew)
- {
- $data['last_execution'] = null;
- }
-
- // If no params, we set as empty array.
- // ? Is this the right place to do this
- $data['params'] = $data['params'] ?? [];
-
- // Parent method takes care of saving to the table
- return parent::save($data);
- }
-
- /**
- * Clean up and standardise execution rules
- *
- * @param array $unprocessedRules The form data [? can just replace with execution_interval]
- *
- * @return array Processed rules
- *
- * @since 4.1.0
- */
- private function processExecutionRules(array $unprocessedRules): array
- {
- $executionRules = $unprocessedRules;
-
- $ruleType = $executionRules['rule-type'];
- $retainKeys = ['rule-type', $ruleType, 'exec-day', 'exec-time'];
- $executionRules = array_intersect_key($executionRules, array_flip($retainKeys));
-
- // Default to current date-time in UTC/GMT as the basis
- $executionRules['exec-day'] = $executionRules['exec-day'] ?: (string) gmdate('d');
- $executionRules['exec-time'] = $executionRules['exec-time'] ?: (string) gmdate('H:i');
-
- // If custom ruleset, sort it
- // ? Is this necessary
- if ($ruleType === 'cron-expression')
- {
- foreach ($executionRules['cron-expression'] as &$values)
- {
- sort($values);
- }
- }
-
- return $executionRules;
- }
-
- /**
- * Private method to build execution expression from input execution rules.
- * This expression is used internally to determine execution times/conditions.
- *
- * @param array $executionRules Execution rules from the Task form, post-processing.
- *
- * @return array
- *
- * @since 4.1.0
- * @throws \Exception
- */
- private function buildExecutionRules(array $executionRules): array
- {
- // Maps interval strings, use with sprintf($map[intType], $interval)
- $intervalStringMap = [
- 'minutes' => 'PT%dM',
- 'hours' => 'PT%dH',
- 'days' => 'P%dD',
- 'months' => 'P%dM',
- 'years' => 'P%dY',
- ];
-
- $ruleType = $executionRules['rule-type'];
- $ruleClass = strpos($ruleType, 'interval') === 0 ? 'interval' : $ruleType;
- $buildExpression = '';
-
- if ($ruleClass === 'interval')
- {
- // Rule type for intervals interval-
- $intervalType = explode('-', $ruleType)[1];
- $interval = $executionRules["interval-$intervalType"];
- $buildExpression = sprintf($intervalStringMap[$intervalType], $interval);
- }
-
- if ($ruleClass === 'cron-expression')
- {
- // ! custom matches are disabled in the form
- $matches = $executionRules['cron-expression'];
- $buildExpression .= $this->wildcardIfMatch($matches['minutes'], range(0, 59), true);
- $buildExpression .= ' ' . $this->wildcardIfMatch($matches['hours'], range(0, 23), true);
- $buildExpression .= ' ' . $this->wildcardIfMatch($matches['days_month'], range(1, 31), true);
- $buildExpression .= ' ' . $this->wildcardIfMatch($matches['months'], range(1, 12), true);
- $buildExpression .= ' ' . $this->wildcardIfMatch($matches['days_week'], range(0, 6), true);
- }
-
- return [
- 'type' => $ruleClass,
- 'exp' => $buildExpression,
- ];
- }
-
- /**
- * This method releases "locks" on a set of tasks from the database.
- * These locks are pseudo-locks that are used to keep a track of running tasks. However, they require require manual
- * intervention to release these locks in cases such as when a task process crashes, leaving the task "locked".
- *
- * @param array $pks A list of the primary keys to unlock.
- *
- * @return boolean True on success.
- *
- * @since 4.1.0
- * @throws \RuntimeException|\UnexpectedValueException|\BadMethodCallException
- */
- public function unlock(array &$pks): bool
- {
- /** @var TaskTable $table */
- $table = $this->getTable();
-
- $user = Factory::getApplication()->getIdentity();
-
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the change of state event.
- PluginHelper::importPlugin($this->events_map['unlock']);
-
- // Access checks.
- foreach ($pks as $i => $pk)
- {
- $table->reset();
-
- if ($table->load($pk))
- {
- if (!$this->canEditState($table))
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- // Prune items that are already at the given state.
- $lockedColumnName = $table->getColumnAlias('locked');
-
- if (property_exists($table, $lockedColumnName) && \is_null($table->get($lockedColumnName)))
- {
- unset($pks[$i]);
- }
- }
- }
-
- // Check if there are items to change.
- if (!\count($pks))
- {
- return true;
- }
-
- $event = AbstractEvent::create(
- $this->event_before_unlock,
- [
- 'subject' => $this,
- 'context' => $context,
- 'pks' => $pks,
- ]
- );
-
- try
- {
- Factory::getApplication()->getDispatcher()->dispatch($this->event_before_unlock, $event);
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Attempt to unlock the records.
- if (!$table->unlock($pks, $user->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after unlock event
- $event = AbstractEvent::create(
- $this->event_unlock,
- [
- 'subject' => $this,
- 'context' => $context,
- 'pks' => $pks,
- ]
- );
-
- try
- {
- Factory::getApplication()->getDispatcher()->dispatch($this->event_unlock, $event);
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Clear the component's cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Determine if an array is populated by all its possible values by comparison to a reference array, if found a
- * match a wildcard '*' is returned.
- *
- * @param array $target The target array
- * @param array $reference The reference array, populated by the complete set of possible values in $target
- * @param bool $targetToInt If true, converts $target array values to integers before comparing
- *
- * @return string A wildcard string if $target is fully populated, else $target itself.
- *
- * @since 4.1.0
- */
- private function wildcardIfMatch(array $target, array $reference, bool $targetToInt = false): string
- {
- if ($targetToInt)
- {
- $target = array_map(
- static function (string $x): int {
- return (int) $x;
- },
- $target
- );
- }
-
- $isMatch = array_diff($reference, $target) === [];
-
- return $isMatch ? "*" : implode(',', $target);
- }
-
- /**
- * Method to allow derived classes to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content'): void
- {
- // Load the 'task' plugin group
- PluginHelper::importPlugin('task');
-
- // Let the parent method take over
- parent::preprocessForm($form, $data, $group);
- }
+ /**
+ * Maps logical states to their values in the DB
+ * ? Do we end up using this?
+ *
+ * @var array
+ * @since 4.1.0
+ */
+ protected const TASK_STATES = [
+ 'enabled' => 1,
+ 'disabled' => 0,
+ 'trashed' => -2,
+ ];
+
+ /**
+ * The name of the database table with task records.
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ public const TASK_TABLE = '#__scheduler_tasks';
+
+ /**
+ * Prefix used with controller messages
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected $text_prefix = 'COM_SCHEDULER';
+
+ /**
+ * Type alias for content type
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ public $typeAlias = 'com_scheduler.task';
+
+ /**
+ * The Application object, for convenience
+ *
+ * @var AdministratorApplication $app
+ * @since 4.1.0
+ */
+ protected $app;
+
+ /**
+ * The event to trigger before unlocking the data.
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected $event_before_unlock = null;
+
+ /**
+ * The event to trigger after unlocking the data.
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected $event_unlock = null;
+
+ /**
+ * TaskModel constructor. Needed just to set $app
+ *
+ * @param array $config An array of configuration options
+ * @param MVCFactoryInterface|null $factory The factory
+ * @param FormFactoryInterface|null $formFactory The form factory
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, FormFactoryInterface $formFactory = null)
+ {
+ $config['events_map'] = $config['events_map'] ?? [];
+
+ $config['events_map'] = array_merge(
+ [
+ 'save' => 'task',
+ 'validate' => 'task',
+ 'unlock' => 'task',
+ ],
+ $config['events_map']
+ );
+
+ if (isset($config['event_before_unlock'])) {
+ $this->event_before_unlock = $config['event_before_unlock'];
+ } elseif (empty($this->event_before_unlock)) {
+ $this->event_before_unlock = 'onContentBeforeUnlock';
+ }
+
+ if (isset($config['event_unlock'])) {
+ $this->event_unlock = $config['event_unlock'];
+ } elseif (empty($this->event_unlock)) {
+ $this->event_unlock = 'onContentUnlock';
+ }
+
+ $this->app = Factory::getApplication();
+
+ parent::__construct($config, $factory, $formFactory);
+ }
+
+ /**
+ * Fetches the form object associated with this model. By default,
+ * loads the corresponding data from the DB and binds it with the form.
+ *
+ * @param array $data Data that needs to go into the form
+ * @param bool $loadData Should the form load its data from the DB?
+ *
+ * @return Form|boolean A JForm object on success, false on failure.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ Form::addFieldPath(JPATH_ADMINISTRATOR . 'components/com_scheduler/src/Field');
+
+ /**
+ * loadForm() (defined by FormBehaviourTrait) also loads the form data by calling
+ * loadFormData() : $data [implemented here] and binds it to the form by calling
+ * $form->bind($data).
+ */
+ $form = $this->loadForm('com_scheduler.task', 'task', ['control' => 'jform', 'load_data' => $loadData]);
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $user = $this->app->getIdentity();
+
+ // If new entry, set task type from state
+ if ($this->getState('task.id', 0) === 0 && $this->getState('task.type') !== null) {
+ $form->setValue('type', null, $this->getState('task.type'));
+ }
+
+ // @todo : Check if this is working as expected for new items (id == 0)
+ if (!$user->authorise('core.edit.state', 'com_scheduler.task.' . $this->getState('task.id'))) {
+ // Disable fields
+ $form->setFieldAttribute('state', 'disabled', 'true');
+
+ // No "hacking" ._.
+ $form->setFieldAttribute('state', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Determine whether a record may be deleted taking into consideration
+ * the user's permissions over the record.
+ *
+ * @param object $record The database row/record in question
+ *
+ * @return boolean True if the record may be deleted
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function canDelete($record): bool
+ {
+ // Record doesn't exist, can't delete
+ if (empty($record->id)) {
+ return false;
+ }
+
+ return $this->app->getIdentity()->authorise('core.delete', 'com_scheduler.task.' . $record->id);
+ }
+
+ /**
+ * Populate the model state, we use these instead of toying with input or the global state
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function populateState(): void
+ {
+ $app = $this->app;
+
+ $taskId = $app->getInput()->getInt('id');
+ $taskType = $app->getUserState('com_scheduler.add.task.task_type');
+
+ // @todo: Remove this. Get the option through a helper call.
+ $taskOption = $app->getUserState('com_scheduler.add.task.task_option');
+
+ $this->setState('task.id', $taskId);
+ $this->setState('task.type', $taskType);
+ $this->setState('task.option', $taskOption);
+
+ // Load component params, though com_scheduler does not (yet) have any params
+ $cParams = ComponentHelper::getParams($this->option);
+ $this->setState('params', $cParams);
+ }
+
+ /**
+ * Don't need to define this method since the parent getTable()
+ * implicitly deduces $name and $prefix anyways. This makes the object
+ * more transparent though.
+ *
+ * @param string $name Name of the table
+ * @param string $prefix Class prefix
+ * @param array $options Model config array
+ *
+ * @return Table
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function getTable($name = 'Task', $prefix = 'Table', $options = array()): Table
+ {
+ return parent::getTable($name, $prefix, $options);
+ }
+
+ /**
+ * Fetches the data to be injected into the form
+ *
+ * @return object Associative array of form data.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ $data = $this->app->getUserState('com_scheduler.edit.task.data', array());
+
+ // If the data from UserState is empty, we fetch it with getItem()
+ if (empty($data)) {
+ /** @var CMSObject $data */
+ $data = $this->getItem();
+
+ // @todo : further data processing goes here
+
+ // For a fresh object, set exec-day and exec-time
+ if (!($data->id ?? 0)) {
+ $data->execution_rules['exec-day'] = gmdate('d');
+ $data->execution_rules['exec-time'] = gmdate('H:i');
+ }
+ }
+
+ // Let plugins manipulate the data
+ $this->preprocessData('com_scheduler.task', $data, 'task');
+
+ return $data;
+ }
+
+ /**
+ * Overloads the parent getItem() method.
+ *
+ * @param integer $pk Primary key
+ *
+ * @return object|boolean Object on success, false on failure
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function getItem($pk = null)
+ {
+ $item = parent::getItem($pk);
+
+ if (!\is_object($item)) {
+ return false;
+ }
+
+ // Parent call leaves `execution_rules` and `cron_rules` JSON encoded
+ $item->set('execution_rules', json_decode($item->get('execution_rules', '')));
+ $item->set('cron_rules', json_decode($item->get('cron_rules', '')));
+
+ $taskOption = SchedulerHelper::getTaskOptions()->findOption(
+ ($item->id ?? 0) ? ($item->type ?? 0) : $this->getState('task.type')
+ );
+
+ $item->set('taskOption', $taskOption);
+
+ return $item;
+ }
+
+ /**
+ * Get a task from the database, only if an exclusive "lock" on the task can be acquired.
+ * The method supports options to customise the limitations on the fetch.
+ *
+ * @param array $options Array with options to fetch the task:
+ * 1. `id`: Optional id of the task to fetch.
+ * 2. `allowDisabled`: If true, disabled tasks can also be fetched.
+ * (default: false)
+ * 3. `bypassScheduling`: If true, tasks that are not due can also be
+ * fetched. Should only be true if an `id` is targeted instead of the
+ * task queue. (default: false)
+ * 4. `allowConcurrent`: If true, fetches even when another task is
+ * running ('locked'). (default: false)
+ * 5. `includeCliExclusive`: If true, can also fetch CLI exclusive tasks. (default: true)
+ *
+ * @return ?\stdClass Task entry as in the database.
+ *
+ * @since 4.1.0
+ * @throws UndefinedOptionsException|InvalidOptionsException
+ * @throws \RuntimeException
+ */
+ public function getTask(array $options = []): ?\stdClass
+ {
+ $resolver = new OptionsResolver();
+
+ try {
+ $this->configureTaskGetterOptions($resolver);
+ } catch (\Exception $e) {
+ }
+
+ try {
+ $options = $resolver->resolve($options);
+ } catch (\Exception $e) {
+ if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
+ throw $e;
+ }
+ }
+
+ $db = $this->getDatabase();
+ $now = Factory::getDate()->toSql();
+
+ // Get lock on the table to help with concurrency issues
+ $db->lockTable(self::TASK_TABLE);
+
+ // If concurrency is not allowed, we only get a task if another one does not have a "lock"
+ if (!$options['allowConcurrent']) {
+ // Get count of locked (presumed running) tasks
+ $lockCountQuery = $db->getQuery(true)
+ ->from($db->quoteName(self::TASK_TABLE))
+ ->select('COUNT(id)')
+ ->where($db->quoteName('locked') . ' IS NOT NULL');
+
+ try {
+ $runningCount = $db->setQuery($lockCountQuery)->loadResult();
+ } catch (\RuntimeException $e) {
+ $db->unlockTables();
+
+ return null;
+ }
+
+ if ($runningCount !== 0) {
+ $db->unlockTables();
+
+ return null;
+ }
+ }
+
+ $lockQuery = $db->getQuery(true);
+
+ $lockQuery->update($db->quoteName(self::TASK_TABLE))
+ ->set($db->quoteName('locked') . ' = :now1')
+ ->bind(':now1', $now);
+
+ // Array of all active routine ids
+ $activeRoutines = array_map(
+ static function (TaskOption $taskOption): string {
+ return $taskOption->id;
+ },
+ SchedulerHelper::getTaskOptions()->options
+ );
+
+ // "Orphaned" tasks are not a part of the task queue!
+ $lockQuery->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
+
+ // If directed, exclude CLI exclusive tasks
+ if (!$options['includeCliExclusive']) {
+ $lockQuery->where($db->quoteName('cli_exclusive') . ' = 0');
+ }
+
+ if (!$options['bypassScheduling']) {
+ $lockQuery->where($db->quoteName('next_execution') . ' <= :now2')
+ ->bind(':now2', $now);
+ }
+
+ if ($options['allowDisabled']) {
+ $lockQuery->whereIn($db->quoteName('state'), [0, 1]);
+ } else {
+ $lockQuery->where($db->quoteName('state') . ' = 1');
+ }
+
+ if ($options['id'] > 0) {
+ $lockQuery->where($db->quoteName('id') . ' = :taskId')
+ ->bind(':taskId', $options['id'], ParameterType::INTEGER);
+ } else {
+ // Pick from the front of the task queue if no 'id' is specified
+ // Get the id of the next task in the task queue
+ $idQuery = $db->getQuery(true)
+ ->from($db->quoteName(self::TASK_TABLE))
+ ->select($db->quoteName('id'))
+ ->where($db->quoteName('state') . ' = 1')
+ ->order($db->quoteName('priority') . ' DESC')
+ ->order($db->quoteName('next_execution') . ' ASC')
+ ->setLimit(1);
+
+ try {
+ $ids = $db->setQuery($idQuery)->loadColumn();
+ } catch (\RuntimeException $e) {
+ $db->unlockTables();
+
+ return null;
+ }
+
+ if (count($ids) === 0) {
+ $db->unlockTables();
+
+ return null;
+ }
+
+ $lockQuery->whereIn($db->quoteName('id'), $ids);
+ }
+
+ try {
+ $db->setQuery($lockQuery)->execute();
+ } catch (\RuntimeException $e) {
+ } finally {
+ $affectedRows = $db->getAffectedRows();
+
+ $db->unlockTables();
+ }
+
+ if ($affectedRows != 1) {
+ /*
+ // @todo
+ // ? Fatal failure handling here?
+ // ! Question is, how? If we check for tasks running beyond there time here, we have no way of
+ // ! what's already been notified (since we're not auto-unlocking/recovering tasks anymore).
+ // The solution __may__ be in a "last_successful_finish" (or something) column.
+ */
+
+ return null;
+ }
+
+ $getQuery = $db->getQuery(true);
+
+ $getQuery->select('*')
+ ->from($db->quoteName(self::TASK_TABLE))
+ ->where($db->quoteName('locked') . ' = :now')
+ ->bind(':now', $now);
+
+ $task = $db->setQuery($getQuery)->loadObject();
+
+ $task->execution_rules = json_decode($task->execution_rules);
+ $task->cron_rules = json_decode($task->cron_rules);
+
+ $task->taskOption = SchedulerHelper::getTaskOptions()->findOption($task->type);
+
+ return $task;
+ }
+
+ /**
+ * Set up an {@see OptionsResolver} to resolve options compatible with the {@see GetTask()} method.
+ *
+ * @param OptionsResolver $resolver The {@see OptionsResolver} instance to set up.
+ *
+ * @return OptionsResolver
+ *
+ * @since 4.1.0
+ * @throws AccessException
+ */
+ public static function configureTaskGetterOptions(OptionsResolver $resolver): OptionsResolver
+ {
+ $resolver->setDefaults(
+ [
+ 'id' => 0,
+ 'allowDisabled' => false,
+ 'bypassScheduling' => false,
+ 'allowConcurrent' => false,
+ 'includeCliExclusive' => true,
+ ]
+ )
+ ->setAllowedTypes('id', 'numeric')
+ ->setAllowedTypes('allowDisabled', 'bool')
+ ->setAllowedTypes('bypassScheduling', 'bool')
+ ->setAllowedTypes('allowConcurrent', 'bool')
+ ->setAllowedTypes('includeCliExclusive', 'bool');
+
+ return $resolver;
+ }
+
+ /**
+ * @param array $data The form data
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function save($data): bool
+ {
+ $id = (int) ($data['id'] ?? $this->getState('task.id'));
+ $isNew = $id === 0;
+
+ // Clean up execution rules
+ $data['execution_rules'] = $this->processExecutionRules($data['execution_rules']);
+
+ // If a new entry, we'll have to put in place a pseudo-last_execution
+ if ($isNew) {
+ $basisDayOfMonth = $data['execution_rules']['exec-day'];
+ [$basisHour, $basisMinute] = explode(':', $data['execution_rules']['exec-time']);
+
+ $data['last_execution'] = Factory::getDate('now', 'GMT')->format('Y-m')
+ . "-$basisDayOfMonth $basisHour:$basisMinute:00";
+ } else {
+ $data['last_execution'] = $this->getItem($id)->last_execution;
+ }
+
+ // Build the `cron_rules` column from `execution_rules`
+ $data['cron_rules'] = $this->buildExecutionRules($data['execution_rules']);
+
+ // `next_execution` would be null if scheduling is disabled with the "manual" rule!
+ $data['next_execution'] = (new ExecRuleHelper($data))->nextExec();
+
+ if ($isNew) {
+ $data['last_execution'] = null;
+ }
+
+ // If no params, we set as empty array.
+ // ? Is this the right place to do this
+ $data['params'] = $data['params'] ?? [];
+
+ // Parent method takes care of saving to the table
+ return parent::save($data);
+ }
+
+ /**
+ * Clean up and standardise execution rules
+ *
+ * @param array $unprocessedRules The form data [? can just replace with execution_interval]
+ *
+ * @return array Processed rules
+ *
+ * @since 4.1.0
+ */
+ private function processExecutionRules(array $unprocessedRules): array
+ {
+ $executionRules = $unprocessedRules;
+
+ $ruleType = $executionRules['rule-type'];
+ $retainKeys = ['rule-type', $ruleType, 'exec-day', 'exec-time'];
+ $executionRules = array_intersect_key($executionRules, array_flip($retainKeys));
+
+ // Default to current date-time in UTC/GMT as the basis
+ $executionRules['exec-day'] = $executionRules['exec-day'] ?: (string) gmdate('d');
+ $executionRules['exec-time'] = $executionRules['exec-time'] ?: (string) gmdate('H:i');
+
+ // If custom ruleset, sort it
+ // ? Is this necessary
+ if ($ruleType === 'cron-expression') {
+ foreach ($executionRules['cron-expression'] as &$values) {
+ sort($values);
+ }
+ }
+
+ return $executionRules;
+ }
+
+ /**
+ * Private method to build execution expression from input execution rules.
+ * This expression is used internally to determine execution times/conditions.
+ *
+ * @param array $executionRules Execution rules from the Task form, post-processing.
+ *
+ * @return array
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ private function buildExecutionRules(array $executionRules): array
+ {
+ // Maps interval strings, use with sprintf($map[intType], $interval)
+ $intervalStringMap = [
+ 'minutes' => 'PT%dM',
+ 'hours' => 'PT%dH',
+ 'days' => 'P%dD',
+ 'months' => 'P%dM',
+ 'years' => 'P%dY',
+ ];
+
+ $ruleType = $executionRules['rule-type'];
+ $ruleClass = strpos($ruleType, 'interval') === 0 ? 'interval' : $ruleType;
+ $buildExpression = '';
+
+ if ($ruleClass === 'interval') {
+ // Rule type for intervals interval-
+ $intervalType = explode('-', $ruleType)[1];
+ $interval = $executionRules["interval-$intervalType"];
+ $buildExpression = sprintf($intervalStringMap[$intervalType], $interval);
+ }
+
+ if ($ruleClass === 'cron-expression') {
+ // ! custom matches are disabled in the form
+ $matches = $executionRules['cron-expression'];
+ $buildExpression .= $this->wildcardIfMatch($matches['minutes'], range(0, 59), true);
+ $buildExpression .= ' ' . $this->wildcardIfMatch($matches['hours'], range(0, 23), true);
+ $buildExpression .= ' ' . $this->wildcardIfMatch($matches['days_month'], range(1, 31), true);
+ $buildExpression .= ' ' . $this->wildcardIfMatch($matches['months'], range(1, 12), true);
+ $buildExpression .= ' ' . $this->wildcardIfMatch($matches['days_week'], range(0, 6), true);
+ }
+
+ return [
+ 'type' => $ruleClass,
+ 'exp' => $buildExpression,
+ ];
+ }
+
+ /**
+ * This method releases "locks" on a set of tasks from the database.
+ * These locks are pseudo-locks that are used to keep a track of running tasks. However, they require require manual
+ * intervention to release these locks in cases such as when a task process crashes, leaving the task "locked".
+ *
+ * @param array $pks A list of the primary keys to unlock.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.1.0
+ * @throws \RuntimeException|\UnexpectedValueException|\BadMethodCallException
+ */
+ public function unlock(array &$pks): bool
+ {
+ /** @var TaskTable $table */
+ $table = $this->getTable();
+
+ $user = Factory::getApplication()->getIdentity();
+
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the change of state event.
+ PluginHelper::importPlugin($this->events_map['unlock']);
+
+ // Access checks.
+ foreach ($pks as $i => $pk) {
+ $table->reset();
+
+ if ($table->load($pk)) {
+ if (!$this->canEditState($table)) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ // Prune items that are already at the given state.
+ $lockedColumnName = $table->getColumnAlias('locked');
+
+ if (property_exists($table, $lockedColumnName) && \is_null($table->get($lockedColumnName))) {
+ unset($pks[$i]);
+ }
+ }
+ }
+
+ // Check if there are items to change.
+ if (!\count($pks)) {
+ return true;
+ }
+
+ $event = AbstractEvent::create(
+ $this->event_before_unlock,
+ [
+ 'subject' => $this,
+ 'context' => $context,
+ 'pks' => $pks,
+ ]
+ );
+
+ try {
+ Factory::getApplication()->getDispatcher()->dispatch($this->event_before_unlock, $event);
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Attempt to unlock the records.
+ if (!$table->unlock($pks, $user->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after unlock event
+ $event = AbstractEvent::create(
+ $this->event_unlock,
+ [
+ 'subject' => $this,
+ 'context' => $context,
+ 'pks' => $pks,
+ ]
+ );
+
+ try {
+ Factory::getApplication()->getDispatcher()->dispatch($this->event_unlock, $event);
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Clear the component's cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Determine if an array is populated by all its possible values by comparison to a reference array, if found a
+ * match a wildcard '*' is returned.
+ *
+ * @param array $target The target array
+ * @param array $reference The reference array, populated by the complete set of possible values in $target
+ * @param bool $targetToInt If true, converts $target array values to integers before comparing
+ *
+ * @return string A wildcard string if $target is fully populated, else $target itself.
+ *
+ * @since 4.1.0
+ */
+ private function wildcardIfMatch(array $target, array $reference, bool $targetToInt = false): string
+ {
+ if ($targetToInt) {
+ $target = array_map(
+ static function (string $x): int {
+ return (int) $x;
+ },
+ $target
+ );
+ }
+
+ $isMatch = array_diff($reference, $target) === [];
+
+ return $isMatch ? "*" : implode(',', $target);
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content'): void
+ {
+ // Load the 'task' plugin group
+ PluginHelper::importPlugin('task');
+
+ // Let the parent method take over
+ parent::preprocessForm($form, $data, $group);
+ }
}
diff --git a/administrator/components/com_scheduler/src/Model/TasksModel.php b/administrator/components/com_scheduler/src/Model/TasksModel.php
index e8f635d636830..36b1539d5bc18 100644
--- a/administrator/components/com_scheduler/src/Model/TasksModel.php
+++ b/administrator/components/com_scheduler/src/Model/TasksModel.php
@@ -1,4 +1,5 @@
getState('filter.search');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.type');
- $id .= ':' . $this->getState('filter.orphaned');
- $id .= ':' . $this->getState('filter.due');
- $id .= ':' . $this->getState('filter.locked');
- $id .= ':' . $this->getState('filter.trigger');
- $id .= ':' . $this->getState('list.select');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to create a query for a list of items.
- *
- * @return QueryInterface
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function getListQuery(): QueryInterface
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- /**
- * Select the required fields from the table.
- * ? Do we need all these defaults ?
- * ? Does 'list.select' exist ?
- */
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.asset_id'),
- $db->quoteName('a.title'),
- $db->quoteName('a.type'),
- $db->quoteName('a.execution_rules'),
- $db->quoteName('a.state'),
- $db->quoteName('a.last_exit_code'),
- $db->quoteName('a.locked'),
- $db->quoteName('a.last_execution'),
- $db->quoteName('a.next_execution'),
- $db->quoteName('a.times_executed'),
- $db->quoteName('a.times_failed'),
- $db->quoteName('a.priority'),
- $db->quoteName('a.ordering'),
- $db->quoteName('a.note'),
- $db->quoteName('a.checked_out'),
- $db->quoteName('a.checked_out_time'),
- ]
- )
- )
- ->select(
- [
- $db->quoteName('uc.name', 'editor'),
- ]
- )
- ->from($db->quoteName('#__scheduler_tasks', 'a'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
-
- // Filters go below
- $filterCount = 0;
-
- /**
- * Extends query if already filtered.
- *
- * @param string $outerGlue
- * @param array $conditions
- * @param string $innerGlue
- *
- * @since 4.1.0
- */
- $extendWhereIfFiltered = static function (
- string $outerGlue,
- array $conditions,
- string $innerGlue
- ) use ($query, &$filterCount) {
- if ($filterCount++)
- {
- $query->extendWhere($outerGlue, $conditions, $innerGlue);
- }
- else
- {
- $query->where($conditions, $innerGlue);
- }
-
- };
-
- // Filter over ID, title (redundant to search, but) ---
- if (is_numeric($id = $this->getState('filter.id')))
- {
- $filterCount++;
- $id = (int) $id;
- $query->where($db->qn('a.id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- }
- elseif ($title = $this->getState('filter.title'))
- {
- $filterCount++;
- $match = "%$title%";
- $query->where($db->qn('a.title') . ' LIKE :match')
- ->bind(':match', $match);
- }
-
- // Filter orphaned (-1: exclude, 0: include, 1: only) ----
- $filterOrphaned = (int) $this->getState('filter.orphaned');
-
- if ($filterOrphaned !== 0)
- {
- $filterCount++;
- $taskOptions = SchedulerHelper::getTaskOptions();
-
- // Array of all active routine ids
- $activeRoutines = array_map(
- static function (TaskOption $taskOption): string
- {
- return $taskOption->id;
- },
- $taskOptions->options
- );
-
- if ($filterOrphaned === -1)
- {
- $query->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
- }
- else
- {
- $query->whereNotIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
- }
- }
-
- // Filter over state ----
- $state = $this->getState('filter.state');
-
- if ($state !== '*')
- {
- $filterCount++;
-
- if (is_numeric($state))
- {
- $state = (int) $state;
-
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
- else
- {
- $query->whereIn($db->quoteName('a.state'), [0, 1]);
- }
- }
-
- // Filter over type ----
- $typeFilter = $this->getState('filter.type');
-
- if ($typeFilter)
- {
- $filterCount++;
- $query->where($db->quotename('a.type') . '= :type')
- ->bind(':type', $typeFilter);
- }
-
- // Filter over exit code ----
- $exitCode = $this->getState('filter.last_exit_code');
-
- if (is_numeric($exitCode))
- {
- $filterCount++;
- $exitCode = (int) $exitCode;
- $query->where($db->quoteName('a.last_exit_code') . '= :last_exit_code')
- ->bind(':last_exit_code', $exitCode, ParameterType::INTEGER);
- }
-
- // Filter due (-1: exclude, 0: include, 1: only) ----
- $due = $this->getState('filter.due');
-
- if (is_numeric($due) && $due != 0)
- {
- $now = Factory::getDate('now', 'GMT')->toSql();
- $operator = $due == 1 ? ' <= ' : ' > ';
- $filterCount++;
- $query->where($db->qn('a.next_execution') . $operator . ':now')
- ->bind(':now', $now);
- }
-
- /*
- * Filter locked ---
- * Locks can be either hard locks or soft locks. Locks that have expired (exceeded the task timeout) are soft
- * locks. Hard-locked tasks are assumed to be running. Soft-locked tasks are assumed to have suffered a fatal
- * failure.
- * {-2: exclude-all, -1: exclude-hard-locked, 0: include, 1: include-only-locked, 2: include-only-soft-locked}
- */
- $locked = $this->getState('filter.locked');
-
- if (is_numeric($locked) && $locked != 0)
- {
- $now = Factory::getDate('now', 'GMT');
- $timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
- $timeout = new \DateInterval(sprintf('PT%dS', $timeout));
- $timeoutThreshold = (clone $now)->sub($timeout)->toSql();
- $now = $now->toSql();
-
- switch ($locked)
- {
- case -2:
- $query->where($db->qn('a.locked') . 'IS NULL');
- break;
- case -1:
- $extendWhereIfFiltered(
- 'AND',
- [
- $db->qn('a.locked') . ' IS NULL',
- $db->qn('a.locked') . ' < :threshold',
- ],
- 'OR'
- );
- $query->bind(':threshold', $timeoutThreshold);
- break;
- case 1:
- $query->where($db->qn('a.locked') . ' IS NOT NULL');
- break;
- case 2:
- $query->where($db->qn('a.locked') . ' < :threshold')
- ->bind(':threshold', $timeoutThreshold);
- }
- }
-
- // Filter over search string if set (title, type title, note, id) ----
- $searchStr = $this->getState('filter.search');
-
- if (!empty($searchStr))
- {
- // Allow search by ID
- if (stripos($searchStr, 'id:') === 0)
- {
- // Add array support [?]
- $id = (int) substr($searchStr, 3);
- $query->where($db->quoteName('a.id') . '= :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- }
- // Search by type is handled exceptionally in _getList() [@todo: remove refs]
- elseif (stripos($searchStr, 'type:') !== 0)
- {
- $searchStr = "%$searchStr%";
-
- // Bind keys to query
- $query->bind(':title', $searchStr)
- ->bind(':note', $searchStr);
- $conditions = [
- $db->quoteName('a.title') . ' LIKE :title',
- $db->quoteName('a.note') . ' LIKE :note',
- ];
- $extendWhereIfFiltered('AND', $conditions, 'OR');
- }
- }
-
- // Add list ordering clause. ----
- // @todo implement multi-column ordering someway
- $multiOrdering = $this->state->get('list.multi_ordering');
-
- if (!$multiOrdering || !\is_array($multiOrdering))
- {
- $orderCol = $this->state->get('list.ordering', 'a.title');
- $orderDir = $this->state->get('list.direction', 'asc');
-
- // Type title ordering is handled exceptionally in _getList()
- if ($orderCol !== 'j.type_title')
- {
- $query->order($db->quoteName($orderCol) . ' ' . $orderDir);
-
- // If ordering by type or state, also order by title.
- if (\in_array($orderCol, ['a.type', 'a.state', 'a.priority']))
- {
- // @todo : Test if things are working as expected
- $query->order($db->quoteName('a.title') . ' ' . $orderDir);
- }
- }
- }
- else
- {
- // @todo Should add quoting here
- $query->order($multiOrdering);
- }
-
- return $query;
- }
-
- /**
- * Overloads the parent _getList() method.
- * Takes care of attaching TaskOption objects and sorting by type titles.
- *
- * @param DatabaseQuery $query The database query to get the list with
- * @param int $limitstart The list offset
- * @param int $limit Number of list items to fetch
- *
- * @return object[]
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function _getList($query, $limitstart = 0, $limit = 0): array
- {
- // Get stuff from the model state
- $listOrder = $this->getState('list.ordering', 'a.title');
- $listDirectionN = strtolower($this->getState('list.direction', 'asc')) == 'desc' ? -1 : 1;
-
- // Set limit parameters and get object list
- $query->setLimit($limit, $limitstart);
- $this->getDatabase()->setQuery($query);
-
- // Return optionally an extended class.
- // @todo: Use something other than CMSObject..
- if ($this->getState('list.customClass'))
- {
- $responseList = array_map(
- static function (array $arr) {
- $o = new CMSObject;
-
- foreach ($arr as $k => $v)
- {
- $o->{$k} = $v;
- }
-
- return $o;
- },
- $this->getDatabase()->loadAssocList() ?: []
- );
- }
- else
- {
- $responseList = $this->getDatabase()->loadObjectList();
- }
-
- // Attach TaskOptions objects and a safe type title
- $this->attachTaskOptions($responseList);
-
- // If ordering by non-db fields, we need to sort here in code
- if ($listOrder == 'j.type_title')
- {
- $responseList = ArrayHelper::sortObjects($responseList, 'safeTypeTitle', $listDirectionN, true, false);
- }
-
- return $responseList;
- }
-
- /**
- * For an array of items, attaches TaskOption objects and (safe) type titles to each.
- *
- * @param array $items Array of items, passed by reference
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- private function attachTaskOptions(array $items): void
- {
- $taskOptions = SchedulerHelper::getTaskOptions();
-
- foreach ($items as $item)
- {
- $item->taskOption = $taskOptions->findOption($item->type);
- $item->safeTypeTitle = $item->taskOption->title ?? Text::_('JGLOBAL_NONAPPLICABLE');
- }
- }
-
- /**
- * Proxy for the parent method.
- * Sets ordering defaults.
- *
- * @param string $ordering Field to order/sort list by
- * @param string $direction Direction in which to sort list
- *
- * @return void
- * @since 4.1.0
- */
- protected function populateState($ordering = 'a.title', $direction = 'ASC'): void
- {
- // Call the parent method
- parent::populateState($ordering, $direction);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface|null $factory The factory.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ * @see \JControllerLegacy
+ */
+ public function __construct($config = [], MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = [
+ 'id', 'a.id',
+ 'asset_id', 'a.asset_id',
+ 'title', 'a.title',
+ 'type', 'a.type',
+ 'type_title', 'j.type_title',
+ 'state', 'a.state',
+ 'last_exit_code', 'a.last_exit_code',
+ 'last_execution', 'a.last_execution',
+ 'next_execution', 'a.next_execution',
+ 'times_executed', 'a.times_executed',
+ 'times_failed', 'a.times_failed',
+ 'ordering', 'a.ordering',
+ 'priority', 'a.priority',
+ 'note', 'a.note',
+ 'created', 'a.created',
+ 'created_by', 'a.created_by',
+ ];
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 4.1.0
+ */
+ protected function getStoreId($id = ''): string
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.type');
+ $id .= ':' . $this->getState('filter.orphaned');
+ $id .= ':' . $this->getState('filter.due');
+ $id .= ':' . $this->getState('filter.locked');
+ $id .= ':' . $this->getState('filter.trigger');
+ $id .= ':' . $this->getState('list.select');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to create a query for a list of items.
+ *
+ * @return QueryInterface
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function getListQuery(): QueryInterface
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ /**
+ * Select the required fields from the table.
+ * ? Do we need all these defaults ?
+ * ? Does 'list.select' exist ?
+ */
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.asset_id'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.type'),
+ $db->quoteName('a.execution_rules'),
+ $db->quoteName('a.state'),
+ $db->quoteName('a.last_exit_code'),
+ $db->quoteName('a.locked'),
+ $db->quoteName('a.last_execution'),
+ $db->quoteName('a.next_execution'),
+ $db->quoteName('a.times_executed'),
+ $db->quoteName('a.times_failed'),
+ $db->quoteName('a.priority'),
+ $db->quoteName('a.ordering'),
+ $db->quoteName('a.note'),
+ $db->quoteName('a.checked_out'),
+ $db->quoteName('a.checked_out_time'),
+ ]
+ )
+ )
+ ->select(
+ [
+ $db->quoteName('uc.name', 'editor'),
+ ]
+ )
+ ->from($db->quoteName('#__scheduler_tasks', 'a'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
+
+ // Filters go below
+ $filterCount = 0;
+
+ /**
+ * Extends query if already filtered.
+ *
+ * @param string $outerGlue
+ * @param array $conditions
+ * @param string $innerGlue
+ *
+ * @since 4.1.0
+ */
+ $extendWhereIfFiltered = static function (
+ string $outerGlue,
+ array $conditions,
+ string $innerGlue
+ ) use (
+ $query,
+ &$filterCount
+) {
+ if ($filterCount++) {
+ $query->extendWhere($outerGlue, $conditions, $innerGlue);
+ } else {
+ $query->where($conditions, $innerGlue);
+ }
+ };
+
+ // Filter over ID, title (redundant to search, but) ---
+ if (is_numeric($id = $this->getState('filter.id'))) {
+ $filterCount++;
+ $id = (int) $id;
+ $query->where($db->qn('a.id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ } elseif ($title = $this->getState('filter.title')) {
+ $filterCount++;
+ $match = "%$title%";
+ $query->where($db->qn('a.title') . ' LIKE :match')
+ ->bind(':match', $match);
+ }
+
+ // Filter orphaned (-1: exclude, 0: include, 1: only) ----
+ $filterOrphaned = (int) $this->getState('filter.orphaned');
+
+ if ($filterOrphaned !== 0) {
+ $filterCount++;
+ $taskOptions = SchedulerHelper::getTaskOptions();
+
+ // Array of all active routine ids
+ $activeRoutines = array_map(
+ static function (TaskOption $taskOption): string {
+ return $taskOption->id;
+ },
+ $taskOptions->options
+ );
+
+ if ($filterOrphaned === -1) {
+ $query->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
+ } else {
+ $query->whereNotIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);
+ }
+ }
+
+ // Filter over state ----
+ $state = $this->getState('filter.state');
+
+ if ($state !== '*') {
+ $filterCount++;
+
+ if (is_numeric($state)) {
+ $state = (int) $state;
+
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ } else {
+ $query->whereIn($db->quoteName('a.state'), [0, 1]);
+ }
+ }
+
+ // Filter over type ----
+ $typeFilter = $this->getState('filter.type');
+
+ if ($typeFilter) {
+ $filterCount++;
+ $query->where($db->quotename('a.type') . '= :type')
+ ->bind(':type', $typeFilter);
+ }
+
+ // Filter over exit code ----
+ $exitCode = $this->getState('filter.last_exit_code');
+
+ if (is_numeric($exitCode)) {
+ $filterCount++;
+ $exitCode = (int) $exitCode;
+ $query->where($db->quoteName('a.last_exit_code') . '= :last_exit_code')
+ ->bind(':last_exit_code', $exitCode, ParameterType::INTEGER);
+ }
+
+ // Filter due (-1: exclude, 0: include, 1: only) ----
+ $due = $this->getState('filter.due');
+
+ if (is_numeric($due) && $due != 0) {
+ $now = Factory::getDate('now', 'GMT')->toSql();
+ $operator = $due == 1 ? ' <= ' : ' > ';
+ $filterCount++;
+ $query->where($db->qn('a.next_execution') . $operator . ':now')
+ ->bind(':now', $now);
+ }
+
+ /*
+ * Filter locked ---
+ * Locks can be either hard locks or soft locks. Locks that have expired (exceeded the task timeout) are soft
+ * locks. Hard-locked tasks are assumed to be running. Soft-locked tasks are assumed to have suffered a fatal
+ * failure.
+ * {-2: exclude-all, -1: exclude-hard-locked, 0: include, 1: include-only-locked, 2: include-only-soft-locked}
+ */
+ $locked = $this->getState('filter.locked');
+
+ if (is_numeric($locked) && $locked != 0) {
+ $now = Factory::getDate('now', 'GMT');
+ $timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
+ $timeout = new \DateInterval(sprintf('PT%dS', $timeout));
+ $timeoutThreshold = (clone $now)->sub($timeout)->toSql();
+ $now = $now->toSql();
+
+ switch ($locked) {
+ case -2:
+ $query->where($db->qn('a.locked') . 'IS NULL');
+ break;
+ case -1:
+ $extendWhereIfFiltered(
+ 'AND',
+ [
+ $db->qn('a.locked') . ' IS NULL',
+ $db->qn('a.locked') . ' < :threshold',
+ ],
+ 'OR'
+ );
+ $query->bind(':threshold', $timeoutThreshold);
+ break;
+ case 1:
+ $query->where($db->qn('a.locked') . ' IS NOT NULL');
+ break;
+ case 2:
+ $query->where($db->qn('a.locked') . ' < :threshold')
+ ->bind(':threshold', $timeoutThreshold);
+ }
+ }
+
+ // Filter over search string if set (title, type title, note, id) ----
+ $searchStr = $this->getState('filter.search');
+
+ if (!empty($searchStr)) {
+ // Allow search by ID
+ if (stripos($searchStr, 'id:') === 0) {
+ // Add array support [?]
+ $id = (int) substr($searchStr, 3);
+ $query->where($db->quoteName('a.id') . '= :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ } elseif (stripos($searchStr, 'type:') !== 0) {
+ // Search by type is handled exceptionally in _getList() [@todo: remove refs]
+ $searchStr = "%$searchStr%";
+
+ // Bind keys to query
+ $query->bind(':title', $searchStr)
+ ->bind(':note', $searchStr);
+ $conditions = [
+ $db->quoteName('a.title') . ' LIKE :title',
+ $db->quoteName('a.note') . ' LIKE :note',
+ ];
+ $extendWhereIfFiltered('AND', $conditions, 'OR');
+ }
+ }
+
+ // Add list ordering clause. ----
+ // @todo implement multi-column ordering someway
+ $multiOrdering = $this->state->get('list.multi_ordering');
+
+ if (!$multiOrdering || !\is_array($multiOrdering)) {
+ $orderCol = $this->state->get('list.ordering', 'a.title');
+ $orderDir = $this->state->get('list.direction', 'asc');
+
+ // Type title ordering is handled exceptionally in _getList()
+ if ($orderCol !== 'j.type_title') {
+ $query->order($db->quoteName($orderCol) . ' ' . $orderDir);
+
+ // If ordering by type or state, also order by title.
+ if (\in_array($orderCol, ['a.type', 'a.state', 'a.priority'])) {
+ // @todo : Test if things are working as expected
+ $query->order($db->quoteName('a.title') . ' ' . $orderDir);
+ }
+ }
+ } else {
+ // @todo Should add quoting here
+ $query->order($multiOrdering);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Overloads the parent _getList() method.
+ * Takes care of attaching TaskOption objects and sorting by type titles.
+ *
+ * @param DatabaseQuery $query The database query to get the list with
+ * @param int $limitstart The list offset
+ * @param int $limit Number of list items to fetch
+ *
+ * @return object[]
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function _getList($query, $limitstart = 0, $limit = 0): array
+ {
+ // Get stuff from the model state
+ $listOrder = $this->getState('list.ordering', 'a.title');
+ $listDirectionN = strtolower($this->getState('list.direction', 'asc')) == 'desc' ? -1 : 1;
+
+ // Set limit parameters and get object list
+ $query->setLimit($limit, $limitstart);
+ $this->getDatabase()->setQuery($query);
+
+ // Return optionally an extended class.
+ // @todo: Use something other than CMSObject..
+ if ($this->getState('list.customClass')) {
+ $responseList = array_map(
+ static function (array $arr) {
+ $o = new CMSObject();
+
+ foreach ($arr as $k => $v) {
+ $o->{$k} = $v;
+ }
+
+ return $o;
+ },
+ $this->getDatabase()->loadAssocList() ?: []
+ );
+ } else {
+ $responseList = $this->getDatabase()->loadObjectList();
+ }
+
+ // Attach TaskOptions objects and a safe type title
+ $this->attachTaskOptions($responseList);
+
+ // If ordering by non-db fields, we need to sort here in code
+ if ($listOrder == 'j.type_title') {
+ $responseList = ArrayHelper::sortObjects($responseList, 'safeTypeTitle', $listDirectionN, true, false);
+ }
+
+ return $responseList;
+ }
+
+ /**
+ * For an array of items, attaches TaskOption objects and (safe) type titles to each.
+ *
+ * @param array $items Array of items, passed by reference
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ private function attachTaskOptions(array $items): void
+ {
+ $taskOptions = SchedulerHelper::getTaskOptions();
+
+ foreach ($items as $item) {
+ $item->taskOption = $taskOptions->findOption($item->type);
+ $item->safeTypeTitle = $item->taskOption->title ?? Text::_('JGLOBAL_NONAPPLICABLE');
+ }
+ }
+
+ /**
+ * Proxy for the parent method.
+ * Sets ordering defaults.
+ *
+ * @param string $ordering Field to order/sort list by
+ * @param string $direction Direction in which to sort list
+ *
+ * @return void
+ * @since 4.1.0
+ */
+ protected function populateState($ordering = 'a.title', $direction = 'ASC'): void
+ {
+ // Call the parent method
+ parent::populateState($ordering, $direction);
+ }
}
diff --git a/administrator/components/com_scheduler/src/Rule/ExecutionRulesRule.php b/administrator/components/com_scheduler/src/Rule/ExecutionRulesRule.php
index 56f149993cb3a..294f0d4c19cd1 100644
--- a/administrator/components/com_scheduler/src/Rule/ExecutionRulesRule.php
+++ b/administrator/components/com_scheduler/src/Rule/ExecutionRulesRule.php
@@ -1,4 +1,5 @@
` tag for the form
- * field object.
- * @param mixed $value The form field value to validate.
- * @param ?string $group The field name group control value. This acts as an array container for the
- * field. For example if the field has `name="foo"` and the group value is set
- * to "bar" then the full field name would end up being "bar[foo]".
- * @param ?Registry $input An optional Registry object with the entire data set to validate against
- * the entire form.
- * @param ?Form $form The form object for which the field is being tested.
- *
- * @return boolean
- *
- * @since 4.1.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null): bool
- {
- $fieldName = (string) $element['name'];
- $ruleType = $input->get(self::RULE_TYPE_FIELD);
+ /**
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form
+ * field object.
+ * @param mixed $value The form field value to validate.
+ * @param ?string $group The field name group control value. This acts as an array container for the
+ * field. For example if the field has `name="foo"` and the group value is set
+ * to "bar" then the full field name would end up being "bar[foo]".
+ * @param ?Registry $input An optional Registry object with the entire data set to validate against
+ * the entire form.
+ * @param ?Form $form The form object for which the field is being tested.
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null): bool
+ {
+ $fieldName = (string) $element['name'];
+ $ruleType = $input->get(self::RULE_TYPE_FIELD);
- if ($ruleType === $fieldName || ($ruleType === 'custom' && $group === self::CUSTOM_RULE_GROUP))
- {
- return $this->validateField($element, $value, $group, $form);
- }
+ if ($ruleType === $fieldName || ($ruleType === 'custom' && $group === self::CUSTOM_RULE_GROUP)) {
+ return $this->validateField($element, $value, $group, $form);
+ }
- return true;
- }
+ return true;
+ }
- /**
- * @param \SimpleXMLElement $element The SimpleXMLElement for the field.
- * @param mixed $value The field value.
- * @param ?string $group The form field group the element belongs to.
- * @param Form|null $form The Form object against which the field is tested/
- *
- * @return boolean True if field is valid
- *
- * @since 4.1.0
- */
- private function validateField(\SimpleXMLElement $element, $value, ?string $group = null, ?Form $form = null): bool
- {
- $elementType = (string) $element['type'];
+ /**
+ * @param \SimpleXMLElement $element The SimpleXMLElement for the field.
+ * @param mixed $value The field value.
+ * @param ?string $group The form field group the element belongs to.
+ * @param Form|null $form The Form object against which the field is tested/
+ *
+ * @return boolean True if field is valid
+ *
+ * @since 4.1.0
+ */
+ private function validateField(\SimpleXMLElement $element, $value, ?string $group = null, ?Form $form = null): bool
+ {
+ $elementType = (string) $element['type'];
- // If element is of cron type, we test against options and return
- if ($elementType === 'cron')
- {
- return (new OptionsRule)->test($element, $value, $group, null, $form);
- }
+ // If element is of cron type, we test against options and return
+ if ($elementType === 'cron') {
+ return (new OptionsRule())->test($element, $value, $group, null, $form);
+ }
- // Test for a positive integer value and return
- return filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
- }
+ // Test for a positive integer value and return
+ return filter_var($value, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
+ }
}
diff --git a/administrator/components/com_scheduler/src/Scheduler/Scheduler.php b/administrator/components/com_scheduler/src/Scheduler/Scheduler.php
index ea1c68f8dc4f3..851b325ced560 100644
--- a/administrator/components/com_scheduler/src/Scheduler/Scheduler.php
+++ b/administrator/components/com_scheduler/src/Scheduler/Scheduler.php
@@ -1,4 +1,5 @@
'COM_SCHEDULER_SCHEDULER_TASK_COMPLETE',
- Status::WILL_RESUME => 'COM_SCHEDULER_SCHEDULER_TASK_WILL_RESUME',
- Status::NO_LOCK => 'COM_SCHEDULER_SCHEDULER_TASK_LOCKED',
- Status::NO_RUN => 'COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED',
- Status::NO_ROUTINE => 'COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA',
- ];
-
- /**
- * Filters for the task queue. Can be used with fetchTaskRecords().
- *
- * @since 4.1.0
- * @todo remove?
- */
- public const TASK_QUEUE_FILTERS = [
- 'due' => 1,
- 'locked' => -1,
- ];
-
- /**
- * List config for the task queue. Can be used with fetchTaskRecords().
- *
- * @since 4.1.0
- * @todo remove?
- */
- public const TASK_QUEUE_LIST_CONFIG = [
- 'multi_ordering' => ['a.priority DESC ', 'a.next_execution ASC'],
- ];
-
- /**
- * Run a scheduled task.
- * Runs a single due task from the task queue by default if $id and $title are not passed.
- *
- * @param array $options Array with options to configure the method's behavior. Supports:
- * 1. `id`: (Optional) ID of the task to run.
- * 2. `allowDisabled`: Allow running disabled tasks.
- * 3. `allowConcurrent`: Allow concurrent execution, i.e., running the task when another
- * task may be running.
- *
- * @return ?Task The task executed or null if not exists
- *
- * @since 4.1.0
- * @throws \RuntimeException
- */
- public function runTask(array $options): ?Task
- {
- $resolver = new OptionsResolver;
-
- try
- {
- $this->configureTaskRunnerOptions($resolver);
- }
- catch (\Exception $e)
- {
- }
-
- try
- {
- $options = $resolver->resolve($options);
- }
- catch (\Exception $e)
- {
- if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException)
- {
- throw $e;
- }
- }
-
- /** @var CMSApplication $app */
- $app = Factory::getApplication();
-
- // ? Sure about inferring scheduling bypass?
- $task = $this->getTask(
- [
- 'id' => (int) $options['id'],
- 'allowDisabled' => $options['allowDisabled'],
- 'bypassScheduling' => (int) $options['id'] !== 0,
- 'allowConcurrent' => $options['allowConcurrent'],
- 'includeCliExclusive' => ($app->isClient('cli')),
- ]
- );
-
- // ? Should this be logged? (probably, if an ID is passed?)
- if (empty($task))
- {
- return null;
- }
-
- $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
-
- $options['text_entry_format'] = '{DATE} {TIME} {PRIORITY} {MESSAGE}';
- $options['text_file'] = 'joomla_scheduler.php';
- Log::addLogger($options, Log::ALL, $task->logCategory);
-
- $taskId = $task->get('id');
- $taskTitle = $task->get('title');
-
- $task->log(Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_START', $taskId, $taskTitle), 'info');
-
- // Let's try to avoid time-outs
- if (\function_exists('set_time_limit'))
- {
- set_time_limit(0);
- }
-
- try
- {
- $task->run();
- }
- catch (\Exception $e)
- {
- // We suppress the exception here, it's still accessible with `$task->getContent()['exception']`.
- }
-
- $executionSnapshot = $task->getContent();
- $exitCode = $executionSnapshot['status'] ?? Status::NO_EXIT;
- $netDuration = $executionSnapshot['netDuration'] ?? 0;
- $duration = $executionSnapshot['duration'] ?? 0;
-
- if (\array_key_exists($exitCode, self::LOG_TEXT))
- {
- $level = in_array($exitCode, [Status::OK, Status::WILL_RESUME]) ? 'info' : 'warning';
- $task->log(Text::sprintf(self::LOG_TEXT[$exitCode], $taskId, $duration, $netDuration), $level);
-
- return $task;
- }
-
- $task->log(
- Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_UNKNOWN_EXIT', $taskId, $duration, $netDuration, $exitCode),
- 'warning'
- );
-
- return $task;
- }
-
- /**
- * Set up an {@see OptionsResolver} to resolve options compatible with {@see runTask}.
- *
- * @param OptionsResolver $resolver The {@see OptionsResolver} instance to set up.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws AccessException
- */
- protected function configureTaskRunnerOptions(OptionsResolver $resolver): void
- {
- $resolver->setDefaults(
- [
- 'id' => 0,
- 'allowDisabled' => false,
- 'allowConcurrent' => false,
- ]
- )
- ->setAllowedTypes('id', 'numeric')
- ->setAllowedTypes('allowDisabled', 'bool')
- ->setAllowedTypes('allowConcurrent', 'bool');
- }
-
- /**
- * Get the next task which is due to run, limit to a specific task when ID is given
- *
- * @param array $options Options for the getter, see {@see TaskModel::getTask()}.
- * ! should probably also support a non-locking getter.
- *
- * @return Task $task The task to execute
- *
- * @since 4.1.0
- * @throws \RuntimeException
- */
- public function getTask(array $options = []): ?Task
- {
- $resolver = new OptionsResolver;
-
- try
- {
- TaskModel::configureTaskGetterOptions($resolver);
- }
- catch (\Exception $e)
- {
- }
-
- try
- {
- $options = $resolver->resolve($options);
- }
- catch (\Exception $e)
- {
- if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException)
- {
- throw $e;
- }
- }
-
- try
- {
- /** @var SchedulerComponent $component */
- $component = Factory::getApplication()->bootComponent('com_scheduler');
-
- /** @var TaskModel $model */
- $model = $component->getMVCFactory()->createModel('Task', 'Administrator', ['ignore_request' => true]);
- }
- catch (\Exception $e)
- {
- }
-
- if (!isset($model))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $task = $model->getTask($options);
-
- if (empty($task))
- {
- return null;
- }
-
- return new Task($task);
- }
-
- /**
- * Fetches a single scheduled task in a Task instance.
- * If no id or title is specified, a due task is returned.
- *
- * @param int $id The task ID.
- * @param bool $allowDisabled Allow disabled/trashed tasks?
- *
- * @return ?object A matching task record, if it exists
- *
- * @since 4.1.0
- * @throws \RuntimeException
- */
- public function fetchTaskRecord(int $id = 0, bool $allowDisabled = false): ?object
- {
- $filters = [];
- $listConfig = ['limit' => 1];
-
- if ($id > 0)
- {
- $filters['id'] = $id;
- }
- else
- {
- // Filters and list config for scheduled task queue
- $filters['due'] = 1;
- $filters['locked'] = -1;
- $listConfig['multi_ordering'] = [
- 'a.priority DESC',
- 'a.next_execution ASC',
- ];
- }
-
- if ($allowDisabled)
- {
- $filters['state'] = '';
- }
-
- return $this->fetchTaskRecords($filters, $listConfig)[0] ?? null;
- }
-
- /**
- * @param array $filters The filters to set to the model
- * @param array $listConfig The list config (ordering, etc.) to set to the model
- *
- * @return array
- *
- * @since 4.1.0
- * @throws \RunTimeException
- */
- public function fetchTaskRecords(array $filters, array $listConfig): array
- {
- $model = null;
-
- try
- {
- /** @var SchedulerComponent $component */
- $component = Factory::getApplication()->bootComponent('com_scheduler');
-
- /** @var TasksModel $model */
- $model = $component->getMVCFactory()
- ->createModel('Tasks', 'Administrator', ['ignore_request' => true]);
- }
- catch (\Exception $e)
- {
- }
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $model->setState('list.select', 'a.*');
-
- // Default to only enabled tasks
- if (!isset($filters['state']))
- {
- $model->setState('filter.state', 1);
- }
-
- // Default to including orphaned tasks
- $model->setState('filter.orphaned', 0);
-
- // Default to ordering by ID
- $model->setState('list.ordering', 'a.id');
- $model->setState('list.direction', 'ASC');
-
- // List options
- foreach ($listConfig as $key => $value)
- {
- $model->setState('list.' . $key, $value);
- }
-
- // Filter options
- foreach ($filters as $type => $filter)
- {
- $model->setState('filter.' . $type, $filter);
- }
-
- return $model->getItems() ?: [];
- }
+ private const LOG_TEXT = [
+ Status::OK => 'COM_SCHEDULER_SCHEDULER_TASK_COMPLETE',
+ Status::WILL_RESUME => 'COM_SCHEDULER_SCHEDULER_TASK_WILL_RESUME',
+ Status::NO_LOCK => 'COM_SCHEDULER_SCHEDULER_TASK_LOCKED',
+ Status::NO_RUN => 'COM_SCHEDULER_SCHEDULER_TASK_UNLOCKED',
+ Status::NO_ROUTINE => 'COM_SCHEDULER_SCHEDULER_TASK_ROUTINE_NA',
+ ];
+
+ /**
+ * Filters for the task queue. Can be used with fetchTaskRecords().
+ *
+ * @since 4.1.0
+ * @todo remove?
+ */
+ public const TASK_QUEUE_FILTERS = [
+ 'due' => 1,
+ 'locked' => -1,
+ ];
+
+ /**
+ * List config for the task queue. Can be used with fetchTaskRecords().
+ *
+ * @since 4.1.0
+ * @todo remove?
+ */
+ public const TASK_QUEUE_LIST_CONFIG = [
+ 'multi_ordering' => ['a.priority DESC ', 'a.next_execution ASC'],
+ ];
+
+ /**
+ * Run a scheduled task.
+ * Runs a single due task from the task queue by default if $id and $title are not passed.
+ *
+ * @param array $options Array with options to configure the method's behavior. Supports:
+ * 1. `id`: (Optional) ID of the task to run.
+ * 2. `allowDisabled`: Allow running disabled tasks.
+ * 3. `allowConcurrent`: Allow concurrent execution, i.e., running the task when another
+ * task may be running.
+ *
+ * @return ?Task The task executed or null if not exists
+ *
+ * @since 4.1.0
+ * @throws \RuntimeException
+ */
+ public function runTask(array $options): ?Task
+ {
+ $resolver = new OptionsResolver();
+
+ try {
+ $this->configureTaskRunnerOptions($resolver);
+ } catch (\Exception $e) {
+ }
+
+ try {
+ $options = $resolver->resolve($options);
+ } catch (\Exception $e) {
+ if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
+ throw $e;
+ }
+ }
+
+ /** @var CMSApplication $app */
+ $app = Factory::getApplication();
+
+ // ? Sure about inferring scheduling bypass?
+ $task = $this->getTask(
+ [
+ 'id' => (int) $options['id'],
+ 'allowDisabled' => $options['allowDisabled'],
+ 'bypassScheduling' => (int) $options['id'] !== 0,
+ 'allowConcurrent' => $options['allowConcurrent'],
+ 'includeCliExclusive' => ($app->isClient('cli')),
+ ]
+ );
+
+ // ? Should this be logged? (probably, if an ID is passed?)
+ if (empty($task)) {
+ return null;
+ }
+
+ $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
+
+ $options['text_entry_format'] = '{DATE} {TIME} {PRIORITY} {MESSAGE}';
+ $options['text_file'] = 'joomla_scheduler.php';
+ Log::addLogger($options, Log::ALL, $task->logCategory);
+
+ $taskId = $task->get('id');
+ $taskTitle = $task->get('title');
+
+ $task->log(Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_START', $taskId, $taskTitle), 'info');
+
+ // Let's try to avoid time-outs
+ if (\function_exists('set_time_limit')) {
+ set_time_limit(0);
+ }
+
+ try {
+ $task->run();
+ } catch (\Exception $e) {
+ // We suppress the exception here, it's still accessible with `$task->getContent()['exception']`.
+ }
+
+ $executionSnapshot = $task->getContent();
+ $exitCode = $executionSnapshot['status'] ?? Status::NO_EXIT;
+ $netDuration = $executionSnapshot['netDuration'] ?? 0;
+ $duration = $executionSnapshot['duration'] ?? 0;
+
+ if (\array_key_exists($exitCode, self::LOG_TEXT)) {
+ $level = in_array($exitCode, [Status::OK, Status::WILL_RESUME]) ? 'info' : 'warning';
+ $task->log(Text::sprintf(self::LOG_TEXT[$exitCode], $taskId, $duration, $netDuration), $level);
+
+ return $task;
+ }
+
+ $task->log(
+ Text::sprintf('COM_SCHEDULER_SCHEDULER_TASK_UNKNOWN_EXIT', $taskId, $duration, $netDuration, $exitCode),
+ 'warning'
+ );
+
+ return $task;
+ }
+
+ /**
+ * Set up an {@see OptionsResolver} to resolve options compatible with {@see runTask}.
+ *
+ * @param OptionsResolver $resolver The {@see OptionsResolver} instance to set up.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws AccessException
+ */
+ protected function configureTaskRunnerOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults(
+ [
+ 'id' => 0,
+ 'allowDisabled' => false,
+ 'allowConcurrent' => false,
+ ]
+ )
+ ->setAllowedTypes('id', 'numeric')
+ ->setAllowedTypes('allowDisabled', 'bool')
+ ->setAllowedTypes('allowConcurrent', 'bool');
+ }
+
+ /**
+ * Get the next task which is due to run, limit to a specific task when ID is given
+ *
+ * @param array $options Options for the getter, see {@see TaskModel::getTask()}.
+ * ! should probably also support a non-locking getter.
+ *
+ * @return Task $task The task to execute
+ *
+ * @since 4.1.0
+ * @throws \RuntimeException
+ */
+ public function getTask(array $options = []): ?Task
+ {
+ $resolver = new OptionsResolver();
+
+ try {
+ TaskModel::configureTaskGetterOptions($resolver);
+ } catch (\Exception $e) {
+ }
+
+ try {
+ $options = $resolver->resolve($options);
+ } catch (\Exception $e) {
+ if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
+ throw $e;
+ }
+ }
+
+ try {
+ /** @var SchedulerComponent $component */
+ $component = Factory::getApplication()->bootComponent('com_scheduler');
+
+ /** @var TaskModel $model */
+ $model = $component->getMVCFactory()->createModel('Task', 'Administrator', ['ignore_request' => true]);
+ } catch (\Exception $e) {
+ }
+
+ if (!isset($model)) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $task = $model->getTask($options);
+
+ if (empty($task)) {
+ return null;
+ }
+
+ return new Task($task);
+ }
+
+ /**
+ * Fetches a single scheduled task in a Task instance.
+ * If no id or title is specified, a due task is returned.
+ *
+ * @param int $id The task ID.
+ * @param bool $allowDisabled Allow disabled/trashed tasks?
+ *
+ * @return ?object A matching task record, if it exists
+ *
+ * @since 4.1.0
+ * @throws \RuntimeException
+ */
+ public function fetchTaskRecord(int $id = 0, bool $allowDisabled = false): ?object
+ {
+ $filters = [];
+ $listConfig = ['limit' => 1];
+
+ if ($id > 0) {
+ $filters['id'] = $id;
+ } else {
+ // Filters and list config for scheduled task queue
+ $filters['due'] = 1;
+ $filters['locked'] = -1;
+ $listConfig['multi_ordering'] = [
+ 'a.priority DESC',
+ 'a.next_execution ASC',
+ ];
+ }
+
+ if ($allowDisabled) {
+ $filters['state'] = '';
+ }
+
+ return $this->fetchTaskRecords($filters, $listConfig)[0] ?? null;
+ }
+
+ /**
+ * @param array $filters The filters to set to the model
+ * @param array $listConfig The list config (ordering, etc.) to set to the model
+ *
+ * @return array
+ *
+ * @since 4.1.0
+ * @throws \RunTimeException
+ */
+ public function fetchTaskRecords(array $filters, array $listConfig): array
+ {
+ $model = null;
+
+ try {
+ /** @var SchedulerComponent $component */
+ $component = Factory::getApplication()->bootComponent('com_scheduler');
+
+ /** @var TasksModel $model */
+ $model = $component->getMVCFactory()
+ ->createModel('Tasks', 'Administrator', ['ignore_request' => true]);
+ } catch (\Exception $e) {
+ }
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $model->setState('list.select', 'a.*');
+
+ // Default to only enabled tasks
+ if (!isset($filters['state'])) {
+ $model->setState('filter.state', 1);
+ }
+
+ // Default to including orphaned tasks
+ $model->setState('filter.orphaned', 0);
+
+ // Default to ordering by ID
+ $model->setState('list.ordering', 'a.id');
+ $model->setState('list.direction', 'ASC');
+
+ // List options
+ foreach ($listConfig as $key => $value) {
+ $model->setState('list.' . $key, $value);
+ }
+
+ // Filter options
+ foreach ($filters as $type => $filter) {
+ $model->setState('filter.' . $type, $filter);
+ }
+
+ return $model->getItems() ?: [];
+ }
}
diff --git a/administrator/components/com_scheduler/src/Table/TaskTable.php b/administrator/components/com_scheduler/src/Table/TaskTable.php
index d06b91ec0b516..ac8533c6cc4c7 100644
--- a/administrator/components/com_scheduler/src/Table/TaskTable.php
+++ b/administrator/components/com_scheduler/src/Table/TaskTable.php
@@ -1,4 +1,5 @@
setColumnAlias('published', 'state');
-
- parent::__construct('#__scheduler_tasks', 'id', $db);
- }
-
- /**
- * Overloads {@see Table::check()} to perform sanity checks on properties and make sure they're
- * safe to store.
- *
- * @return boolean True if checks pass.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function check(): bool
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage());
-
- return false;
- }
-
- $this->title = htmlspecialchars_decode($this->title, ENT_QUOTES);
-
- // Set created date if not set.
- // ? Might not need since the constructor already sets this
- if (!(int) $this->created)
- {
- $this->created = Factory::getDate()->toSql();
- }
-
- // @todo : Add more checks if needed
-
- return true;
- }
-
- /**
- * Override {@see Table::store()} to update null fields as a default, which is needed when DATETIME
- * fields need to be updated to NULL. This override is needed because {@see AdminModel::save()} does not
- * expose an option to pass true to Table::store(). Also ensures the `created` and `created_by` fields are
- * set.
- *
- * @param boolean $updateNulls True to update fields even if they're null.
- *
- * @return boolean True if successful.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function store($updateNulls = true): bool
- {
- $isNew = empty($this->getId());
-
- // Set creation date if not set for a new item.
- if ($isNew && empty($this->created))
- {
- $this->created = Factory::getDate()->toSql();
- }
-
- // Set `created_by` if not set for a new item.
- if ($isNew && empty($this->created_by))
- {
- $this->created_by = Factory::getApplication()->getIdentity()->id;
- }
-
- // @todo : Should we add modified, modified_by fields? [ ]
-
- return parent::store($updateNulls);
- }
-
- /**
- * Returns the asset name of the entry as it appears in the {@see Asset} table.
- *
- * @return string The asset name.
- *
- * @since 4.1.0
- */
- protected function _getAssetName(): string
- {
- $k = $this->_tbl_key;
-
- return 'com_scheduler.task.' . (int) $this->$k;
- }
-
- /**
- * Override {@see Table::bind()} to bind some fields even if they're null given they're present in $src.
- * This override is needed specifically for DATETIME fields, of which the `next_execution` field is updated to
- * null if a task is configured to execute only on manual trigger.
- *
- * @param array|object $src An associative array or object to bind to the Table instance.
- * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean
- *
- * @since 4.1.0
- */
- public function bind($src, $ignore = array()): bool
- {
- $fields = ['next_execution'];
-
- foreach ($fields as $field)
- {
- if (\array_key_exists($field, $src) && \is_null($src[$field]))
- {
- $this->$field = $src[$field];
- }
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Release pseudo-locks on a set of task records. If an empty set is passed, this method releases lock on its
- * instance primary key, if available.
- *
- * @param integer[] $pks An optional array of primary key values to update. If not set the instance property
- * value is used.
- * @param ?int $userId ID of the user unlocking the tasks.
- *
- * @return boolean True on success; false if $pks is empty.
- *
- * @since 4.1.0
- * @throws QueryTypeAlreadyDefinedException|\UnexpectedValueException|\BadMethodCallException
- */
- public function unlock(array $pks = [], ?int $userId = null): bool
- {
- // Pre-processing by observers
- $event = AbstractEvent::create(
- 'onTaskBeforeUnlock',
- [
- 'subject' => $this,
- 'pks' => $pks,
- 'userId' => $userId,
- ]
- );
-
- $this->getDispatcher()->dispatch('onTaskBeforeUnlock', $event);
-
- // Some pre-processing before we can work with the keys.
- if (!empty($pks))
- {
- foreach ($pks as $key => $pk)
- {
- if (!\is_array($pk))
- {
- $pks[$key] = array($this->_tbl_key => $pk);
- }
- }
- }
-
- // If there are no primary keys set check to see if the instance key is set and use that.
- if (empty($pks))
- {
- $pk = [];
-
- foreach ($this->_tbl_keys as $key)
- {
- if ($this->$key)
- {
- $pk[$key] = $this->$key;
- }
- // We don't have a full primary key - return false.
- else
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
-
- return false;
- }
- }
-
- $pks = [$pk];
- }
-
- $lockedField = $this->getColumnAlias('locked');
-
- foreach ($pks as $pk)
- {
- // Update the publishing state for rows with the given primary keys.
- $query = $this->_db->getQuery(true)
- ->update($this->_tbl)
- ->set($this->_db->quoteName($lockedField) . ' = NULL');
-
- // Build the WHERE clause for the primary keys.
- $this->appendPrimaryKeys($query, $pk);
-
- $this->_db->setQuery($query);
-
- try
- {
- $this->_db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // If the Table instance value is in the list of primary keys that were set, set the instance.
- $ours = true;
-
- foreach ($this->_tbl_keys as $key)
- {
- if ($this->$key != $pk[$key])
- {
- $ours = false;
- }
- }
-
- if ($ours)
- {
- $this->$lockedField = null;
- }
- }
-
- // Pre-processing by observers
- $event = AbstractEvent::create(
- 'onTaskAfterUnlock',
- [
- 'subject' => $this,
- 'pks' => $pks,
- 'userId' => $userId,
- ]
- );
-
- $this->getDispatcher()->dispatch('onTaskAfterUnlock', $event);
-
- return true;
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.1.1
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Ensure params are json encoded by the bind method.
+ *
+ * @var string[]
+ * @since 4.1.0
+ */
+ protected $_jsonEncode = ['params', 'execution_rules', 'cron_rules'];
+
+ /**
+ * The 'created' column.
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ public $created;
+
+ /**
+ * The 'title' column.
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ public $title;
+
+ /**
+ * @var string
+ * @since 4.1.0
+ */
+ public $typeAlias = 'com_scheduler.task';
+
+ /**
+ * TaskTable constructor override, needed to pass the DB table name and primary key to {@see Table::__construct()}.
+ *
+ * @param DatabaseDriver $db A database connector object.
+ *
+ * @since 4.1.0
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->setColumnAlias('published', 'state');
+
+ parent::__construct('#__scheduler_tasks', 'id', $db);
+ }
+
+ /**
+ * Overloads {@see Table::check()} to perform sanity checks on properties and make sure they're
+ * safe to store.
+ *
+ * @return boolean True if checks pass.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function check(): bool
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage());
+
+ return false;
+ }
+
+ $this->title = htmlspecialchars_decode($this->title, ENT_QUOTES);
+
+ // Set created date if not set.
+ // ? Might not need since the constructor already sets this
+ if (!(int) $this->created) {
+ $this->created = Factory::getDate()->toSql();
+ }
+
+ // @todo : Add more checks if needed
+
+ return true;
+ }
+
+ /**
+ * Override {@see Table::store()} to update null fields as a default, which is needed when DATETIME
+ * fields need to be updated to NULL. This override is needed because {@see AdminModel::save()} does not
+ * expose an option to pass true to Table::store(). Also ensures the `created` and `created_by` fields are
+ * set.
+ *
+ * @param boolean $updateNulls True to update fields even if they're null.
+ *
+ * @return boolean True if successful.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function store($updateNulls = true): bool
+ {
+ $isNew = empty($this->getId());
+
+ // Set creation date if not set for a new item.
+ if ($isNew && empty($this->created)) {
+ $this->created = Factory::getDate()->toSql();
+ }
+
+ // Set `created_by` if not set for a new item.
+ if ($isNew && empty($this->created_by)) {
+ $this->created_by = Factory::getApplication()->getIdentity()->id;
+ }
+
+ // @todo : Should we add modified, modified_by fields? [ ]
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Returns the asset name of the entry as it appears in the {@see Asset} table.
+ *
+ * @return string The asset name.
+ *
+ * @since 4.1.0
+ */
+ protected function _getAssetName(): string
+ {
+ $k = $this->_tbl_key;
+
+ return 'com_scheduler.task.' . (int) $this->$k;
+ }
+
+ /**
+ * Override {@see Table::bind()} to bind some fields even if they're null given they're present in $src.
+ * This override is needed specifically for DATETIME fields, of which the `next_execution` field is updated to
+ * null if a task is configured to execute only on manual trigger.
+ *
+ * @param array|object $src An associative array or object to bind to the Table instance.
+ * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ */
+ public function bind($src, $ignore = array()): bool
+ {
+ $fields = ['next_execution'];
+
+ foreach ($fields as $field) {
+ if (\array_key_exists($field, $src) && \is_null($src[$field])) {
+ $this->$field = $src[$field];
+ }
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Release pseudo-locks on a set of task records. If an empty set is passed, this method releases lock on its
+ * instance primary key, if available.
+ *
+ * @param integer[] $pks An optional array of primary key values to update. If not set the instance property
+ * value is used.
+ * @param ?int $userId ID of the user unlocking the tasks.
+ *
+ * @return boolean True on success; false if $pks is empty.
+ *
+ * @since 4.1.0
+ * @throws QueryTypeAlreadyDefinedException|\UnexpectedValueException|\BadMethodCallException
+ */
+ public function unlock(array $pks = [], ?int $userId = null): bool
+ {
+ // Pre-processing by observers
+ $event = AbstractEvent::create(
+ 'onTaskBeforeUnlock',
+ [
+ 'subject' => $this,
+ 'pks' => $pks,
+ 'userId' => $userId,
+ ]
+ );
+
+ $this->getDispatcher()->dispatch('onTaskBeforeUnlock', $event);
+
+ // Some pre-processing before we can work with the keys.
+ if (!empty($pks)) {
+ foreach ($pks as $key => $pk) {
+ if (!\is_array($pk)) {
+ $pks[$key] = array($this->_tbl_key => $pk);
+ }
+ }
+ }
+
+ // If there are no primary keys set check to see if the instance key is set and use that.
+ if (empty($pks)) {
+ $pk = [];
+
+ foreach ($this->_tbl_keys as $key) {
+ if ($this->$key) {
+ $pk[$key] = $this->$key;
+ } else {
+ // We don't have a full primary key - return false.
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));
+
+ return false;
+ }
+ }
+
+ $pks = [$pk];
+ }
+
+ $lockedField = $this->getColumnAlias('locked');
+
+ foreach ($pks as $pk) {
+ // Update the publishing state for rows with the given primary keys.
+ $query = $this->_db->getQuery(true)
+ ->update($this->_tbl)
+ ->set($this->_db->quoteName($lockedField) . ' = NULL');
+
+ // Build the WHERE clause for the primary keys.
+ $this->appendPrimaryKeys($query, $pk);
+
+ $this->_db->setQuery($query);
+
+ try {
+ $this->_db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // If the Table instance value is in the list of primary keys that were set, set the instance.
+ $ours = true;
+
+ foreach ($this->_tbl_keys as $key) {
+ if ($this->$key != $pk[$key]) {
+ $ours = false;
+ }
+ }
+
+ if ($ours) {
+ $this->$lockedField = null;
+ }
+ }
+
+ // Pre-processing by observers
+ $event = AbstractEvent::create(
+ 'onTaskAfterUnlock',
+ [
+ 'subject' => $this,
+ 'pks' => $pks,
+ 'userId' => $userId,
+ ]
+ );
+
+ $this->getDispatcher()->dispatch('onTaskAfterUnlock', $event);
+
+ return true;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Task/Status.php b/administrator/components/com_scheduler/src/Task/Status.php
index 50f4673577deb..da35c8479c86d 100644
--- a/administrator/components/com_scheduler/src/Task/Status.php
+++ b/administrator/components/com_scheduler/src/Task/Status.php
@@ -1,4 +1,5 @@
'trashed',
- self::STATE_DISABLED => 'disabled',
- self::STATE_ENABLED => 'enabled',
- ];
-
- /**
- * The task snapshot
- *
- * @var array
- * @since 4.1.0
- */
- protected $snapshot = [];
-
- /**
- * @var Registry
- * @since 4.1.0
- */
- protected $taskRegistry;
-
- /**
- * @var string
- * @since 4.1.0
- */
- public $logCategory;
-
- /**
- * @var CMSApplication
- * @since 4.1.0
- */
- protected $app;
-
- /**
- * @var DatabaseInterface
- * @since 4.1.0
- */
- protected $db;
-
- /**
- * Maps task exit codes to events which should be dispatched when the task finishes.
- * 'NA' maps to the event for general task failures.
- *
- * @var string[]
- * @since 4.1.0
- */
- protected const EVENTS_MAP = [
- Status::OK => 'onTaskExecuteSuccess',
- Status::NO_ROUTINE => 'onTaskRoutineNotFound',
- Status::WILL_RESUME => 'onTaskRoutineWillResume',
- 'NA' => 'onTaskExecuteFailure',
- ];
-
- /**
- * Constructor for {@see Task}.
- *
- * @param object $record A task from {@see TaskTable}.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function __construct(object $record)
- {
- // Workaround because Registry dumps private properties otherwise.
- $taskOption = $record->taskOption;
- $record->params = json_decode($record->params, true);
-
- $this->taskRegistry = new Registry($record);
-
- $this->set('taskOption', $taskOption);
- $this->app = Factory::getApplication();
- $this->db = Factory::getContainer()->get(DatabaseDriver::class);
- $this->setLogger(Log::createDelegatedLogger());
- $this->logCategory = 'task' . $this->get('id');
-
- if ($this->get('params.individual_log'))
- {
- $logFile = $this->get('params.log_file') ?? 'task_' . $this->get('id') . '.log.php';
-
- $options['text_entry_format'] = '{DATE} {TIME} {PRIORITY} {MESSAGE}';
- $options['text_file'] = $logFile;
- Log::addLogger($options, Log::ALL, [$this->logCategory]);
- }
- }
-
- /**
- * Get the task as a data object that can be stored back in the database.
- * ! This method should be removed or changed as part of a better API implementation for the driver.
- *
- * @return object
- *
- * @since 4.1.0
- */
- public function getRecord(): object
- {
- // ! Probably, an array instead
- $recObject = $this->taskRegistry->toObject();
-
- $recObject->cron_rules = (array) $recObject->cron_rules;
-
- return $recObject;
- }
-
- /**
- * Execute the task.
- *
- * @return boolean True if success
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function run(): bool
- {
- /**
- * We try to acquire the lock here, only if we don't already have one.
- * We do this, so we can support two ways of running tasks:
- * 1. Directly through {@see Scheduler}, which optimises acquiring a lock while fetching from the task queue.
- * 2. Running a task without a pre-acquired lock.
- * ! This needs some more thought, for whether it should be allowed or if the single-query optimisation
- * should be used everywhere, although it doesn't make sense in the context of fetching
- * a task when it doesn't need to be run. This might be solved if we force a re-fetch
- * with the lock or do it here ourselves (using acquireLock as a proxy to the model's
- * getter).
- */
- if ($this->get('locked') === null)
- {
- $this->acquireLock();
- }
-
- // Exit early if task routine is not available
- if (!SchedulerHelper::getTaskOptions()->findOption($this->get('type')))
- {
- $this->snapshot['status'] = Status::NO_ROUTINE;
- $this->skipExecution();
- $this->dispatchExitEvent();
-
- return $this->isSuccess();
- }
-
- $this->snapshot['status'] = Status::RUNNING;
- $this->snapshot['taskStart'] = $this->snapshot['taskStart'] ?? microtime(true);
- $this->snapshot['netDuration'] = 0;
-
- /** @var ExecuteTaskEvent $event */
- $event = AbstractEvent::create(
- 'onExecuteTask',
- [
- 'eventClass' => ExecuteTaskEvent::class,
- 'subject' => $this,
- 'routineId' => $this->get('type'),
- 'langConstPrefix' => $this->get('taskOption')->langConstPrefix,
- 'params' => $this->get('params'),
- ]
- );
-
- PluginHelper::importPlugin('task');
-
- try
- {
- $this->app->getDispatcher()->dispatch('onExecuteTask', $event);
- }
- catch (\Exception $e)
- {
- // Suppress the exception for now, we'll throw it again once it's safe
- $this->log(Text::sprintf('COM_SCHEDULER_TASK_ROUTINE_EXCEPTION', $e->getMessage()), 'error');
- $this->snapshot['exception'] = $e;
- $this->snapshot['status'] = Status::KNOCKOUT;
- }
-
- $resultSnapshot = $event->getResultSnapshot();
-
- $this->snapshot['taskEnd'] = microtime(true);
- $this->snapshot['netDuration'] = $this->snapshot['taskEnd'] - $this->snapshot['taskStart'];
- $this->snapshot = array_merge($this->snapshot, $resultSnapshot);
-
- // @todo make the ExecRuleHelper usage less ugly, perhaps it should be composed into Task
- // Update object state.
- $this->set('last_execution', Factory::getDate('@' . (int) $this->snapshot['taskStart'])->toSql());
- $this->set('last_exit_code', $this->snapshot['status']);
-
- if ($this->snapshot['status'] !== Status::WILL_RESUME)
- {
- $this->set('next_execution', (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec());
- $this->set('times_executed', $this->get('times_executed') + 1);
- }
- else
- {
- /**
- * Resumable tasks need special handling.
- *
- * They are rescheduled as soon as possible to let their next step to be executed without
- * a very large temporal gap to the previous step.
- *
- * Moreover, the times executed does NOT increase for each step. It will increase once,
- * after the last step, when they return Status::OK.
- */
- $this->set('next_execution', Factory::getDate('now', 'UTC')->sub(new \DateInterval('PT1M'))->toSql());
- }
-
- // The only acceptable "successful" statuses are either clean exit or resuming execution.
- if (!in_array($this->snapshot['status'], [Status::WILL_RESUME, Status::OK]))
- {
- $this->set('times_failed', $this->get('times_failed') + 1);
- }
-
- if (!$this->releaseLock())
- {
- $this->snapshot['status'] = Status::NO_RELEASE;
- }
-
- $this->dispatchExitEvent();
-
- if (!empty($this->snapshot['exception']))
- {
- throw $this->snapshot['exception'];
- }
-
- return $this->isSuccess();
- }
-
- /**
- * Get the task execution snapshot.
- * ! Access locations will need updates once a more robust Snapshot container is implemented.
- *
- * @return array
- *
- * @since 4.1.0
- */
- public function getContent(): array
- {
- return $this->snapshot;
- }
-
- /**
- * Acquire a pseudo-lock on the task record.
- * ! At the moment, this method is not used anywhere as task locks are already
- * acquired when they're fetched. As such this method is not functional and should
- * not be reviewed until it is updated.
- *
- * @return boolean
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function acquireLock(): bool
- {
- $db = $this->db;
- $query = $db->getQuery(true);
- $id = $this->get('id');
- $now = Factory::getDate('now', 'GMT');
-
- $timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
- $timeout = new \DateInterval(sprintf('PT%dS', $timeout));
- $timeoutThreshold = (clone $now)->sub($timeout)->toSql();
- $now = $now->toSql();
-
- // @todo update or remove this method
- $query->update($db->qn('#__scheduler_tasks'))
- ->set('locked = :now')
- ->where($db->qn('id') . ' = :taskId')
- ->extendWhere(
- 'AND',
- [
- $db->qn('locked') . ' < :threshold',
- $db->qn('locked') . 'IS NULL',
- ],
- 'OR'
- )
- ->bind(':taskId', $id, ParameterType::INTEGER)
- ->bind(':now', $now)
- ->bind(':threshold', $timeoutThreshold);
-
- try
- {
- $db->lockTable('#__scheduler_tasks');
- $db->setQuery($query)->execute();
- }
- catch (\RuntimeException $e)
- {
- return false;
- }
- finally
- {
- $db->unlockTables();
- }
-
- if ($db->getAffectedRows() === 0)
- {
- return false;
- }
-
- $this->set('locked', $now);
-
- return true;
- }
-
- /**
- * Remove the pseudo-lock and optionally update the task record.
- *
- * @param bool $update If true, the record is updated with the snapshot
- *
- * @return boolean
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function releaseLock(bool $update = true): bool
- {
- $db = $this->db;
- $query = $db->getQuery(true);
- $id = $this->get('id');
-
- $query->update($db->qn('#__scheduler_tasks', 't'))
- ->set('locked = NULL')
- ->where($db->qn('id') . ' = :taskId')
- ->where($db->qn('locked') . ' IS NOT NULL')
- ->bind(':taskId', $id, ParameterType::INTEGER);
-
- if ($update)
- {
- $exitCode = $this->get('last_exit_code');
- $lastExec = $this->get('last_execution');
- $nextExec = $this->get('next_execution');
- $timesFailed = $this->get('times_failed');
- $timesExecuted = $this->get('times_executed');
-
- $query->set(
- [
- 'last_exit_code = :exitCode',
- 'last_execution = :lastExec',
- 'next_execution = :nextExec',
- 'times_executed = :times_executed',
- 'times_failed = :times_failed',
- ]
- )
- ->bind(':exitCode', $exitCode, ParameterType::INTEGER)
- ->bind(':lastExec', $lastExec)
- ->bind(':nextExec', $nextExec)
- ->bind(':times_executed', $timesExecuted)
- ->bind(':times_failed', $timesFailed);
- }
-
- try
- {
- $db->setQuery($query)->execute();
- }
- catch (\RuntimeException $e)
- {
- return false;
- }
-
- if (!$db->getAffectedRows())
- {
- return false;
- }
-
- $this->set('locked', null);
-
- return true;
- }
-
- /**
- * @param string $message Log message
- * @param string $priority Log level, defaults to 'info'
- *
- * @return void
- *
- * @since 4.1.0
- * @throws InvalidArgumentException
- */
- public function log(string $message, string $priority = 'info'): void
- {
- $this->logger->log($priority, $message, ['category' => $this->logCategory]);
- }
-
- /**
- * Advance the task entry's next calculated execution, effectively skipping the current execution.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function skipExecution(): void
- {
- $db = $this->db;
- $query = $db->getQuery(true);
-
- $id = $this->get('id');
- $nextExec = (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec(true, true);
-
- $query->update($db->qn('#__scheduler_tasks', 't'))
- ->set('t.next_execution = :nextExec')
- ->where('t.id = :id')
- ->bind(':nextExec', $nextExec)
- ->bind(':id', $id);
-
- try
- {
- $db->setQuery($query)->execute();
- }
- catch (\RuntimeException $e)
- {
- }
-
- $this->set('next_execution', $nextExec);
- }
-
- /**
- * Handles task exit (dispatch event).
- *
- * @return void
- *
- * @since 4.1.0
- *
- * @throws \UnexpectedValueException|\BadMethodCallException
- */
- protected function dispatchExitEvent(): void
- {
- $exitCode = $this->snapshot['status'] ?? 'NA';
- $eventName = self::EVENTS_MAP[$exitCode] ?? self::EVENTS_MAP['NA'];
-
- $event = AbstractEvent::create(
- $eventName,
- [
- 'subject' => $this,
- ]
- );
-
- $this->app->getDispatcher()->dispatch($eventName, $event);
- }
-
- /**
- * Was the task successful?
- *
- * @return boolean True if the task was successful.
- * @since 4.1.0
- */
- public function isSuccess(): bool
- {
- return in_array(($this->snapshot['status'] ?? null), [Status::OK, Status::WILL_RESUME]);
- }
-
- /**
- * Set a task property. This method is a proxy to {@see Registry::set()}.
- *
- * @param string $path Registry path of the task property.
- * @param mixed $value The value to set to the property.
- * @param ?string $separator The key separator.
- *
- * @return mixed|null
- *
- * @since 4.1.0
- */
- protected function set(string $path, $value, string $separator = null)
- {
- return $this->taskRegistry->set($path, $value, $separator);
- }
-
- /**
- * Get a task property. This method is a proxy to {@see Registry::get()}.
- *
- * @param string $path Registry path of the task property.
- * @param mixed $default Default property to return, if the actual value is null.
- *
- * @return mixed The task property.
- *
- * @since 4.1.0
- */
- public function get(string $path, $default = null)
- {
- return $this->taskRegistry->get($path, $default);
- }
-
- /**
- * Static method to determine whether an enumerated task state (as a string) is valid.
- *
- * @param string $state The task state (enumerated, as a string).
- *
- * @return boolean
- *
- * @since 4.1.0
- */
- public static function isValidState(string $state): bool
- {
- if (!is_numeric($state))
- {
- return false;
- }
-
- // Takes care of interpreting as float/int
- $state = $state + 0;
-
- return ArrayHelper::getValue(self::STATE_MAP, $state) !== null;
- }
-
- /**
- * Static method to determine whether a task id is valid. Note that this does not
- * validate ids against the database, but only verifies that an id may exist.
- *
- * @param string $id The task id (as a string).
- *
- * @return boolean
- *
- * @since 4.1.0
- */
- public static function isValidId(string $id): bool
- {
- $id = is_numeric($id) ? ($id + 0) : $id;
-
- if (!\is_int($id) || $id <= 0)
- {
- return false;
- }
-
- return true;
- }
+ use LoggerAwareTrait;
+
+ /**
+ * Enumerated state for enabled tasks.
+ *
+ * @since 4.1.0
+ */
+ public const STATE_ENABLED = 1;
+
+ /**
+ * Enumerated state for disabled tasks.
+ *
+ * @since 4.1.0
+ */
+ public const STATE_DISABLED = 0;
+
+ /**
+ * Enumerated state for trashed tasks.
+ *
+ * @since 4.1.0
+ */
+ public const STATE_TRASHED = -2;
+
+ /**
+ * Map state enumerations to logical language adjectives.
+ *
+ * @since 4.1.0
+ */
+ public const STATE_MAP = [
+ self::STATE_TRASHED => 'trashed',
+ self::STATE_DISABLED => 'disabled',
+ self::STATE_ENABLED => 'enabled',
+ ];
+
+ /**
+ * The task snapshot
+ *
+ * @var array
+ * @since 4.1.0
+ */
+ protected $snapshot = [];
+
+ /**
+ * @var Registry
+ * @since 4.1.0
+ */
+ protected $taskRegistry;
+
+ /**
+ * @var string
+ * @since 4.1.0
+ */
+ public $logCategory;
+
+ /**
+ * @var CMSApplication
+ * @since 4.1.0
+ */
+ protected $app;
+
+ /**
+ * @var DatabaseInterface
+ * @since 4.1.0
+ */
+ protected $db;
+
+ /**
+ * Maps task exit codes to events which should be dispatched when the task finishes.
+ * 'NA' maps to the event for general task failures.
+ *
+ * @var string[]
+ * @since 4.1.0
+ */
+ protected const EVENTS_MAP = [
+ Status::OK => 'onTaskExecuteSuccess',
+ Status::NO_ROUTINE => 'onTaskRoutineNotFound',
+ Status::WILL_RESUME => 'onTaskRoutineWillResume',
+ 'NA' => 'onTaskExecuteFailure',
+ ];
+
+ /**
+ * Constructor for {@see Task}.
+ *
+ * @param object $record A task from {@see TaskTable}.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function __construct(object $record)
+ {
+ // Workaround because Registry dumps private properties otherwise.
+ $taskOption = $record->taskOption;
+ $record->params = json_decode($record->params, true);
+
+ $this->taskRegistry = new Registry($record);
+
+ $this->set('taskOption', $taskOption);
+ $this->app = Factory::getApplication();
+ $this->db = Factory::getContainer()->get(DatabaseDriver::class);
+ $this->setLogger(Log::createDelegatedLogger());
+ $this->logCategory = 'task' . $this->get('id');
+
+ if ($this->get('params.individual_log')) {
+ $logFile = $this->get('params.log_file') ?? 'task_' . $this->get('id') . '.log.php';
+
+ $options['text_entry_format'] = '{DATE} {TIME} {PRIORITY} {MESSAGE}';
+ $options['text_file'] = $logFile;
+ Log::addLogger($options, Log::ALL, [$this->logCategory]);
+ }
+ }
+
+ /**
+ * Get the task as a data object that can be stored back in the database.
+ * ! This method should be removed or changed as part of a better API implementation for the driver.
+ *
+ * @return object
+ *
+ * @since 4.1.0
+ */
+ public function getRecord(): object
+ {
+ // ! Probably, an array instead
+ $recObject = $this->taskRegistry->toObject();
+
+ $recObject->cron_rules = (array) $recObject->cron_rules;
+
+ return $recObject;
+ }
+
+ /**
+ * Execute the task.
+ *
+ * @return boolean True if success
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function run(): bool
+ {
+ /**
+ * We try to acquire the lock here, only if we don't already have one.
+ * We do this, so we can support two ways of running tasks:
+ * 1. Directly through {@see Scheduler}, which optimises acquiring a lock while fetching from the task queue.
+ * 2. Running a task without a pre-acquired lock.
+ * ! This needs some more thought, for whether it should be allowed or if the single-query optimisation
+ * should be used everywhere, although it doesn't make sense in the context of fetching
+ * a task when it doesn't need to be run. This might be solved if we force a re-fetch
+ * with the lock or do it here ourselves (using acquireLock as a proxy to the model's
+ * getter).
+ */
+ if ($this->get('locked') === null) {
+ $this->acquireLock();
+ }
+
+ // Exit early if task routine is not available
+ if (!SchedulerHelper::getTaskOptions()->findOption($this->get('type'))) {
+ $this->snapshot['status'] = Status::NO_ROUTINE;
+ $this->skipExecution();
+ $this->dispatchExitEvent();
+
+ return $this->isSuccess();
+ }
+
+ $this->snapshot['status'] = Status::RUNNING;
+ $this->snapshot['taskStart'] = $this->snapshot['taskStart'] ?? microtime(true);
+ $this->snapshot['netDuration'] = 0;
+
+ /** @var ExecuteTaskEvent $event */
+ $event = AbstractEvent::create(
+ 'onExecuteTask',
+ [
+ 'eventClass' => ExecuteTaskEvent::class,
+ 'subject' => $this,
+ 'routineId' => $this->get('type'),
+ 'langConstPrefix' => $this->get('taskOption')->langConstPrefix,
+ 'params' => $this->get('params'),
+ ]
+ );
+
+ PluginHelper::importPlugin('task');
+
+ try {
+ $this->app->getDispatcher()->dispatch('onExecuteTask', $event);
+ } catch (\Exception $e) {
+ // Suppress the exception for now, we'll throw it again once it's safe
+ $this->log(Text::sprintf('COM_SCHEDULER_TASK_ROUTINE_EXCEPTION', $e->getMessage()), 'error');
+ $this->snapshot['exception'] = $e;
+ $this->snapshot['status'] = Status::KNOCKOUT;
+ }
+
+ $resultSnapshot = $event->getResultSnapshot();
+
+ $this->snapshot['taskEnd'] = microtime(true);
+ $this->snapshot['netDuration'] = $this->snapshot['taskEnd'] - $this->snapshot['taskStart'];
+ $this->snapshot = array_merge($this->snapshot, $resultSnapshot);
+
+ // @todo make the ExecRuleHelper usage less ugly, perhaps it should be composed into Task
+ // Update object state.
+ $this->set('last_execution', Factory::getDate('@' . (int) $this->snapshot['taskStart'])->toSql());
+ $this->set('last_exit_code', $this->snapshot['status']);
+
+ if ($this->snapshot['status'] !== Status::WILL_RESUME) {
+ $this->set('next_execution', (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec());
+ $this->set('times_executed', $this->get('times_executed') + 1);
+ } else {
+ /**
+ * Resumable tasks need special handling.
+ *
+ * They are rescheduled as soon as possible to let their next step to be executed without
+ * a very large temporal gap to the previous step.
+ *
+ * Moreover, the times executed does NOT increase for each step. It will increase once,
+ * after the last step, when they return Status::OK.
+ */
+ $this->set('next_execution', Factory::getDate('now', 'UTC')->sub(new \DateInterval('PT1M'))->toSql());
+ }
+
+ // The only acceptable "successful" statuses are either clean exit or resuming execution.
+ if (!in_array($this->snapshot['status'], [Status::WILL_RESUME, Status::OK])) {
+ $this->set('times_failed', $this->get('times_failed') + 1);
+ }
+
+ if (!$this->releaseLock()) {
+ $this->snapshot['status'] = Status::NO_RELEASE;
+ }
+
+ $this->dispatchExitEvent();
+
+ if (!empty($this->snapshot['exception'])) {
+ throw $this->snapshot['exception'];
+ }
+
+ return $this->isSuccess();
+ }
+
+ /**
+ * Get the task execution snapshot.
+ * ! Access locations will need updates once a more robust Snapshot container is implemented.
+ *
+ * @return array
+ *
+ * @since 4.1.0
+ */
+ public function getContent(): array
+ {
+ return $this->snapshot;
+ }
+
+ /**
+ * Acquire a pseudo-lock on the task record.
+ * ! At the moment, this method is not used anywhere as task locks are already
+ * acquired when they're fetched. As such this method is not functional and should
+ * not be reviewed until it is updated.
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function acquireLock(): bool
+ {
+ $db = $this->db;
+ $query = $db->getQuery(true);
+ $id = $this->get('id');
+ $now = Factory::getDate('now', 'GMT');
+
+ $timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
+ $timeout = new \DateInterval(sprintf('PT%dS', $timeout));
+ $timeoutThreshold = (clone $now)->sub($timeout)->toSql();
+ $now = $now->toSql();
+
+ // @todo update or remove this method
+ $query->update($db->qn('#__scheduler_tasks'))
+ ->set('locked = :now')
+ ->where($db->qn('id') . ' = :taskId')
+ ->extendWhere(
+ 'AND',
+ [
+ $db->qn('locked') . ' < :threshold',
+ $db->qn('locked') . 'IS NULL',
+ ],
+ 'OR'
+ )
+ ->bind(':taskId', $id, ParameterType::INTEGER)
+ ->bind(':now', $now)
+ ->bind(':threshold', $timeoutThreshold);
+
+ try {
+ $db->lockTable('#__scheduler_tasks');
+ $db->setQuery($query)->execute();
+ } catch (\RuntimeException $e) {
+ return false;
+ } finally {
+ $db->unlockTables();
+ }
+
+ if ($db->getAffectedRows() === 0) {
+ return false;
+ }
+
+ $this->set('locked', $now);
+
+ return true;
+ }
+
+ /**
+ * Remove the pseudo-lock and optionally update the task record.
+ *
+ * @param bool $update If true, the record is updated with the snapshot
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function releaseLock(bool $update = true): bool
+ {
+ $db = $this->db;
+ $query = $db->getQuery(true);
+ $id = $this->get('id');
+
+ $query->update($db->qn('#__scheduler_tasks', 't'))
+ ->set('locked = NULL')
+ ->where($db->qn('id') . ' = :taskId')
+ ->where($db->qn('locked') . ' IS NOT NULL')
+ ->bind(':taskId', $id, ParameterType::INTEGER);
+
+ if ($update) {
+ $exitCode = $this->get('last_exit_code');
+ $lastExec = $this->get('last_execution');
+ $nextExec = $this->get('next_execution');
+ $timesFailed = $this->get('times_failed');
+ $timesExecuted = $this->get('times_executed');
+
+ $query->set(
+ [
+ 'last_exit_code = :exitCode',
+ 'last_execution = :lastExec',
+ 'next_execution = :nextExec',
+ 'times_executed = :times_executed',
+ 'times_failed = :times_failed',
+ ]
+ )
+ ->bind(':exitCode', $exitCode, ParameterType::INTEGER)
+ ->bind(':lastExec', $lastExec)
+ ->bind(':nextExec', $nextExec)
+ ->bind(':times_executed', $timesExecuted)
+ ->bind(':times_failed', $timesFailed);
+ }
+
+ try {
+ $db->setQuery($query)->execute();
+ } catch (\RuntimeException $e) {
+ return false;
+ }
+
+ if (!$db->getAffectedRows()) {
+ return false;
+ }
+
+ $this->set('locked', null);
+
+ return true;
+ }
+
+ /**
+ * @param string $message Log message
+ * @param string $priority Log level, defaults to 'info'
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws InvalidArgumentException
+ */
+ public function log(string $message, string $priority = 'info'): void
+ {
+ $this->logger->log($priority, $message, ['category' => $this->logCategory]);
+ }
+
+ /**
+ * Advance the task entry's next calculated execution, effectively skipping the current execution.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function skipExecution(): void
+ {
+ $db = $this->db;
+ $query = $db->getQuery(true);
+
+ $id = $this->get('id');
+ $nextExec = (new ExecRuleHelper($this->taskRegistry->toObject()))->nextExec(true, true);
+
+ $query->update($db->qn('#__scheduler_tasks', 't'))
+ ->set('t.next_execution = :nextExec')
+ ->where('t.id = :id')
+ ->bind(':nextExec', $nextExec)
+ ->bind(':id', $id);
+
+ try {
+ $db->setQuery($query)->execute();
+ } catch (\RuntimeException $e) {
+ }
+
+ $this->set('next_execution', $nextExec);
+ }
+
+ /**
+ * Handles task exit (dispatch event).
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ *
+ * @throws \UnexpectedValueException|\BadMethodCallException
+ */
+ protected function dispatchExitEvent(): void
+ {
+ $exitCode = $this->snapshot['status'] ?? 'NA';
+ $eventName = self::EVENTS_MAP[$exitCode] ?? self::EVENTS_MAP['NA'];
+
+ $event = AbstractEvent::create(
+ $eventName,
+ [
+ 'subject' => $this,
+ ]
+ );
+
+ $this->app->getDispatcher()->dispatch($eventName, $event);
+ }
+
+ /**
+ * Was the task successful?
+ *
+ * @return boolean True if the task was successful.
+ * @since 4.1.0
+ */
+ public function isSuccess(): bool
+ {
+ return in_array(($this->snapshot['status'] ?? null), [Status::OK, Status::WILL_RESUME]);
+ }
+
+ /**
+ * Set a task property. This method is a proxy to {@see Registry::set()}.
+ *
+ * @param string $path Registry path of the task property.
+ * @param mixed $value The value to set to the property.
+ * @param ?string $separator The key separator.
+ *
+ * @return mixed|null
+ *
+ * @since 4.1.0
+ */
+ protected function set(string $path, $value, string $separator = null)
+ {
+ return $this->taskRegistry->set($path, $value, $separator);
+ }
+
+ /**
+ * Get a task property. This method is a proxy to {@see Registry::get()}.
+ *
+ * @param string $path Registry path of the task property.
+ * @param mixed $default Default property to return, if the actual value is null.
+ *
+ * @return mixed The task property.
+ *
+ * @since 4.1.0
+ */
+ public function get(string $path, $default = null)
+ {
+ return $this->taskRegistry->get($path, $default);
+ }
+
+ /**
+ * Static method to determine whether an enumerated task state (as a string) is valid.
+ *
+ * @param string $state The task state (enumerated, as a string).
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ */
+ public static function isValidState(string $state): bool
+ {
+ if (!is_numeric($state)) {
+ return false;
+ }
+
+ // Takes care of interpreting as float/int
+ $state = $state + 0;
+
+ return ArrayHelper::getValue(self::STATE_MAP, $state) !== null;
+ }
+
+ /**
+ * Static method to determine whether a task id is valid. Note that this does not
+ * validate ids against the database, but only verifies that an id may exist.
+ *
+ * @param string $id The task id (as a string).
+ *
+ * @return boolean
+ *
+ * @since 4.1.0
+ */
+ public static function isValidId(string $id): bool
+ {
+ $id = is_numeric($id) ? ($id + 0) : $id;
+
+ if (!\is_int($id) || $id <= 0) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Task/TaskOption.php b/administrator/components/com_scheduler/src/Task/TaskOption.php
index 05fc2ecdd05d8..802d479c1d785 100644
--- a/administrator/components/com_scheduler/src/Task/TaskOption.php
+++ b/administrator/components/com_scheduler/src/Task/TaskOption.php
@@ -1,4 +1,5 @@
id = $type;
- $this->title = Text::_("${langConstPrefix}_TITLE");
- $this->desc = Text::_("${langConstPrefix}_DESC");
- $this->langConstPrefix = $langConstPrefix;
- }
+ /**
+ * TaskOption constructor.
+ *
+ * @param string $type A unique ID string for a plugin task routine.
+ * @param string $langConstPrefix The Language constant prefix $p. Expects $p . _TITLE and $p . _DESC to exist.
+ *
+ * @since 4.1.0
+ */
+ public function __construct(string $type, string $langConstPrefix)
+ {
+ $this->id = $type;
+ $this->title = Text::_("${langConstPrefix}_TITLE");
+ $this->desc = Text::_("${langConstPrefix}_DESC");
+ $this->langConstPrefix = $langConstPrefix;
+ }
- /**
- * Magic method to allow read-only access to private properties.
- *
- * @param string $name The object property requested.
- *
- * @return ?string
- *
- * @since 4.1.0
- */
- public function __get(string $name)
- {
- if (property_exists($this, $name))
- {
- return $this->$name;
- }
+ /**
+ * Magic method to allow read-only access to private properties.
+ *
+ * @param string $name The object property requested.
+ *
+ * @return ?string
+ *
+ * @since 4.1.0
+ */
+ public function __get(string $name)
+ {
+ if (property_exists($this, $name)) {
+ return $this->$name;
+ }
- // Trigger a deprecation for the 'type' property (replaced with {@see id}).
- if ($name === 'type')
- {
- try
- {
- Log::add(
- sprintf(
- 'The %1$s property is deprecated. Use %2$s instead.',
- $name,
- 'id'
- ),
- Log::WARNING,
- 'deprecated'
- );
- }
- catch (\RuntimeException $e)
- {
- // Pass
- }
+ // Trigger a deprecation for the 'type' property (replaced with {@see id}).
+ if ($name === 'type') {
+ try {
+ Log::add(
+ sprintf(
+ 'The %1$s property is deprecated. Use %2$s instead.',
+ $name,
+ 'id'
+ ),
+ Log::WARNING,
+ 'deprecated'
+ );
+ } catch (\RuntimeException $e) {
+ // Pass
+ }
- return $this->id;
- }
+ return $this->id;
+ }
- return null;
- }
+ return null;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Task/TaskOptions.php b/administrator/components/com_scheduler/src/Task/TaskOptions.php
index 4488aab494d7d..4cc909df5e5e7 100644
--- a/administrator/components/com_scheduler/src/Task/TaskOptions.php
+++ b/administrator/components/com_scheduler/src/Task/TaskOptions.php
@@ -1,4 +1,5 @@
'languageConstantPrefix', ... ]
- *
- * @return void
- *
- * @since 4.1.0
- */
- public function addOptions(array $taskRoutines): void
- {
- foreach ($taskRoutines as $routineId => $langConstPrefix)
- {
- $this->options[] = new TaskOption($routineId, $langConstPrefix);
- }
- }
+ /**
+ * A plugin can support several task routines
+ * This method is used by a plugin's onTaskOptionsList subscriber to advertise supported routines.
+ *
+ * @param array $taskRoutines An associative array of {@var TaskOption} constructor argument pairs:
+ * [ 'routineId' => 'languageConstantPrefix', ... ]
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function addOptions(array $taskRoutines): void
+ {
+ foreach ($taskRoutines as $routineId => $langConstPrefix) {
+ $this->options[] = new TaskOption($routineId, $langConstPrefix);
+ }
+ }
- /**
- * @param ?string $routineId A unique identifier for a plugin task routine
- *
- * @return ?TaskOption A matching TaskOption if available, null otherwise
- *
- * @since 4.1.0
- */
- public function findOption(?string $routineId): ?TaskOption
- {
- if ($routineId === null)
- {
- return null;
- }
+ /**
+ * @param ?string $routineId A unique identifier for a plugin task routine
+ *
+ * @return ?TaskOption A matching TaskOption if available, null otherwise
+ *
+ * @since 4.1.0
+ */
+ public function findOption(?string $routineId): ?TaskOption
+ {
+ if ($routineId === null) {
+ return null;
+ }
- foreach ($this->options as $option)
- {
- if ($option->id === $routineId)
- {
- return $option;
- }
- }
+ foreach ($this->options as $option) {
+ if ($option->id === $routineId) {
+ return $option;
+ }
+ }
- return null;
- }
+ return null;
+ }
}
diff --git a/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php b/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php
index 16f3ac1481542..6c45c09c12839 100644
--- a/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php
+++ b/administrator/components/com_scheduler/src/Traits/TaskPluginTrait.php
@@ -1,4 +1,5 @@
snapshot['logCategory'] = $event->getArgument('subject')->logCategory;
- $this->snapshot['plugin'] = $this->_name;
- $this->snapshot['startTime'] = microtime(true);
- $this->snapshot['status'] = Status::RUNNING;
- }
-
- /**
- * Set information to {@see $snapshot} when ending a routine. This information includes the routine exit code and
- * timing information.
- *
- * @param ExecuteTaskEvent $event The event
- * @param ?int $exitCode The task exit code
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function endRoutine(ExecuteTaskEvent $event, int $exitCode): void
- {
- if (!$this instanceof CMSPlugin)
- {
- return;
- }
-
- $this->snapshot['endTime'] = $endTime = microtime(true);
- $this->snapshot['duration'] = $endTime - $this->snapshot['startTime'];
- $this->snapshot['status'] = $exitCode ?? Status::OK;
- $event->setResult($this->snapshot);
- }
-
- /**
- * Enhance the task form with routine-specific fields from an XML file declared through the TASKS_MAP constant.
- * If a plugin only supports the task form and does not need additional logic, this method can be mapped to the
- * `onContentPrepareForm` event through {@see SubscriberInterface::getSubscribedEvents()} and will take care
- * of injecting the fields without additional logic in the plugin class.
- *
- * @param EventInterface|Form $context The onContentPrepareForm event or the Form object.
- * @param mixed $data The form data, required when $context is a {@see Form} instance.
- *
- * @return boolean True if the form was successfully enhanced or the context was not relevant.
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function enhanceTaskItemForm($context, $data = null): bool
- {
- if ($context instanceof EventInterface)
- {
- /** @var Form $form */
- $form = $context->getArgument('0');
- $data = $context->getArgument('1');
- }
- elseif ($context instanceof Form)
- {
- $form = $context;
- }
- else
- {
- throw new \InvalidArgumentException(
- sprintf(
- 'Argument 0 of %1$s must be an instance of %2$s or %3$s',
- __METHOD__,
- EventInterface::class,
- Form::class
- )
- );
- }
-
- if ($form->getName() !== 'com_scheduler.task')
- {
- return true;
- }
-
- $routineId = $this->getRoutineId($form, $data);
- $isSupported = \array_key_exists($routineId, self::TASKS_MAP);
- $enhancementFormName = self::TASKS_MAP[$routineId]['form'] ?? '';
-
- // Return if routine is not supported by the plugin or the routine does not have a form linked in TASKS_MAP.
- if (!$isSupported || \strlen($enhancementFormName) === 0)
- {
- return true;
- }
-
- // We expect the form XML in "{PLUGIN_PATH}/forms/{FORM_NAME}.xml"
- $path = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name;
- $enhancementFormFile = $path . '/forms/' . $enhancementFormName . '.xml';
-
- try
- {
- $enhancementFormFile = Path::check($enhancementFormFile);
- }
- catch (\Exception $e)
- {
- return false;
- }
-
- if (is_file($enhancementFormFile))
- {
- return $form->loadFile($enhancementFormFile);
- }
-
- return false;
- }
-
- /**
- * Advertise the task routines supported by the plugin. This method should be mapped to the `onTaskOptionsList`,
- * enabling the plugin to advertise its routines without any custom logic.
- * **Note:** This method expects the `TASKS_MAP` class constant to have relevant information.
- *
- * @param EventInterface $event onTaskOptionsList Event
- *
- * @return void
- *
- * @since 4.1.0
- */
- public function advertiseRoutines(EventInterface $event): void
- {
- $options = [];
-
- foreach (self::TASKS_MAP as $routineId => $details)
- {
- // Sanity check against non-compliant plugins
- if (isset($details['langConstPrefix']))
- {
- $options[$routineId] = $details['langConstPrefix'];
- }
- }
-
- $subject = $event->getArgument('subject');
- $subject->addOptions($options);
- }
-
- /**
- * Get the relevant task routine ID in the context of a form event, e.g., the `onContentPrepareForm` event.
- *
- * @param Form $form The form
- * @param mixed $data The data
- *
- * @return string
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function getRoutineId(Form $form, $data): string
- {
- /*
- * Depending on when the form is loaded, the ID may either be in $data or the data already bound to the form.
- * $data can also either be an object or an array.
- */
- $routineId = $data->taskOption->id ?? $data->type ?? $data['type'] ?? $form->getValue('type') ?? $data['taskOption']->id ?? '';
-
- // If we're unable to find a routineId, it might be in the form input.
- if (empty($routineId))
- {
- $app = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
- $form = $app->getInput()->get('jform', []);
- $routineId = ArrayHelper::getValue($form, 'type', '', 'STRING');
- }
-
- return $routineId;
- }
-
- /**
- * Add a log message to the task log.
- *
- * @param string $message The log message
- * @param string $priority The log message priority
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- * @todo : use dependency injection here (starting from the Task & Scheduler classes).
- */
- protected function logTask(string $message, string $priority = 'info'): void
- {
- static $langLoaded;
- static $priorityMap = [
- 'debug' => Log::DEBUG,
- 'error' => Log::ERROR,
- 'info' => Log::INFO,
- 'notice' => Log::NOTICE,
- 'warning' => Log::WARNING,
- ];
-
- if (!$langLoaded)
- {
- $app = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
- $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
- $langLoaded = true;
- }
-
- $category = $this->snapshot['logCategory'];
-
- Log::add(Text::_('COM_SCHEDULER_ROUTINE_LOG_PREFIX') . $message, $priorityMap[$priority] ?? Log::INFO, $category);
- }
-
- /**
- * Handler for *standard* task routines. Standard routines are mapped to valid class methods 'method' through
- * `static::TASKS_MAP`. These methods are expected to take a single argument (the Event) and return an integer
- * return status (see {@see Status}). For a plugin that maps each of its task routines to valid methods and does
- * not need non-standard handling, this method can be mapped to the `onExecuteTask` event through
- * {@see SubscriberInterface::getSubscribedEvents()}, which would allow it to then check if the event wants to
- * execute a routine offered by the parent plugin, call the routine and do some other housework without any code
- * in the parent classes.
- * **Compatible routine method signature:** ({@see ExecuteTaskEvent::class}, ...): int
- *
- * @param ExecuteTaskEvent $event The `onExecuteTask` event.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function standardRoutineHandler(ExecuteTaskEvent $event): void
- {
- if (!\array_key_exists($event->getRoutineId(), self::TASKS_MAP))
- {
- return;
- }
-
- $this->startRoutine($event);
- $routineId = $event->getRoutineId();
- $methodName = (string) self::TASKS_MAP[$routineId]['method'] ?? '';
- $exitCode = Status::NO_EXIT;
-
- // We call the mapped method if it exists and confirms to the ($event) -> int signature.
- if (!empty($methodName) && ($staticReflection = new \ReflectionClass($this))->hasMethod($methodName))
- {
- $method = $staticReflection->getMethod($methodName);
-
- // Might need adjustments here for PHP8 named parameters.
- if (!($method->getNumberOfRequiredParameters() === 1)
- || !$method->getParameters()[0]->hasType()
- || $method->getParameters()[0]->getType()->getName() !== ExecuteTaskEvent::class
- || !$method->hasReturnType()
- || $method->getReturnType()->getName() !== 'int')
- {
- $this->logTask(
- sprintf(
- 'Incorrect routine method signature for %1$s(). See checks in %2$s()',
- $method->getName(),
- __METHOD__
- ),
- 'error'
- );
-
- return;
- }
-
- try
- {
- // Enable invocation of private/protected methods.
- $method->setAccessible(true);
- $exitCode = $method->invoke($this, $event);
- }
- catch (\ReflectionException $e)
- {
- // @todo replace with language string (?)
- $this->logTask('Exception when calling routine: ' . $e->getMessage(), 'error');
- $exitCode = Status::NO_RUN;
- }
- }
- else
- {
- $this->logTask(
- sprintf(
- 'Incorrectly configured TASKS_MAP in class %s. Missing valid method for `routine_id` %s',
- static::class,
- $routineId
- ),
- 'error'
- );
- }
-
- /**
- * Closure to validate a status against {@see Status}
- *
- * @since 4.1.0
- */
- $validateStatus = static function (int $statusCode): bool {
- return \in_array(
- $statusCode,
- (new \ReflectionClass(Status::class))->getConstants()
- );
- };
-
- // Validate the exit code.
- if (!\is_int($exitCode) || !$validateStatus($exitCode))
- {
- $exitCode = Status::INVALID_EXIT;
- }
-
- $this->endRoutine($event, $exitCode);
- }
+ /**
+ * A snapshot of the routine state.
+ *
+ * @var array
+ * @since 4.1.0
+ */
+ protected $snapshot = [];
+
+ /**
+ * Set information to {@see $snapshot} when initializing a routine.
+ *
+ * @param ExecuteTaskEvent $event The onExecuteTask event.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function startRoutine(ExecuteTaskEvent $event): void
+ {
+ if (!$this instanceof CMSPlugin) {
+ return;
+ }
+
+ $this->snapshot['logCategory'] = $event->getArgument('subject')->logCategory;
+ $this->snapshot['plugin'] = $this->_name;
+ $this->snapshot['startTime'] = microtime(true);
+ $this->snapshot['status'] = Status::RUNNING;
+ }
+
+ /**
+ * Set information to {@see $snapshot} when ending a routine. This information includes the routine exit code and
+ * timing information.
+ *
+ * @param ExecuteTaskEvent $event The event
+ * @param ?int $exitCode The task exit code
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function endRoutine(ExecuteTaskEvent $event, int $exitCode): void
+ {
+ if (!$this instanceof CMSPlugin) {
+ return;
+ }
+
+ $this->snapshot['endTime'] = $endTime = microtime(true);
+ $this->snapshot['duration'] = $endTime - $this->snapshot['startTime'];
+ $this->snapshot['status'] = $exitCode ?? Status::OK;
+ $event->setResult($this->snapshot);
+ }
+
+ /**
+ * Enhance the task form with routine-specific fields from an XML file declared through the TASKS_MAP constant.
+ * If a plugin only supports the task form and does not need additional logic, this method can be mapped to the
+ * `onContentPrepareForm` event through {@see SubscriberInterface::getSubscribedEvents()} and will take care
+ * of injecting the fields without additional logic in the plugin class.
+ *
+ * @param EventInterface|Form $context The onContentPrepareForm event or the Form object.
+ * @param mixed $data The form data, required when $context is a {@see Form} instance.
+ *
+ * @return boolean True if the form was successfully enhanced or the context was not relevant.
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function enhanceTaskItemForm($context, $data = null): bool
+ {
+ if ($context instanceof EventInterface) {
+ /** @var Form $form */
+ $form = $context->getArgument('0');
+ $data = $context->getArgument('1');
+ } elseif ($context instanceof Form) {
+ $form = $context;
+ } else {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Argument 0 of %1$s must be an instance of %2$s or %3$s',
+ __METHOD__,
+ EventInterface::class,
+ Form::class
+ )
+ );
+ }
+
+ if ($form->getName() !== 'com_scheduler.task') {
+ return true;
+ }
+
+ $routineId = $this->getRoutineId($form, $data);
+ $isSupported = \array_key_exists($routineId, self::TASKS_MAP);
+ $enhancementFormName = self::TASKS_MAP[$routineId]['form'] ?? '';
+
+ // Return if routine is not supported by the plugin or the routine does not have a form linked in TASKS_MAP.
+ if (!$isSupported || \strlen($enhancementFormName) === 0) {
+ return true;
+ }
+
+ // We expect the form XML in "{PLUGIN_PATH}/forms/{FORM_NAME}.xml"
+ $path = JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name;
+ $enhancementFormFile = $path . '/forms/' . $enhancementFormName . '.xml';
+
+ try {
+ $enhancementFormFile = Path::check($enhancementFormFile);
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ if (is_file($enhancementFormFile)) {
+ return $form->loadFile($enhancementFormFile);
+ }
+
+ return false;
+ }
+
+ /**
+ * Advertise the task routines supported by the plugin. This method should be mapped to the `onTaskOptionsList`,
+ * enabling the plugin to advertise its routines without any custom logic.
+ * **Note:** This method expects the `TASKS_MAP` class constant to have relevant information.
+ *
+ * @param EventInterface $event onTaskOptionsList Event
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function advertiseRoutines(EventInterface $event): void
+ {
+ $options = [];
+
+ foreach (self::TASKS_MAP as $routineId => $details) {
+ // Sanity check against non-compliant plugins
+ if (isset($details['langConstPrefix'])) {
+ $options[$routineId] = $details['langConstPrefix'];
+ }
+ }
+
+ $subject = $event->getArgument('subject');
+ $subject->addOptions($options);
+ }
+
+ /**
+ * Get the relevant task routine ID in the context of a form event, e.g., the `onContentPrepareForm` event.
+ *
+ * @param Form $form The form
+ * @param mixed $data The data
+ *
+ * @return string
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function getRoutineId(Form $form, $data): string
+ {
+ /*
+ * Depending on when the form is loaded, the ID may either be in $data or the data already bound to the form.
+ * $data can also either be an object or an array.
+ */
+ $routineId = $data->taskOption->id ?? $data->type ?? $data['type'] ?? $form->getValue('type') ?? $data['taskOption']->id ?? '';
+
+ // If we're unable to find a routineId, it might be in the form input.
+ if (empty($routineId)) {
+ $app = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
+ $form = $app->getInput()->get('jform', []);
+ $routineId = ArrayHelper::getValue($form, 'type', '', 'STRING');
+ }
+
+ return $routineId;
+ }
+
+ /**
+ * Add a log message to the task log.
+ *
+ * @param string $message The log message
+ * @param string $priority The log message priority
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ * @todo : use dependency injection here (starting from the Task & Scheduler classes).
+ */
+ protected function logTask(string $message, string $priority = 'info'): void
+ {
+ static $langLoaded;
+ static $priorityMap = [
+ 'debug' => Log::DEBUG,
+ 'error' => Log::ERROR,
+ 'info' => Log::INFO,
+ 'notice' => Log::NOTICE,
+ 'warning' => Log::WARNING,
+ ];
+
+ if (!$langLoaded) {
+ $app = $this->getApplication() ?? ($this->app ?? Factory::getApplication());
+ $app->getLanguage()->load('com_scheduler', JPATH_ADMINISTRATOR);
+ $langLoaded = true;
+ }
+
+ $category = $this->snapshot['logCategory'];
+
+ Log::add(Text::_('COM_SCHEDULER_ROUTINE_LOG_PREFIX') . $message, $priorityMap[$priority] ?? Log::INFO, $category);
+ }
+
+ /**
+ * Handler for *standard* task routines. Standard routines are mapped to valid class methods 'method' through
+ * `static::TASKS_MAP`. These methods are expected to take a single argument (the Event) and return an integer
+ * return status (see {@see Status}). For a plugin that maps each of its task routines to valid methods and does
+ * not need non-standard handling, this method can be mapped to the `onExecuteTask` event through
+ * {@see SubscriberInterface::getSubscribedEvents()}, which would allow it to then check if the event wants to
+ * execute a routine offered by the parent plugin, call the routine and do some other housework without any code
+ * in the parent classes.
+ * **Compatible routine method signature:** ({@see ExecuteTaskEvent::class}, ...): int
+ *
+ * @param ExecuteTaskEvent $event The `onExecuteTask` event.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function standardRoutineHandler(ExecuteTaskEvent $event): void
+ {
+ if (!\array_key_exists($event->getRoutineId(), self::TASKS_MAP)) {
+ return;
+ }
+
+ $this->startRoutine($event);
+ $routineId = $event->getRoutineId();
+ $methodName = (string) self::TASKS_MAP[$routineId]['method'] ?? '';
+ $exitCode = Status::NO_EXIT;
+
+ // We call the mapped method if it exists and confirms to the ($event) -> int signature.
+ if (!empty($methodName) && ($staticReflection = new \ReflectionClass($this))->hasMethod($methodName)) {
+ $method = $staticReflection->getMethod($methodName);
+
+ // Might need adjustments here for PHP8 named parameters.
+ if (
+ !($method->getNumberOfRequiredParameters() === 1)
+ || !$method->getParameters()[0]->hasType()
+ || $method->getParameters()[0]->getType()->getName() !== ExecuteTaskEvent::class
+ || !$method->hasReturnType()
+ || $method->getReturnType()->getName() !== 'int'
+ ) {
+ $this->logTask(
+ sprintf(
+ 'Incorrect routine method signature for %1$s(). See checks in %2$s()',
+ $method->getName(),
+ __METHOD__
+ ),
+ 'error'
+ );
+
+ return;
+ }
+
+ try {
+ // Enable invocation of private/protected methods.
+ $method->setAccessible(true);
+ $exitCode = $method->invoke($this, $event);
+ } catch (\ReflectionException $e) {
+ // @todo replace with language string (?)
+ $this->logTask('Exception when calling routine: ' . $e->getMessage(), 'error');
+ $exitCode = Status::NO_RUN;
+ }
+ } else {
+ $this->logTask(
+ sprintf(
+ 'Incorrectly configured TASKS_MAP in class %s. Missing valid method for `routine_id` %s',
+ static::class,
+ $routineId
+ ),
+ 'error'
+ );
+ }
+
+ /**
+ * Closure to validate a status against {@see Status}
+ *
+ * @since 4.1.0
+ */
+ $validateStatus = static function (int $statusCode): bool {
+ return \in_array(
+ $statusCode,
+ (new \ReflectionClass(Status::class))->getConstants()
+ );
+ };
+
+ // Validate the exit code.
+ if (!\is_int($exitCode) || !$validateStatus($exitCode)) {
+ $exitCode = Status::INVALID_EXIT;
+ }
+
+ $this->endRoutine($event, $exitCode);
+ }
}
diff --git a/administrator/components/com_scheduler/src/View/Select/HtmlView.php b/administrator/components/com_scheduler/src/View/Select/HtmlView.php
index c2d35d3a44b12..04d9b5d32dea4 100644
--- a/administrator/components/com_scheduler/src/View/Select/HtmlView.php
+++ b/administrator/components/com_scheduler/src/View/Select/HtmlView.php
@@ -1,4 +1,5 @@
app = Factory::getApplication();
-
- parent::__construct($config);
- }
-
- /**
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function display($tpl = null): void
- {
- $this->state = $this->get('State');
- $this->items = $this->get('Items');
- $this->modalLink = '';
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function addToolbar(): void
- {
- /*
- * Get the global Toolbar instance
- * @todo : Replace usage with ToolbarFactoryInterface. but how?
- * Probably some changes in the core, since mod_menu calls and renders the getInstance() toolbar
- */
- $toolbar = Toolbar::getInstance();
-
- // Add page title
- ToolbarHelper::title(Text::_('COM_SCHEDULER_MANAGER_TASKS'), 'clock');
-
- $toolbar->linkButton('cancel')
- ->url('index.php?option=com_scheduler')
- ->buttonClass('btn btn-danger')
- ->icon('icon-times')
- ->text(Text::_('JCANCEL'));
- }
+ /**
+ * @var AdministratorApplication
+ * @since 4.1.0
+ */
+ protected $app;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ * @since 4.1.0
+ */
+ protected $state;
+
+ /**
+ * An array of items
+ *
+ * @var TaskOption[]
+ * @since 4.1.0
+ */
+ protected $items;
+
+ /**
+ * A suffix for links for modal use [?]
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected $modalLink;
+
+ /**
+ * HtmlView constructor.
+ *
+ * @param array $config A named configuration array for object construction.
+ * name: the name (optional) of the view (defaults to the view class name suffix).
+ * charset: the character set to use for display
+ * escape: the name (optional) of the function to use for escaping strings
+ * base_path: the parent path (optional) of the `views` directory (defaults to the component
+ * folder) template_plath: the path (optional) of the layout directory (defaults to
+ * base_path + /views/ + view name helper_path: the path (optional) of the helper files
+ * (defaults to base_path + /helpers/) layout: the layout (optional) to use to display the
+ * view
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function __construct($config = [])
+ {
+ $this->app = Factory::getApplication();
+
+ parent::__construct($config);
+ }
+
+ /**
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function display($tpl = null): void
+ {
+ $this->state = $this->get('State');
+ $this->items = $this->get('Items');
+ $this->modalLink = '';
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function addToolbar(): void
+ {
+ /*
+ * Get the global Toolbar instance
+ * @todo : Replace usage with ToolbarFactoryInterface. but how?
+ * Probably some changes in the core, since mod_menu calls and renders the getInstance() toolbar
+ */
+ $toolbar = Toolbar::getInstance();
+
+ // Add page title
+ ToolbarHelper::title(Text::_('COM_SCHEDULER_MANAGER_TASKS'), 'clock');
+
+ $toolbar->linkButton('cancel')
+ ->url('index.php?option=com_scheduler')
+ ->buttonClass('btn btn-danger')
+ ->icon('icon-times')
+ ->text(Text::_('JCANCEL'));
+ }
}
diff --git a/administrator/components/com_scheduler/src/View/Task/HtmlView.php b/administrator/components/com_scheduler/src/View/Task/HtmlView.php
index 6023fc9715f3b..08c9df9621df4 100644
--- a/administrator/components/com_scheduler/src/View/Task/HtmlView.php
+++ b/administrator/components/com_scheduler/src/View/Task/HtmlView.php
@@ -1,4 +1,5 @@
app = Factory::getApplication();
- parent::__construct($config);
- }
-
- /**
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- public function display($tpl = null): void
- {
- /*
- * Will call the getForm() method of TaskModel
- */
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
- $this->canDo = ContentHelper::getActions('com_scheduler', 'task', $this->item->id);
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Adds the page title and toolbar
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function addToolbar(): void
- {
- $app = $this->app;
-
- $app->getInput()->set('hidemainmenu', true);
- $isNew = ($this->item->id == 0);
- $canDo = $this->canDo;
-
- /*
- * Get the toolbar object instance
- * !! @todo : Replace usage with ToolbarFactoryInterface
- */
- $toolbar = Toolbar::getInstance();
-
- ToolbarHelper::title($isNew ? Text::_('COM_SCHEDULER_MANAGER_TASK_NEW') : Text::_('COM_SCHEDULER_MANAGER_TASK_EDIT'), 'clock');
-
- if (($isNew && $canDo->get('core.create')) || (!$isNew && $canDo->get('core.edit')))
- {
- $toolbar->apply('task.apply');
- $toolbar->save('task.save');
- }
-
- // @todo | ? : Do we need save2new, save2copy?
-
- $toolbar->cancel('task.cancel', $isNew ? 'JTOOLBAR_CANCEL' : 'JTOOLBAR_CLOSE');
- $toolbar->help('Scheduled_Tasks:_Edit');
- }
+ /**
+ * @var AdministratorApplication $app
+ * @since 4.1.0
+ */
+ protected $app;
+
+ /**
+ * The Form object
+ *
+ * @var Form
+ * @since 4.1.0
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ * @since 4.1.0
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ * @since 4.1.0
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ * @since 4.1.0
+ */
+ protected $canDo;
+
+ /**
+ * Overloads the parent constructor.
+ * Just needed to fetch the Application object.
+ *
+ * @param array $config A named configuration array for object construction.
+ * name: the name (optional) of the view (defaults to the view class name suffix).
+ * charset: the character set to use for display
+ * escape: the name (optional) of the function to use for escaping strings
+ * base_path: the parent path (optional) of the `views` directory (defaults to the
+ * component folder) template_plath: the path (optional) of the layout directory (defaults
+ * to base_path + /views/ + view name helper_path: the path (optional) of the helper files
+ * (defaults to base_path + /helpers/) layout: the layout (optional) to use to display the
+ * view
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function __construct($config = array())
+ {
+ $this->app = Factory::getApplication();
+ parent::__construct($config);
+ }
+
+ /**
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function display($tpl = null): void
+ {
+ /*
+ * Will call the getForm() method of TaskModel
+ */
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $this->canDo = ContentHelper::getActions('com_scheduler', 'task', $this->item->id);
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Adds the page title and toolbar
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function addToolbar(): void
+ {
+ $app = $this->app;
+
+ $app->getInput()->set('hidemainmenu', true);
+ $isNew = ($this->item->id == 0);
+ $canDo = $this->canDo;
+
+ /*
+ * Get the toolbar object instance
+ * !! @todo : Replace usage with ToolbarFactoryInterface
+ */
+ $toolbar = Toolbar::getInstance();
+
+ ToolbarHelper::title($isNew ? Text::_('COM_SCHEDULER_MANAGER_TASK_NEW') : Text::_('COM_SCHEDULER_MANAGER_TASK_EDIT'), 'clock');
+
+ if (($isNew && $canDo->get('core.create')) || (!$isNew && $canDo->get('core.edit'))) {
+ $toolbar->apply('task.apply');
+ $toolbar->save('task.save');
+ }
+
+ // @todo | ? : Do we need save2new, save2copy?
+
+ $toolbar->cancel('task.cancel', $isNew ? 'JTOOLBAR_CANCEL' : 'JTOOLBAR_CLOSE');
+ $toolbar->help('Scheduled_Tasks:_Edit');
+ }
}
diff --git a/administrator/components/com_scheduler/src/View/Tasks/HtmlView.php b/administrator/components/com_scheduler/src/View/Tasks/HtmlView.php
index a7fd01bde0670..6f958d1576ea2 100644
--- a/administrator/components/com_scheduler/src/View/Tasks/HtmlView.php
+++ b/administrator/components/com_scheduler/src/View/Tasks/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('empty_state');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.1.0
- * @throws \Exception
- */
- protected function addToolbar(): void
- {
- $canDo = ContentHelper::getActions('com_scheduler');
- $user = Factory::getApplication()->getIdentity();
-
- /*
- * Get the toolbar object instance
- * !! @todo : Replace usage with ToolbarFactoryInterface
- */
- $toolbar = Toolbar::getInstance();
-
- ToolbarHelper::title(Text::_('COM_SCHEDULER_MANAGER_TASKS'), 'clock');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->linkButton('new', 'JTOOLBAR_NEW')
- ->url('index.php?option=com_scheduler&view=select&layout=default')
- ->buttonClass('btn btn-success')
- ->icon('icon-new');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin')))
- {
- /** @var DropdownButton $dropdown */
- $dropdown = $toolbar->dropdownButton('status-group')
- ->toggleSplit(false)
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- // Add the batch Enable, Disable and Trash buttons if privileged
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('tasks.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
- $childBar->unpublish('tasks.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
-
- if ($canDo->get('core.admin'))
- {
- $childBar->checkin('tasks.checkin')->listCheck(true);
- }
-
- $childBar->checkin('tasks.unlock', 'COM_SCHEDULER_TOOLBAR_UNLOCK')->listCheck(true)->icon('icon-unlock');
-
- // We don't want the batch Trash button if displayed entries are all trashed
- if ($this->state->get('filter.state') != -2)
- {
- $childBar->trash('tasks.trash')->listCheck(true);
- }
- }
- }
-
- // Add "Empty Trash" button if filtering by trashed.
- if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('tasks.delete')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->listCheck(true);
- }
-
- // Link to component preferences if user has admin privileges
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_scheduler');
- }
-
- $toolbar->help('Scheduled_Tasks');
- }
+ /**
+ * Array of task items.
+ *
+ * @var array
+ * @since 4.1.0
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var Pagination
+ * @since 4.1.0
+ * @todo Test pagination.
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 4.1.0
+ */
+ protected $state;
+
+ /**
+ * A Form object for search filters.
+ *
+ * @var Form
+ * @since 4.1.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters.
+ *
+ * @var array
+ * @since 4.1.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view in an empty state?
+ *
+ * @var boolean
+ * @since 4.1.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * @inheritDoc
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ public function display($tpl = null): void
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('empty_state');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function addToolbar(): void
+ {
+ $canDo = ContentHelper::getActions('com_scheduler');
+ $user = Factory::getApplication()->getIdentity();
+
+ /*
+ * Get the toolbar object instance
+ * !! @todo : Replace usage with ToolbarFactoryInterface
+ */
+ $toolbar = Toolbar::getInstance();
+
+ ToolbarHelper::title(Text::_('COM_SCHEDULER_MANAGER_TASKS'), 'clock');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->linkButton('new', 'JTOOLBAR_NEW')
+ ->url('index.php?option=com_scheduler&view=select&layout=default')
+ ->buttonClass('btn btn-success')
+ ->icon('icon-new');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin'))) {
+ /** @var DropdownButton $dropdown */
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->toggleSplit(false)
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ // Add the batch Enable, Disable and Trash buttons if privileged
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('tasks.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
+ $childBar->unpublish('tasks.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
+
+ if ($canDo->get('core.admin')) {
+ $childBar->checkin('tasks.checkin')->listCheck(true);
+ }
+
+ $childBar->checkin('tasks.unlock', 'COM_SCHEDULER_TOOLBAR_UNLOCK')->listCheck(true)->icon('icon-unlock');
+
+ // We don't want the batch Trash button if displayed entries are all trashed
+ if ($this->state->get('filter.state') != -2) {
+ $childBar->trash('tasks.trash')->listCheck(true);
+ }
+ }
+ }
+
+ // Add "Empty Trash" button if filtering by trashed.
+ if ($this->state->get('filter.state') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('tasks.delete')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->listCheck(true);
+ }
+
+ // Link to component preferences if user has admin privileges
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_scheduler');
+ }
+
+ $toolbar->help('Scheduled_Tasks');
+ }
}
diff --git a/administrator/components/com_scheduler/tmpl/select/default.php b/administrator/components/com_scheduler/tmpl/select/default.php
index efffafd4f13ef..680860340fed5 100644
--- a/administrator/components/com_scheduler/tmpl/select/default.php
+++ b/administrator/components/com_scheduler/tmpl/select/default.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
- items as $item) : ?>
-
- id; ?>
- escape($item->title); ?>
- escape(strip_tags($item->desc)), 200); ?>
-
-
-
-
-
-
-
-
-
-
-
+
+ items as $item) : ?>
+
+ id; ?>
+ escape($item->title); ?>
+ escape(strip_tags($item->desc)), 200); ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/components/com_scheduler/tmpl/select/modal.php b/administrator/components/com_scheduler/tmpl/select/modal.php
index 745adeeef782e..5c2d100569e8a 100644
--- a/administrator/components/com_scheduler/tmpl/select/modal.php
+++ b/administrator/components/com_scheduler/tmpl/select/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_scheduler/tmpl/task/edit.php b/administrator/components/com_scheduler/tmpl/task/edit.php
index 184fcb1dfb47c..7d91554d3a53f 100644
--- a/administrator/components/com_scheduler/tmpl/task/edit.php
+++ b/administrator/components/com_scheduler/tmpl/task/edit.php
@@ -1,4 +1,5 @@
$fieldset) :
- if ($name === 'task_params') :
- unset($advancedFieldsets[$name]);
- continue;
- endif;
+ if ($name === 'task_params') :
+ unset($advancedFieldsets[$name]);
+ continue;
+ endif;
- $this->ignore_fieldsets[] = $fieldset->name;
+ $this->ignore_fieldsets[] = $fieldset->name;
endforeach;
?>
diff --git a/administrator/components/com_scheduler/tmpl/tasks/default.php b/administrator/components/com_scheduler/tmpl/tasks/default.php
index ec6f238b2a889..c33990606550a 100644
--- a/administrator/components/com_scheduler/tmpl/tasks/default.php
+++ b/administrator/components/com_scheduler/tmpl/tasks/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect')
- ->useScript('com_scheduler.test-task')
- ->useStyle('com_scheduler.admin-view-tasks-css');
+ ->useScript('multiselect')
+ ->useScript('com_scheduler.test-task')
+ ->useStyle('com_scheduler.admin-view-tasks-css');
Text::script('COM_SCHEDULER_TEST_RUN_TITLE');
Text::script('COM_SCHEDULER_TEST_RUN_TASK');
@@ -42,14 +43,11 @@
Text::script('JLIB_JS_AJAX_ERROR_NO_CONTENT');
Text::script('JLIB_JS_AJAX_ERROR_PARSE');
-try
-{
- /** @var CMSWebApplicationInterface $app */
- $app = Factory::getApplication();
-}
-catch (Exception $e)
-{
- die('Failed to get app');
+try {
+ /** @var CMSWebApplicationInterface $app */
+ $app = Factory::getApplication();
+} catch (Exception $e) {
+ die('Failed to get app');
}
$user = $app->getIdentity();
@@ -60,237 +58,235 @@
$section = null;
$mode = false;
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_scheduler&task=tasks.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_scheduler&task=tasks.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$this->document->addScriptOptions('com_scheduler.test-task.token', Session::getFormToken());
?>
diff --git a/administrator/components/com_scheduler/tmpl/tasks/empty_state.php b/administrator/components/com_scheduler/tmpl/tasks/empty_state.php
index adbcc38835678..96e2eb0958ca4 100644
--- a/administrator/components/com_scheduler/tmpl/tasks/empty_state.php
+++ b/administrator/components/com_scheduler/tmpl/tasks/empty_state.php
@@ -1,4 +1,5 @@
'COM_SCHEDULER',
- 'formURL' => 'index.php?option=com_scheduler&task=task.add',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/J4.x:Task_Scheduler',
- 'icon' => 'icon-clock clock',
+ 'textPrefix' => 'COM_SCHEDULER',
+ 'formURL' => 'index.php?option=com_scheduler&task=task.add',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/J4.x:Task_Scheduler',
+ 'icon' => 'icon-clock clock',
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_scheduler'))
-{
- $displayData['createURL'] = 'index.php?option=com_scheduler&view=select&layout=default';
+if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_scheduler')) {
+ $displayData['createURL'] = 'index.php?option=com_scheduler&view=select&layout=default';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_tags/services/provider.php b/administrator/components/com_tags/services/provider.php
index 669a7013d295c..6352e87ae35fa 100644
--- a/administrator/components/com_tags/services/provider.php
+++ b/administrator/components/com_tags/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Tags'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Tags'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Tags'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new TagsComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Tags'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Tags'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Tags'));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new TagsComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_tags/src/Controller/DisplayController.php b/administrator/components/com_tags/src/Controller/DisplayController.php
index 366c926efd126..b6b15ba734c4b 100644
--- a/administrator/components/com_tags/src/Controller/DisplayController.php
+++ b/administrator/components/com_tags/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'tags');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 3.1
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $view = $this->input->get('view', 'tags');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
- // Check for edit form.
- if ($view == 'tag' && $layout == 'edit' && !$this->checkEditId('com_tags.edit.tag', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($view == 'tag' && $layout == 'edit' && !$this->checkEditId('com_tags.edit.tag', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_tags&view=tags', false));
+ $this->setRedirect(Route::_('index.php?option=com_tags&view=tags', false));
- return false;
- }
+ return false;
+ }
- parent::display();
+ parent::display();
- return $this;
- }
+ return $this;
+ }
}
diff --git a/administrator/components/com_tags/src/Controller/TagController.php b/administrator/components/com_tags/src/Controller/TagController.php
index 1abe0e5f90aba..26485516e0533 100644
--- a/administrator/components/com_tags/src/Controller/TagController.php
+++ b/administrator/components/com_tags/src/Controller/TagController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Tags\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Tags\Administrator\Controller;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Versioning\VersionableControllerTrait;
@@ -20,57 +20,57 @@
*/
class TagController extends FormController
{
- use VersionableControllerTrait;
+ use VersionableControllerTrait;
- /**
- * Method to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 3.1
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', 'com_tags');
- }
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 3.1
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', 'com_tags');
+ }
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 3.1
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- // Since there is no asset tracking and no categories, revert to the component permissions.
- return parent::allowEdit($data, $key);
- }
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 3.1
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ // Since there is no asset tracking and no categories, revert to the component permissions.
+ return parent::allowEdit($data, $key);
+ }
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True if successful, false otherwise and internal error is set.
- *
- * @since 3.1
- */
- public function batch($model = null)
- {
- $this->checkToken();
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True if successful, false otherwise and internal error is set.
+ *
+ * @since 3.1
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
- // Set the model
- $model = $this->getModel('Tag');
+ // Set the model
+ $model = $this->getModel('Tag');
- // Preset the redirect
- $this->setRedirect('index.php?option=com_tags&view=tags');
+ // Preset the redirect
+ $this->setRedirect('index.php?option=com_tags&view=tags');
- return parent::batch($model);
- }
+ return parent::batch($model);
+ }
}
diff --git a/administrator/components/com_tags/src/Controller/TagsController.php b/administrator/components/com_tags/src/Controller/TagsController.php
index 78c4fefd802cb..e642d9041ff86 100644
--- a/administrator/components/com_tags/src/Controller/TagsController.php
+++ b/administrator/components/com_tags/src/Controller/TagsController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Rebuild the nested set tree.
- *
- * @return boolean False on failure or error, true on success.
- *
- * @since 3.1
- */
- public function rebuild()
- {
- $this->checkToken();
-
- $this->setRedirect(Route::_('index.php?option=com_tags&view=tags', false));
-
- /** @var \Joomla\Component\Tags\Administrator\Model\TagModel $model */
- $model = $this->getModel();
-
- if ($model->rebuild())
- {
- // Rebuild succeeded.
- $this->setMessage(Text::_('COM_TAGS_REBUILD_SUCCESS'));
-
- return true;
- }
- else
- {
- // Rebuild failed.
- $this->setMessage(Text::_('COM_TAGS_REBUILD_FAILURE'));
-
- return false;
- }
- }
-
- /**
- * Method to get the JSON-encoded amount of published tags for quickicons
- *
- * @return void
- *
- * @since 4.1.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('tags');
-
- $model->setState('filter.published', 1);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_TAGS_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_TAGS_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
+ /**
+ * Proxy for getModel
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 3.1
+ */
+ public function getModel($name = 'Tag', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Rebuild the nested set tree.
+ *
+ * @return boolean False on failure or error, true on success.
+ *
+ * @since 3.1
+ */
+ public function rebuild()
+ {
+ $this->checkToken();
+
+ $this->setRedirect(Route::_('index.php?option=com_tags&view=tags', false));
+
+ /** @var \Joomla\Component\Tags\Administrator\Model\TagModel $model */
+ $model = $this->getModel();
+
+ if ($model->rebuild()) {
+ // Rebuild succeeded.
+ $this->setMessage(Text::_('COM_TAGS_REBUILD_SUCCESS'));
+
+ return true;
+ } else {
+ // Rebuild failed.
+ $this->setMessage(Text::_('COM_TAGS_REBUILD_FAILURE'));
+
+ return false;
+ }
+ }
+
+ /**
+ * Method to get the JSON-encoded amount of published tags for quickicons
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('tags');
+
+ $model->setState('filter.published', 1);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_TAGS_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_TAGS_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
}
diff --git a/administrator/components/com_tags/src/Extension/TagsComponent.php b/administrator/components/com_tags/src/Extension/TagsComponent.php
index 4ca170b8e0082..de46fd2425dda 100644
--- a/administrator/components/com_tags/src/Extension/TagsComponent.php
+++ b/administrator/components/com_tags/src/Extension/TagsComponent.php
@@ -1,4 +1,5 @@
'batchAccess',
- 'language_id' => 'batchLanguage',
- );
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
- *
- * @since 3.1
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * Auto-populate the model state.
- *
- * @note Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 3.1
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- $parentId = $app->input->getInt('parent_id');
- $this->setState('tag.parent_id', $parentId);
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState($this->getName() . '.id', $pk);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_tags');
- $this->setState('params', $params);
- }
-
- /**
- * Method to get a tag.
- *
- * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
- *
- * @return mixed Tag data object on success, false on failure.
- *
- * @since 3.1
- */
- public function getItem($pk = null)
- {
- if ($result = parent::getItem($pk))
- {
- // Prime required properties.
- if (empty($result->id))
- {
- $result->parent_id = $this->getState('tag.parent_id');
- }
-
- // Convert the metadata field to an array.
- $registry = new Registry($result->metadata);
- $result->metadata = $registry->toArray();
-
- // Convert the images field to an array.
- $registry = new Registry($result->images);
- $result->images = $registry->toArray();
-
- // Convert the urls field to an array.
- $registry = new Registry($result->urls);
- $result->urls = $registry->toArray();
-
- // Convert the modified date to local user time for display in the form.
- $tz = new \DateTimeZone(Factory::getApplication()->get('offset'));
-
- if ((int) $result->modified_time)
- {
- $date = new Date($result->modified_time);
- $date->setTimezone($tz);
- $result->modified_time = $date->toSql(true);
- }
- else
- {
- $result->modified_time = null;
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get the row form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return bool|\Joomla\CMS\Form\Form A Form object on success, false on failure
- *
- * @since 3.1
- */
- public function getForm($data = array(), $loadData = true)
- {
- $jinput = Factory::getApplication()->input;
-
- // Get the form.
- $form = $this->loadForm('com_tags.tag', 'tag', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- $user = Factory::getUser();
-
- if (!$user->authorise('core.edit.state', 'com_tags' . $jinput->get('id')))
- {
- // Disable fields for display.
- $form->setFieldAttribute('ordering', 'disabled', 'true');
- $form->setFieldAttribute('published', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('ordering', 'filter', 'unset');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 3.1
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_tags.edit.tag.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_tags.tag', $data);
-
- return $data;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 3.1
- */
- public function save($data)
- {
- /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
- $table = $this->getTable();
- $input = Factory::getApplication()->input;
- $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id');
- $isNew = true;
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the save events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- try
- {
- // Load the row if saving an existing tag.
- if ($pk > 0)
- {
- $table->load($pk);
- $isNew = false;
- }
-
- // Set the new parent id if parent id not matched OR while New/Save as Copy .
- if ($table->parent_id != $data['parent_id'] || $data['id'] == 0)
- {
- $table->setLocation($data['parent_id'], 'last-child');
- }
-
- // Alter the title for save as copy
- if ($input->get('task') == 'save2copy')
- {
- $origTable = $this->getTable();
- $origTable->load($input->getInt('id'));
-
- if ($data['title'] == $origTable->title)
- {
- list($title, $alias) = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']);
- $data['title'] = $title;
- $data['alias'] = $alias;
- }
- elseif ($data['alias'] == $origTable->alias)
- {
- $data['alias'] = '';
- }
-
- $data['published'] = 0;
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Prepare the row for saving
- $this->prepareTable($table);
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, $table, $isNew, $data));
-
- if (in_array(false, $result, true))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Store the data.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array($context, $table, $isNew));
-
- // Rebuild the path for the tag:
- if (!$table->rebuildPath($table->id))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Rebuild the paths of the tag's children:
- if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path))
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $this->setState($this->getName() . '.id', $table->id);
- $this->setState($this->getName() . '.new', $isNew);
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Prepare and sanitise the table data prior to saving.
- *
- * @param \Joomla\CMS\Table\Table $table A Table object.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function prepareTable($table)
- {
- // Increment the content version number.
- $table->version++;
- }
-
- /**
- * Method rebuild the entire nested set tree.
- *
- * @return boolean False on failure or error, true otherwise.
- *
- * @since 3.1
- */
- public function rebuild()
- {
- // Get an instance of the table object.
- /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
-
- $table = $this->getTable();
-
- if (!$table->rebuild())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to save the reordered nested set tree.
- * First we save the new order values in the lft values of the changed ids.
- * Then we invoke the table rebuild to implement the new ordering.
- *
- * @param array $idArray An array of primary key ids.
- * @param integer $lftArray The lft value
- *
- * @return boolean False on failure or error, True otherwise
- *
- * @since 3.1
- */
- public function saveorder($idArray = null, $lftArray = null)
- {
- // Get an instance of the table object.
- /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
-
- $table = $this->getTable();
-
- if (!$table->saveorder($idArray, $lftArray))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the title & alias.
- *
- * @param integer $parentId The id of the parent.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 3.1
- */
- protected function generateNewTitle($parentId, $alias, $title)
- {
- // Alter the title & alias
- /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
-
- $table = $this->getTable();
-
- while ($table->load(array('alias' => $alias, 'parent_id' => $parentId)))
- {
- $title = ($table->title != $title) ? $title : StringHelper::increment($title);
- $alias = StringHelper::increment($alias, 'dash');
- }
-
- return array($title, $alias);
- }
+ use VersionableModelTrait;
+
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 3.1
+ */
+ protected $text_prefix = 'COM_TAGS';
+
+ /**
+ * @var string The type alias for this content type.
+ * @since 3.2
+ */
+ public $typeAlias = 'com_tags.tag';
+
+ /**
+ * Allowed batch commands
+ *
+ * @var array
+ * @since 3.7.0
+ */
+ protected $batch_commands = array(
+ 'assetgroup_id' => 'batchAccess',
+ 'language_id' => 'batchLanguage',
+ );
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 3.1
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * Auto-populate the model state.
+ *
+ * @note Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ $parentId = $app->input->getInt('parent_id');
+ $this->setState('tag.parent_id', $parentId);
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState($this->getName() . '.id', $pk);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_tags');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Method to get a tag.
+ *
+ * @param integer $pk An optional id of the object to get, otherwise the id from the model state is used.
+ *
+ * @return mixed Tag data object on success, false on failure.
+ *
+ * @since 3.1
+ */
+ public function getItem($pk = null)
+ {
+ if ($result = parent::getItem($pk)) {
+ // Prime required properties.
+ if (empty($result->id)) {
+ $result->parent_id = $this->getState('tag.parent_id');
+ }
+
+ // Convert the metadata field to an array.
+ $registry = new Registry($result->metadata);
+ $result->metadata = $registry->toArray();
+
+ // Convert the images field to an array.
+ $registry = new Registry($result->images);
+ $result->images = $registry->toArray();
+
+ // Convert the urls field to an array.
+ $registry = new Registry($result->urls);
+ $result->urls = $registry->toArray();
+
+ // Convert the modified date to local user time for display in the form.
+ $tz = new \DateTimeZone(Factory::getApplication()->get('offset'));
+
+ if ((int) $result->modified_time) {
+ $date = new Date($result->modified_time);
+ $date->setTimezone($tz);
+ $result->modified_time = $date->toSql(true);
+ } else {
+ $result->modified_time = null;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the row form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return bool|\Joomla\CMS\Form\Form A Form object on success, false on failure
+ *
+ * @since 3.1
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $jinput = Factory::getApplication()->input;
+
+ // Get the form.
+ $form = $this->loadForm('com_tags.tag', 'tag', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ if (!$user->authorise('core.edit.state', 'com_tags' . $jinput->get('id'))) {
+ // Disable fields for display.
+ $form->setFieldAttribute('ordering', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('ordering', 'filter', 'unset');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 3.1
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_tags.edit.tag.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_tags.tag', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.1
+ */
+ public function save($data)
+ {
+ /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
+ $table = $this->getTable();
+ $input = Factory::getApplication()->input;
+ $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id');
+ $isNew = true;
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the save events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ try {
+ // Load the row if saving an existing tag.
+ if ($pk > 0) {
+ $table->load($pk);
+ $isNew = false;
+ }
+
+ // Set the new parent id if parent id not matched OR while New/Save as Copy .
+ if ($table->parent_id != $data['parent_id'] || $data['id'] == 0) {
+ $table->setLocation($data['parent_id'], 'last-child');
+ }
+
+ // Alter the title for save as copy
+ if ($input->get('task') == 'save2copy') {
+ $origTable = $this->getTable();
+ $origTable->load($input->getInt('id'));
+
+ if ($data['title'] == $origTable->title) {
+ list($title, $alias) = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']);
+ $data['title'] = $title;
+ $data['alias'] = $alias;
+ } elseif ($data['alias'] == $origTable->alias) {
+ $data['alias'] = '';
+ }
+
+ $data['published'] = 0;
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Prepare the row for saving
+ $this->prepareTable($table);
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, $table, $isNew, $data));
+
+ if (in_array(false, $result, true)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Store the data.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array($context, $table, $isNew));
+
+ // Rebuild the path for the tag:
+ if (!$table->rebuildPath($table->id)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Rebuild the paths of the tag's children:
+ if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $this->setState($this->getName() . '.id', $table->id);
+ $this->setState($this->getName() . '.new', $isNew);
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Prepare and sanitise the table data prior to saving.
+ *
+ * @param \Joomla\CMS\Table\Table $table A Table object.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function prepareTable($table)
+ {
+ // Increment the content version number.
+ $table->version++;
+ }
+
+ /**
+ * Method rebuild the entire nested set tree.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since 3.1
+ */
+ public function rebuild()
+ {
+ // Get an instance of the table object.
+ /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
+
+ $table = $this->getTable();
+
+ if (!$table->rebuild()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to save the reordered nested set tree.
+ * First we save the new order values in the lft values of the changed ids.
+ * Then we invoke the table rebuild to implement the new ordering.
+ *
+ * @param array $idArray An array of primary key ids.
+ * @param integer $lftArray The lft value
+ *
+ * @return boolean False on failure or error, True otherwise
+ *
+ * @since 3.1
+ */
+ public function saveorder($idArray = null, $lftArray = null)
+ {
+ // Get an instance of the table object.
+ /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
+
+ $table = $this->getTable();
+
+ if (!$table->saveorder($idArray, $lftArray)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the title & alias.
+ *
+ * @param integer $parentId The id of the parent.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 3.1
+ */
+ protected function generateNewTitle($parentId, $alias, $title)
+ {
+ // Alter the title & alias
+ /** @var \Joomla\Component\Tags\Administrator\Table\TagTable $table */
+
+ $table = $this->getTable();
+
+ while ($table->load(array('alias' => $alias, 'parent_id' => $parentId))) {
+ $title = ($table->title != $title) ? $title : StringHelper::increment($title);
+ $alias = StringHelper::increment($alias, 'dash');
+ }
+
+ return array($title, $alias);
+ }
}
diff --git a/administrator/components/com_tags/src/Model/TagsModel.php b/administrator/components/com_tags/src/Model/TagsModel.php
index 254075adcd502..81738cc0ca69c 100644
--- a/administrator/components/com_tags/src/Model/TagsModel.php
+++ b/administrator/components/com_tags/src/Model/TagsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.filter.extension', 'extension', 'com_content', 'cmd');
-
- $this->setState('filter.extension', $extension);
- $parts = explode('.', $extension);
-
- // Extract the component name
- $this->setState('filter.component', $parts[0]);
-
- // Extract the optional section name
- $this->setState('filter.section', (count($parts) > 1) ? $parts[1] : null);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_tags');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 3.1
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.extension');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.level');
- $id .= ':' . $this->getState('filter.access');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.language');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to create a query for a list of items.
- *
- * @return string
- *
- * @since 3.1
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $user = Factory::getUser();
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.id, a.title, a.alias, a.note, a.published, a.access, a.description' .
- ', a.checked_out, a.checked_out_time, a.created_user_id' .
- ', a.path, a.parent_id, a.level, a.lft, a.rgt' .
- ', a.language'
- )
- );
- $query->from($db->quoteName('#__tags', 'a'))
- ->where($db->quoteName('a.alias') . ' <> ' . $db->quote('root'));
-
- // Join over the language
- $query->select(
- [
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image', 'language_image'),
- ]
- )
- ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
-
- // Join over the users for the checked out user.
- $query->select($db->quoteName('uc.name', 'editor'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
-
- // Join over the users for the author.
- $query->select($db->quoteName('ua.name', 'author_name'))
- ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id'))
- ->select($db->quoteName('ug.title', 'access_title'))
- ->join('LEFT', $db->quoteName('#__viewlevels', 'ug'), $db->quoteName('ug.id') . ' = ' . $db->quoteName('a.access'));
-
- // Count Items
- $subQueryCountTaggedItems = $db->getQuery(true);
- $subQueryCountTaggedItems
- ->select('COUNT(' . $db->quoteName('tag_map.content_item_id') . ')')
- ->from($db->quoteName('#__contentitem_tag_map', 'tag_map'))
- ->where($db->quoteName('tag_map.tag_id') . ' = ' . $db->quoteName('a.id'));
- $query->select('(' . (string) $subQueryCountTaggedItems . ') AS ' . $db->quoteName('countTaggedItems'));
-
- // Filter on the level.
- if ($level = (int) $this->getState('filter.level'))
- {
- $query->where($db->quoteName('a.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Filter by access level.
- if ($access = (int) $this->getState('filter.access'))
- {
- $query->where($db->quoteName('a.access') . ' = :access')
- ->bind(':access', $access, ParameterType::INTEGER);
- }
-
- // Implement View Level Access
- if (!$user->authorise('core.admin'))
- {
- $groups = $user->getAuthorisedViewLevels();
- $query->whereIn($db->quoteName('a.access'), $groups);
- }
-
- // Filter by published state
- $published = (string) $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.published') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif ($published === '')
- {
- $query->whereIn($db->quoteName('a.published'), [0, 1]);
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.title') . ' LIKE :title',
- $db->quoteName('a.alias') . ' LIKE :alias',
- $db->quoteName('a.note') . ' LIKE :note',
-
- ],
- 'OR'
- );
- $query->bind(':title', $search)
- ->bind(':alias', $search)
- ->bind(':note', $search);
- }
- }
-
- // Filter on the language.
- if ($language = $this->getState('filter.language'))
- {
- $query->where($db->quoteName('a.language') . ' = :language')
- ->bind(':language', $language);
- }
-
- // Add the list ordering clause
- $listOrdering = $this->getState('list.ordering', 'a.lft');
- $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
-
- if ($listOrdering == 'a.access')
- {
- $query->order('a.access ' . $listDirn . ', a.lft ' . $listDirn);
- }
- else
- {
- $query->order($db->escape($listOrdering) . ' ' . $listDirn);
- }
-
- return $query;
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 3.0.1
- */
- public function getItems()
- {
- $items = parent::getItems();
-
- if ($items != false)
- {
- $extension = $this->getState('filter.extension');
-
- $this->countItems($items, $extension);
- }
-
- return $items;
- }
-
- /**
- * Method to load the countItems method from the extensions
- *
- * @param \stdClass[] &$items The category items
- * @param string $extension The category extension
- *
- * @return void
- *
- * @since 3.5
- */
- public function countItems(&$items, $extension)
- {
- $parts = explode('.', $extension);
-
- if (count($parts) < 2)
- {
- return;
- }
-
- $component = Factory::getApplication()->bootComponent($parts[0]);
-
- if ($component instanceof TagServiceInterface)
- {
- $component->countTagItems($items, $extension);
- }
- }
-
- /**
- * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
- *
- * @return DatabaseQuery
- *
- * @since 4.0.0
- */
- protected function getEmptyStateQuery()
- {
- $query = parent::getEmptyStateQuery();
-
- $db = $this->getDatabase();
-
- $query->where($db->quoteName('alias') . ' != ' . $db->quote('root'));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id',
+ 'a.id',
+ 'title',
+ 'a.title',
+ 'alias',
+ 'a.alias',
+ 'published',
+ 'a.published',
+ 'access',
+ 'a.access',
+ 'access_level',
+ 'language',
+ 'a.language',
+ 'checked_out',
+ 'a.checked_out',
+ 'checked_out_time',
+ 'a.checked_out_time',
+ 'created_time',
+ 'a.created_time',
+ 'created_user_id',
+ 'a.created_user_id',
+ 'lft',
+ 'a.lft',
+ 'rgt',
+ 'a.rgt',
+ 'level',
+ 'a.level',
+ 'path',
+ 'a.path',
+ 'countTaggedItems',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ $extension = $this->getUserStateFromRequest($this->context . '.filter.extension', 'extension', 'com_content', 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ $parts = explode('.', $extension);
+
+ // Extract the component name
+ $this->setState('filter.component', $parts[0]);
+
+ // Extract the optional section name
+ $this->setState('filter.section', (count($parts) > 1) ? $parts[1] : null);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_tags');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 3.1
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.extension');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.level');
+ $id .= ':' . $this->getState('filter.access');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.language');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to create a query for a list of items.
+ *
+ * @return string
+ *
+ * @since 3.1
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $user = Factory::getUser();
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.title, a.alias, a.note, a.published, a.access, a.description' .
+ ', a.checked_out, a.checked_out_time, a.created_user_id' .
+ ', a.path, a.parent_id, a.level, a.lft, a.rgt' .
+ ', a.language'
+ )
+ );
+ $query->from($db->quoteName('#__tags', 'a'))
+ ->where($db->quoteName('a.alias') . ' <> ' . $db->quote('root'));
+
+ // Join over the language
+ $query->select(
+ [
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image', 'language_image'),
+ ]
+ )
+ ->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
+
+ // Join over the users for the checked out user.
+ $query->select($db->quoteName('uc.name', 'editor'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
+
+ // Join over the users for the author.
+ $query->select($db->quoteName('ua.name', 'author_name'))
+ ->join('LEFT', $db->quoteName('#__users', 'ua'), $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_user_id'))
+ ->select($db->quoteName('ug.title', 'access_title'))
+ ->join('LEFT', $db->quoteName('#__viewlevels', 'ug'), $db->quoteName('ug.id') . ' = ' . $db->quoteName('a.access'));
+
+ // Count Items
+ $subQueryCountTaggedItems = $db->getQuery(true);
+ $subQueryCountTaggedItems
+ ->select('COUNT(' . $db->quoteName('tag_map.content_item_id') . ')')
+ ->from($db->quoteName('#__contentitem_tag_map', 'tag_map'))
+ ->where($db->quoteName('tag_map.tag_id') . ' = ' . $db->quoteName('a.id'));
+ $query->select('(' . (string) $subQueryCountTaggedItems . ') AS ' . $db->quoteName('countTaggedItems'));
+
+ // Filter on the level.
+ if ($level = (int) $this->getState('filter.level')) {
+ $query->where($db->quoteName('a.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Filter by access level.
+ if ($access = (int) $this->getState('filter.access')) {
+ $query->where($db->quoteName('a.access') . ' = :access')
+ ->bind(':access', $access, ParameterType::INTEGER);
+ }
+
+ // Implement View Level Access
+ if (!$user->authorise('core.admin')) {
+ $groups = $user->getAuthorisedViewLevels();
+ $query->whereIn($db->quoteName('a.access'), $groups);
+ }
+
+ // Filter by published state
+ $published = (string) $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.published') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif ($published === '') {
+ $query->whereIn($db->quoteName('a.published'), [0, 1]);
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.title') . ' LIKE :title',
+ $db->quoteName('a.alias') . ' LIKE :alias',
+ $db->quoteName('a.note') . ' LIKE :note',
+
+ ],
+ 'OR'
+ );
+ $query->bind(':title', $search)
+ ->bind(':alias', $search)
+ ->bind(':note', $search);
+ }
+ }
+
+ // Filter on the language.
+ if ($language = $this->getState('filter.language')) {
+ $query->where($db->quoteName('a.language') . ' = :language')
+ ->bind(':language', $language);
+ }
+
+ // Add the list ordering clause
+ $listOrdering = $this->getState('list.ordering', 'a.lft');
+ $listDirn = $db->escape($this->getState('list.direction', 'ASC'));
+
+ if ($listOrdering == 'a.access') {
+ $query->order('a.access ' . $listDirn . ', a.lft ' . $listDirn);
+ } else {
+ $query->order($db->escape($listOrdering) . ' ' . $listDirn);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 3.0.1
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ if ($items != false) {
+ $extension = $this->getState('filter.extension');
+
+ $this->countItems($items, $extension);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Method to load the countItems method from the extensions
+ *
+ * @param \stdClass[] &$items The category items
+ * @param string $extension The category extension
+ *
+ * @return void
+ *
+ * @since 3.5
+ */
+ public function countItems(&$items, $extension)
+ {
+ $parts = explode('.', $extension);
+
+ if (count($parts) < 2) {
+ return;
+ }
+
+ $component = Factory::getApplication()->bootComponent($parts[0]);
+
+ if ($component instanceof TagServiceInterface) {
+ $component->countTagItems($items, $extension);
+ }
+ }
+
+ /**
+ * Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 4.0.0
+ */
+ protected function getEmptyStateQuery()
+ {
+ $query = parent::getEmptyStateQuery();
+
+ $db = $this->getDatabase();
+
+ $query->where($db->quoteName('alias') . ' != ' . $db->quote('root'));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_tags/src/Table/TagTable.php b/administrator/components/com_tags/src/Table/TagTable.php
index 5e65a182664ae..8c74add08c42e 100644
--- a/administrator/components/com_tags/src/Table/TagTable.php
+++ b/administrator/components/com_tags/src/Table/TagTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_tags.tag';
-
- parent::__construct('#__tags', 'id', $db);
- }
-
- /**
- * Overloaded check method to ensure data integrity.
- *
- * @return boolean True on success.
- *
- * @since 3.1
- * @throws \UnexpectedValueException
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Check for valid name.
- if (trim($this->title) == '')
- {
- throw new \UnexpectedValueException('The title is empty');
- }
-
- if (empty($this->alias))
- {
- $this->alias = $this->title;
- }
-
- $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
-
- if (trim(str_replace('-', '', $this->alias)) == '')
- {
- $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
- }
-
- // Check the publish down date is not earlier than publish up.
- if (!empty($this->publish_down) && !empty($this->publish_up) && $this->publish_down < $this->publish_up)
- {
- throw new \UnexpectedValueException('End publish date is before start publish date.');
- }
-
- // Clean up description -- eliminate quotes and <> brackets
- if (!empty($this->metadesc))
- {
- // Only process if not empty
- $bad_characters = array("\"", '<', '>');
- $this->metadesc = StringHelper::str_ireplace($bad_characters, '', $this->metadesc);
- }
-
- if (empty($this->path))
- {
- $this->path = '';
- }
-
- if (empty($this->hits))
- {
- $this->hits = 0;
- }
-
- if (empty($this->params))
- {
- $this->params = '{}';
- }
-
- if (empty($this->metadesc))
- {
- $this->metadesc = '';
- }
-
- if (empty($this->metakey))
- {
- $this->metakey = '';
- }
-
- if (empty($this->metadata))
- {
- $this->metadata = '{}';
- }
-
- if (empty($this->urls))
- {
- $this->urls = '{}';
- }
-
- if (empty($this->images))
- {
- $this->images = '{}';
- }
-
- if (!(int) $this->checked_out_time)
- {
- $this->checked_out_time = null;
- }
-
- if (!(int) $this->publish_up)
- {
- $this->publish_up = null;
- }
-
- if (!(int) $this->publish_down)
- {
- $this->publish_down = null;
- }
-
- return true;
- }
-
- /**
- * Overridden \JTable::store to set modified data and user id.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success.
- *
- * @since 3.1
- */
- public function store($updateNulls = true)
- {
- $date = Factory::getDate();
- $user = Factory::getUser();
-
- if ($this->id)
- {
- // Existing item
- $this->modified_user_id = $user->get('id');
- $this->modified_time = $date->toSql();
- }
- else
- {
- // New tag. A tag created and created_by field can be set by the user,
- // so we don't touch either of these if they are set.
- if (!(int) $this->created_time)
- {
- $this->created_time = $date->toSql();
- }
-
- if (empty($this->created_user_id))
- {
- $this->created_user_id = $user->get('id');
- }
-
- if (!(int) $this->modified_time)
- {
- $this->modified_time = $this->created_time;
- }
-
- if (empty($this->modified_user_id))
- {
- $this->modified_user_id = $this->created_user_id;
- }
- }
-
- // Verify that the alias is unique
- $table = new static($this->getDbo());
-
- if ($table->load(array('alias' => $this->alias)) && ($table->id != $this->id || $this->id == 0))
- {
- $this->setError(Text::_('COM_TAGS_ERROR_UNIQUE_ALIAS'));
-
- return false;
- }
-
- return parent::store($updateNulls);
- }
-
- /**
- * Method to delete a node and, optionally, its child nodes from the table.
- *
- * @param integer $pk The primary key of the node to delete.
- * @param boolean $children True to delete child nodes, false to move them up a level.
- *
- * @return boolean True on success.
- *
- * @since 3.1
- */
- public function delete($pk = null, $children = false)
- {
- $return = parent::delete($pk, $children);
-
- if ($return)
- {
- $helper = new TagsHelper;
- $helper->tagDeleteInstances($pk);
- }
-
- return $return;
- }
-
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ /**
+ * An array of key names to be json encoded in the bind function
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $_jsonEncode = ['params', 'metadata', 'urls', 'images'];
+
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db A database connector object
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_tags.tag';
+
+ parent::__construct('#__tags', 'id', $db);
+ }
+
+ /**
+ * Overloaded check method to ensure data integrity.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.1
+ * @throws \UnexpectedValueException
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Check for valid name.
+ if (trim($this->title) == '') {
+ throw new \UnexpectedValueException('The title is empty');
+ }
+
+ if (empty($this->alias)) {
+ $this->alias = $this->title;
+ }
+
+ $this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);
+
+ if (trim(str_replace('-', '', $this->alias)) == '') {
+ $this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
+ }
+
+ // Check the publish down date is not earlier than publish up.
+ if (!empty($this->publish_down) && !empty($this->publish_up) && $this->publish_down < $this->publish_up) {
+ throw new \UnexpectedValueException('End publish date is before start publish date.');
+ }
+
+ // Clean up description -- eliminate quotes and <> brackets
+ if (!empty($this->metadesc)) {
+ // Only process if not empty
+ $bad_characters = array("\"", '<', '>');
+ $this->metadesc = StringHelper::str_ireplace($bad_characters, '', $this->metadesc);
+ }
+
+ if (empty($this->path)) {
+ $this->path = '';
+ }
+
+ if (empty($this->hits)) {
+ $this->hits = 0;
+ }
+
+ if (empty($this->params)) {
+ $this->params = '{}';
+ }
+
+ if (empty($this->metadesc)) {
+ $this->metadesc = '';
+ }
+
+ if (empty($this->metakey)) {
+ $this->metakey = '';
+ }
+
+ if (empty($this->metadata)) {
+ $this->metadata = '{}';
+ }
+
+ if (empty($this->urls)) {
+ $this->urls = '{}';
+ }
+
+ if (empty($this->images)) {
+ $this->images = '{}';
+ }
+
+ if (!(int) $this->checked_out_time) {
+ $this->checked_out_time = null;
+ }
+
+ if (!(int) $this->publish_up) {
+ $this->publish_up = null;
+ }
+
+ if (!(int) $this->publish_down) {
+ $this->publish_down = null;
+ }
+
+ return true;
+ }
+
+ /**
+ * Overridden \JTable::store to set modified data and user id.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.1
+ */
+ public function store($updateNulls = true)
+ {
+ $date = Factory::getDate();
+ $user = Factory::getUser();
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_user_id = $user->get('id');
+ $this->modified_time = $date->toSql();
+ } else {
+ // New tag. A tag created and created_by field can be set by the user,
+ // so we don't touch either of these if they are set.
+ if (!(int) $this->created_time) {
+ $this->created_time = $date->toSql();
+ }
+
+ if (empty($this->created_user_id)) {
+ $this->created_user_id = $user->get('id');
+ }
+
+ if (!(int) $this->modified_time) {
+ $this->modified_time = $this->created_time;
+ }
+
+ if (empty($this->modified_user_id)) {
+ $this->modified_user_id = $this->created_user_id;
+ }
+ }
+
+ // Verify that the alias is unique
+ $table = new static($this->getDbo());
+
+ if ($table->load(array('alias' => $this->alias)) && ($table->id != $this->id || $this->id == 0)) {
+ $this->setError(Text::_('COM_TAGS_ERROR_UNIQUE_ALIAS'));
+
+ return false;
+ }
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to delete a node and, optionally, its child nodes from the table.
+ *
+ * @param integer $pk The primary key of the node to delete.
+ * @param boolean $children True to delete child nodes, false to move them up a level.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.1
+ */
+ public function delete($pk = null, $children = false)
+ {
+ $return = parent::delete($pk, $children);
+
+ if ($return) {
+ $helper = new TagsHelper();
+ $helper->tagDeleteInstances($pk);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
}
diff --git a/administrator/components/com_tags/src/View/Tag/HtmlView.php b/administrator/components/com_tags/src/View/Tag/HtmlView.php
index 80a2745b25dfc..905e9ad436541 100644
--- a/administrator/components/com_tags/src/View/Tag/HtmlView.php
+++ b/administrator/components/com_tags/src/View/Tag/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @since 3.1
- *
- * @return void
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->get('id');
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
-
- $canDo = ContentHelper::getActions('com_tags');
-
- ToolbarHelper::title($isNew ? Text::_('COM_TAGS_MANAGER_TAG_NEW') : Text::_('COM_TAGS_MANAGER_TAG_EDIT'), 'tag');
-
- // Build the actions for new and existing records.
- if ($isNew)
- {
- ToolbarHelper::apply('tag.apply');
- ToolbarHelper::saveGroup(
- [
- ['save', 'tag.save'],
- ['save2new', 'tag.save2new']
- ],
- 'btn-success'
- );
-
- ToolbarHelper::cancel('tag.cancel');
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_user_id == $userId);
-
- $toolbarButtons = [];
-
- // Can't save the record if it's checked out and editable
- if (!$checkedOut && $itemEditable)
- {
- ToolbarHelper::apply('tag.apply');
- $toolbarButtons[] = ['save', 'tag.save'];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'tag.save2new'];
- }
- }
-
- // If checked out, we can still save
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'tag.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel('tag.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable)
- {
- ToolbarHelper::versions('com_tags.tag', $this->item->id);
- }
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Tags:_New_or_Edit');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * Flag if an association exists
+ *
+ * @var boolean
+ */
+ protected $assoc;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ *
+ * @since 4.0.0
+ */
+ protected $canDo;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @since 3.1
+ *
+ * @return void
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->get('id');
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $userId);
+
+ $canDo = ContentHelper::getActions('com_tags');
+
+ ToolbarHelper::title($isNew ? Text::_('COM_TAGS_MANAGER_TAG_NEW') : Text::_('COM_TAGS_MANAGER_TAG_EDIT'), 'tag');
+
+ // Build the actions for new and existing records.
+ if ($isNew) {
+ ToolbarHelper::apply('tag.apply');
+ ToolbarHelper::saveGroup(
+ [
+ ['save', 'tag.save'],
+ ['save2new', 'tag.save2new']
+ ],
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('tag.cancel');
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_user_id == $userId);
+
+ $toolbarButtons = [];
+
+ // Can't save the record if it's checked out and editable
+ if (!$checkedOut && $itemEditable) {
+ ToolbarHelper::apply('tag.apply');
+ $toolbarButtons[] = ['save', 'tag.save'];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'tag.save2new'];
+ }
+ }
+
+ // If checked out, we can still save
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'tag.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel('tag.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable) {
+ ToolbarHelper::versions('com_tags.tag', $this->item->id);
+ }
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Tags:_New_or_Edit');
+ }
}
diff --git a/administrator/components/com_tags/src/View/Tags/HtmlView.php b/administrator/components/com_tags/src/View/Tags/HtmlView.php
index 3616b1fbcaf5e..3f92a51ba1e1f 100644
--- a/administrator/components/com_tags/src/View/Tags/HtmlView.php
+++ b/administrator/components/com_tags/src/View/Tags/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Preprocess the list of items to find ordering divisions.
- foreach ($this->items as &$item)
- {
- $this->ordering[$item->parent_id][] = $item->id;
- }
-
- // We don't need toolbar in the modal window.
- if ($this->getLayout() !== 'modal')
- {
- $this->addToolbar();
-
- // We do not need to filter by language when multilingual is disabled
- if (!Multilanguage::isEnabled())
- {
- unset($this->activeFilters['language']);
- $this->filterForm->removeField('language', 'filter');
- }
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 3.1
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_tags');
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_TAGS_MANAGER_TAGS'), 'tags');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('tag.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('tags.publish')->listCheck(true);
- $childBar->unpublish('tags.unpublish')->listCheck(true);
- $childBar->archive('tags.archive')->listCheck(true);
- }
-
- if ($user->authorise('core.admin'))
- {
- $childBar->checkin('tags.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2)
- {
- $childBar->trash('tags.trash')->listCheck(true);
- }
-
- // Add a batch button
- if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('tags.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_tags');
- }
-
- $toolbar->help('Tags');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return mixed A string if successful, otherwise an Error object.
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Preprocess the list of items to find ordering divisions.
+ foreach ($this->items as &$item) {
+ $this->ordering[$item->parent_id][] = $item->id;
+ }
+
+ // We don't need toolbar in the modal window.
+ if ($this->getLayout() !== 'modal') {
+ $this->addToolbar();
+
+ // We do not need to filter by language when multilingual is disabled
+ if (!Multilanguage::isEnabled()) {
+ unset($this->activeFilters['language']);
+ $this->filterForm->removeField('language', 'filter');
+ }
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_tags');
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_TAGS_MANAGER_TAGS'), 'tags');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('tag.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $user->authorise('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('tags.publish')->listCheck(true);
+ $childBar->unpublish('tags.unpublish')->listCheck(true);
+ $childBar->archive('tags.archive')->listCheck(true);
+ }
+
+ if ($user->authorise('core.admin')) {
+ $childBar->checkin('tags.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2) {
+ $childBar->trash('tags.trash')->listCheck(true);
+ }
+
+ // Add a batch button
+ if ($canDo->get('core.create') && $canDo->get('core.edit') && $canDo->get('core.edit.state')) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('tags.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_tags');
+ }
+
+ $toolbar->help('Tags');
+ }
}
diff --git a/administrator/components/com_tags/tmpl/tag/edit.php b/administrator/components/com_tags/tmpl/tag/edit.php
index d4cd2270515e3..f451c3cb0f425 100644
--- a/administrator/components/com_tags/tmpl/tag/edit.php
+++ b/administrator/components/com_tags/tmpl/tag/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
// Fieldsets to not automatically render by /layouts/joomla/edit/params.php
$this->ignore_fieldsets = ['jmetadata'];
@@ -27,50 +28,50 @@
diff --git a/administrator/components/com_tags/tmpl/tags/default.php b/administrator/components/com_tags/tmpl/tags/default.php
index c3e455a2b66dc..b7799fd6df4ba 100644
--- a/administrator/components/com_tags/tmpl/tags/default.php
+++ b/administrator/components/com_tags/tmpl/tags/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$app = Factory::getApplication();
$user = Factory::getUser();
@@ -35,249 +36,240 @@
$section = null;
$mode = false;
-if (count($parts) > 1)
-{
- $section = $parts[1];
- $inflector = Inflector::getInstance();
+if (count($parts) > 1) {
+ $section = $parts[1];
+ $inflector = Inflector::getInstance();
- if (!$inflector->isPlural($section))
- {
- $section = $inflector->toPlural($section);
- }
+ if (!$inflector->isPlural($section)) {
+ $section = $inflector->toPlural($section);
+ }
}
-if ($section === 'categories')
-{
- $mode = true;
- $section = $component;
- $component = 'com_categories';
+if ($section === 'categories') {
+ $mode = true;
+ $section = $component;
+ $component = 'com_categories';
}
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_tags&task=tags.saveOrderAjax&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_tags&task=tags.saveOrderAjax&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_tags/tmpl/tags/default_batch_body.php b/administrator/components/com_tags/tmpl/tags/default_batch_body.php
index bc82733e4def3..cffa2753fb27e 100644
--- a/administrator/components/com_tags/tmpl/tags/default_batch_body.php
+++ b/administrator/components/com_tags/tmpl/tags/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Multilanguage;
@@ -15,18 +17,18 @@
?>
diff --git a/administrator/components/com_tags/tmpl/tags/default_batch_footer.php b/administrator/components/com_tags/tmpl/tags/default_batch_footer.php
index a30292feb9cd3..c448ec1ba0dd8 100644
--- a/administrator/components/com_tags/tmpl/tags/default_batch_footer.php
+++ b/administrator/components/com_tags/tmpl/tags/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_tags/tmpl/tags/emptystate.php b/administrator/components/com_tags/tmpl/tags/emptystate.php
index 04eb96caa97c5..2b49d1b63bcda 100644
--- a/administrator/components/com_tags/tmpl/tags/emptystate.php
+++ b/administrator/components/com_tags/tmpl/tags/emptystate.php
@@ -1,4 +1,5 @@
'COM_TAGS',
- 'formURL' => 'index.php?option=com_tags&task=tag.add',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/J3.x:How_To_Use_Content_Tags_in_Joomla!',
- 'icon' => 'icon-tags tags',
+ 'textPrefix' => 'COM_TAGS',
+ 'formURL' => 'index.php?option=com_tags&task=tag.add',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/J3.x:How_To_Use_Content_Tags_in_Joomla!',
+ 'icon' => 'icon-tags tags',
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_tags'))
-{
- $displayData['createURL'] = 'index.php?option=com_tags&task=tag.add';
+if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_tags')) {
+ $displayData['createURL'] = 'index.php?option=com_tags&task=tag.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_templates/helpers/template.php b/administrator/components/com_templates/helpers/template.php
index 38115180eef74..873db920a2c28 100644
--- a/administrator/components/com_templates/helpers/template.php
+++ b/administrator/components/com_templates/helpers/template.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Template Helper class.
diff --git a/administrator/components/com_templates/helpers/templates.php b/administrator/components/com_templates/helpers/templates.php
index 6ff305326e12f..5f719455936ad 100644
--- a/administrator/components/com_templates/helpers/templates.php
+++ b/administrator/components/com_templates/helpers/templates.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Templates component helper.
diff --git a/administrator/components/com_templates/services/provider.php b/administrator/components/com_templates/services/provider.php
index 737e8e5fcd45b..af2dff277bdb8 100644
--- a/administrator/components/com_templates/services/provider.php
+++ b/administrator/components/com_templates/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Templates'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Templates'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Templates'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Templates'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new TemplatesComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new TemplatesComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_templates/src/Controller/DisplayController.php b/administrator/components/com_templates/src/Controller/DisplayController.php
index d7eec744efa42..a253dcebe27e9 100644
--- a/administrator/components/com_templates/src/Controller/DisplayController.php
+++ b/administrator/components/com_templates/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->get('view', 'styles');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param boolean $urlparams An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
+ *
+ * @return static|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = false)
+ {
+ $view = $this->input->get('view', 'styles');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
- // For JSON requests
- if ($this->app->getDocument()->getType() == 'json')
- {
- return parent::display();
- }
+ // For JSON requests
+ if ($this->app->getDocument()->getType() == 'json') {
+ return parent::display();
+ }
- // Check for edit form.
- if ($view == 'style' && $layout == 'edit' && !$this->checkEditId('com_templates.edit.style', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
+ // Check for edit form.
+ if ($view == 'style' && $layout == 'edit' && !$this->checkEditId('com_templates.edit.style', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
- $this->setRedirect(Route::_('index.php?option=com_templates&view=styles', false));
+ $this->setRedirect(Route::_('index.php?option=com_templates&view=styles', false));
- return false;
- }
+ return false;
+ }
- return parent::display();
- }
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_templates/src/Controller/StyleController.php b/administrator/components/com_templates/src/Controller/StyleController.php
index 748af6bcb6e7b..c324ca8f09eaf 100644
--- a/administrator/components/com_templates/src/Controller/StyleController.php
+++ b/administrator/components/com_templates/src/Controller/StyleController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Templates\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Templates\Administrator\Controller;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
@@ -21,135 +21,124 @@
*/
class StyleController extends FormController
{
- /**
- * The prefix to use with controller messages.
- *
- * @var string
- * @since 1.6
- */
- protected $text_prefix = 'COM_TEMPLATES_STYLE';
-
- /**
- * Method to save a template style.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 1.6
- */
- public function save($key = null, $urlVar = null)
- {
- $this->checkToken();
-
- if ($this->app->getDocument()->getType() === 'json')
- {
- $model = $this->getModel('Style', 'Administrator');
- $table = $model->getTable();
- $data = $this->input->post->get('params', array(), 'array');
- $checkin = $table->hasField('checked_out');
- $context = $this->option . '.edit.' . $this->context;
-
- $item = $model->getItem($this->app->getTemplate(true)->id);
-
- // Setting received params
- $item->set('params', $data);
-
- $data = $item->getProperties();
- unset($data['xml']);
-
- $key = $table->getKeyName();
-
- // Access check.
- if (!$this->allowSave($data, $key))
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return false;
- }
-
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_templates/forms');
-
- // Validate the posted data.
- // Sometimes the form needs some posted data, such as for plugins and modules.
- $form = $model->getForm($data, false);
-
- if (!$form)
- {
- $this->app->enqueueMessage($model->getError(), 'error');
-
- return false;
- }
-
- // Test whether the data is valid.
- $validData = $model->validate($form, $data);
-
- if ($validData === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $this->app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $data);
-
- return false;
- }
-
- if (!isset($validData['tags']))
- {
- $validData['tags'] = null;
- }
-
- // Attempt to save the data.
- if (!$model->save($validData))
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $validData);
-
- $this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
-
- return false;
- }
-
- // Save succeeded, so check-in the record.
- if ($checkin && $model->checkin($validData[$key]) === false)
- {
- // Save the data in the session.
- $this->app->setUserState($context . '.data', $validData);
-
- // Check-in failed, so go back to the record and display a notice.
- $this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
-
- return false;
- }
-
- // Redirect the user and adjust session state
- // Set the record data in the session.
- $recordId = $model->getState($this->context . '.id');
- $this->holdEditId($context, $recordId);
- $this->app->setUserState($context . '.data', null);
- $model->checkout($recordId);
-
- // Invoke the postSave method to allow for the child class to access the model.
- $this->postSaveHook($model, $validData);
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_TEMPLATES_STYLE';
+
+ /**
+ * Method to save a template style.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 1.6
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ $this->checkToken();
+
+ if ($this->app->getDocument()->getType() === 'json') {
+ $model = $this->getModel('Style', 'Administrator');
+ $table = $model->getTable();
+ $data = $this->input->post->get('params', array(), 'array');
+ $checkin = $table->hasField('checked_out');
+ $context = $this->option . '.edit.' . $this->context;
+
+ $item = $model->getItem($this->app->getTemplate(true)->id);
+
+ // Setting received params
+ $item->set('params', $data);
+
+ $data = $item->getProperties();
+ unset($data['xml']);
+
+ $key = $table->getKeyName();
+
+ // Access check.
+ if (!$this->allowSave($data, $key)) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return false;
+ }
+
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_templates/forms');
+
+ // Validate the posted data.
+ // Sometimes the form needs some posted data, such as for plugins and modules.
+ $form = $model->getForm($data, false);
+
+ if (!$form) {
+ $this->app->enqueueMessage($model->getError(), 'error');
+
+ return false;
+ }
+
+ // Test whether the data is valid.
+ $validData = $model->validate($form, $data);
+
+ if ($validData === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $this->app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $data);
+
+ return false;
+ }
+
+ if (!isset($validData['tags'])) {
+ $validData['tags'] = null;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($validData)) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $validData);
+
+ $this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
+
+ return false;
+ }
+
+ // Save succeeded, so check-in the record.
+ if ($checkin && $model->checkin($validData[$key]) === false) {
+ // Save the data in the session.
+ $this->app->setUserState($context . '.data', $validData);
+
+ // Check-in failed, so go back to the record and display a notice.
+ $this->app->enqueueMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
+
+ return false;
+ }
+
+ // Redirect the user and adjust session state
+ // Set the record data in the session.
+ $recordId = $model->getState($this->context . '.id');
+ $this->holdEditId($context, $recordId);
+ $this->app->setUserState($context . '.data', null);
+ $model->checkout($recordId);
+
+ // Invoke the postSave method to allow for the child class to access the model.
+ $this->postSaveHook($model, $validData);
- return true;
- }
+ return true;
+ }
- return parent::save($key, $urlVar);
- }
+ return parent::save($key, $urlVar);
+ }
}
diff --git a/administrator/components/com_templates/src/Controller/StylesController.php b/administrator/components/com_templates/src/Controller/StylesController.php
index 2a3afaa530ed2..016ad3df9b6bf 100644
--- a/administrator/components/com_templates/src/Controller/StylesController.php
+++ b/administrator/components/com_templates/src/Controller/StylesController.php
@@ -1,4 +1,5 @@
checkToken();
-
- $pks = (array) $this->input->post->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $pks = array_filter($pks);
-
- try
- {
- if (empty($pks))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
- }
-
- $model = $this->getModel();
- $model->duplicate($pks);
- $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_DUPLICATED'));
- }
- catch (\Exception $e)
- {
- $this->app->enqueueMessage($e->getMessage(), 'error');
- }
-
- $this->setRedirect('index.php?option=com_templates&view=styles');
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return BaseDatabaseModel
- *
- * @since 1.6
- */
- public function getModel($name = 'Style', $prefix = 'Administrator', $config = array())
- {
- return parent::getModel($name, $prefix, array('ignore_request' => true));
- }
-
- /**
- * Method to set the home template for a client.
- *
- * @return void
- *
- * @since 1.6
- */
- public function setDefault()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $pks = (array) $this->input->post->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $pks = array_filter($pks);
-
- try
- {
- if (empty($pks))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
- }
-
- // Pop off the first element.
- $id = array_shift($pks);
-
- /** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
- $model = $this->getModel();
- $model->setHome($id);
- $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_SET'));
- }
- catch (\Exception $e)
- {
- $this->setMessage($e->getMessage(), 'warning');
- }
-
- $this->setRedirect('index.php?option=com_templates&view=styles');
- }
-
- /**
- * Method to unset the default template for a client and for a language
- *
- * @return void
- *
- * @since 1.6
- */
- public function unsetDefault()
- {
- // Check for request forgeries
- $this->checkToken('request');
-
- $pks = (array) $this->input->get->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $pks = array_filter($pks);
-
- try
- {
- if (empty($pks))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
- }
-
- // Pop off the first element.
- $id = array_shift($pks);
-
- /** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
- $model = $this->getModel();
- $model->unsetHome($id);
- $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_UNSET'));
- }
- catch (\Exception $e)
- {
- $this->setMessage($e->getMessage(), 'warning');
- }
-
- $this->setRedirect('index.php?option=com_templates&view=styles');
- }
+ /**
+ * Method to clone and existing template style.
+ *
+ * @return void
+ */
+ public function duplicate()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $pks = (array) $this->input->post->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $pks = array_filter($pks);
+
+ try {
+ if (empty($pks)) {
+ throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
+ }
+
+ $model = $this->getModel();
+ $model->duplicate($pks);
+ $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_DUPLICATED'));
+ } catch (\Exception $e) {
+ $this->app->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ $this->setRedirect('index.php?option=com_templates&view=styles');
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return BaseDatabaseModel
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Style', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getModel($name, $prefix, array('ignore_request' => true));
+ }
+
+ /**
+ * Method to set the home template for a client.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function setDefault()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $pks = (array) $this->input->post->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $pks = array_filter($pks);
+
+ try {
+ if (empty($pks)) {
+ throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
+ }
+
+ // Pop off the first element.
+ $id = array_shift($pks);
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
+ $model = $this->getModel();
+ $model->setHome($id);
+ $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_SET'));
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'warning');
+ }
+
+ $this->setRedirect('index.php?option=com_templates&view=styles');
+ }
+
+ /**
+ * Method to unset the default template for a client and for a language
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function unsetDefault()
+ {
+ // Check for request forgeries
+ $this->checkToken('request');
+
+ $pks = (array) $this->input->get->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $pks = array_filter($pks);
+
+ try {
+ if (empty($pks)) {
+ throw new \Exception(Text::_('COM_TEMPLATES_NO_TEMPLATE_SELECTED'));
+ }
+
+ // Pop off the first element.
+ $id = array_shift($pks);
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\StyleModel $model */
+ $model = $this->getModel();
+ $model->unsetHome($id);
+ $this->setMessage(Text::_('COM_TEMPLATES_SUCCESS_HOME_UNSET'));
+ } catch (\Exception $e) {
+ $this->setMessage($e->getMessage(), 'warning');
+ }
+
+ $this->setRedirect('index.php?option=com_templates&view=styles');
+ }
}
diff --git a/administrator/components/com_templates/src/Controller/TemplateController.php b/administrator/components/com_templates/src/Controller/TemplateController.php
index 15643a5cfe776..01bb5a6d073a8 100644
--- a/administrator/components/com_templates/src/Controller/TemplateController.php
+++ b/administrator/components/com_templates/src/Controller/TemplateController.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- $this->registerTask('unpublish', 'publish');
- $this->registerTask('publish', 'publish');
- $this->registerTask('deleteOverrideHistory', 'publish');
- }
-
- /**
- * Method for closing the template.
- *
- * @return void
- *
- * @since 3.2
- */
- public function cancel()
- {
- $this->setRedirect(Route::_('index.php?option=com_templates&view=templates', false));
- }
-
- /**
- * Method for closing a file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function close()
- {
- $file = base64_encode('home');
- $id = (int) $this->input->get('id', 0, 'int');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' .
- $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
-
- /**
- * Marked as Checked/Unchecked of override history.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function publish()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $file = $this->input->get('file');
- $id = $this->input->get('id');
-
- $ids = (array) $this->input->get('cid', array(), 'string');
- $values = array('publish' => 1, 'unpublish' => 0, 'deleteOverrideHistory' => -3);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($values, $task, 0, 'int');
-
- if (empty($ids))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_NO_FILE_SELECTED'), 'warning');
- }
- else
- {
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- // Change the state of the records.
- if (!$model->publish($ids, $value, $id))
- {
- $this->setMessage(implode(' ', $model->getErrors()), 'warning');
- }
- else
- {
- if ($value === 1)
- {
- $ntext = 'COM_TEMPLATES_N_OVERRIDE_CHECKED';
- }
- elseif ($value === 0)
- {
- $ntext = 'COM_TEMPLATES_N_OVERRIDE_UNCHECKED';
- }
- elseif ($value === -3)
- {
- $ntext = 'COM_TEMPLATES_N_OVERRIDE_DELETED';
- }
-
- $this->setMessage(Text::plural($ntext, count($ids)));
- }
- }
-
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' .
- $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
-
- /**
- * Method for copying the template.
- *
- * @return boolean true on success, false otherwise
- *
- * @since 3.2
- */
- public function copy()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $app = $this->app;
- $this->input->set('installtype', 'folder');
- $newNameRaw = $this->input->get('new_name', null, 'string');
- // Only accept letters, numbers and underscore for template name
- $newName = preg_replace('/[^a-zA-Z0-9_]/', '', $newNameRaw);
- $templateID = (int) $this->input->getInt('id', 0);
- $file = (string) $this->input->get('file', '', 'cmd');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return false;
- }
-
- $this->setRedirect('index.php?option=com_templates&view=template&id=' . $templateID . '&file=' . $file);
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel('Template', 'Administrator');
- $model->setState('new_name', $newName);
- $model->setState('tmp_prefix', uniqid('template_copy_'));
- $model->setState('to_path', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
-
- // Process only if we have a new name entered
- if (strlen($newName) > 0)
- {
- if (!$this->app->getIdentity()->authorise('core.create', 'com_templates'))
- {
- // User is not authorised to delete
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_CREATE_NOT_PERMITTED'), 'error');
-
- return false;
- }
-
- // Check that new name is valid
- if (($newNameRaw !== null) && ($newName !== $newNameRaw))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
-
- return false;
- }
-
- // Check that new name doesn't already exist
- if (!$model->checkNewName())
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_DUPLICATE_TEMPLATE_NAME'), 'error');
-
- return false;
- }
-
- // Check that from name does exist and get the folder name
- $fromName = $model->getFromName();
-
- if (!$fromName)
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
-
- return false;
- }
-
- // Call model's copy method
- if (!$model->copy())
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_COPY'), 'error');
-
- return false;
- }
-
- // Call installation model
- $this->input->set('install_directory', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
-
- /** @var \Joomla\Component\Installer\Administrator\Model\InstallModel $installModel */
- $installModel = $this->app->bootComponent('com_installer')
- ->getMVCFactory()->createModel('Install', 'Administrator');
- $this->app->getLanguage()->load('com_installer');
-
- if (!$installModel->install())
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INSTALL'), 'error');
-
- return false;
- }
-
- $this->setMessage(Text::sprintf('COM_TEMPLATES_COPY_SUCCESS', $newName));
- $model->cleanup();
-
- return true;
- }
-
- $this->setMessage(Text::sprintf('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
-
- return false;
- }
-
- /**
- * Method to get a model object, loading it if required.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional (note, the empty array is atypical compared to other models).
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 3.2
- */
- public function getModel($name = 'Template', $prefix = 'Administrator', $config = array())
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to check if you can add a new record.
- *
- * @return boolean
- *
- * @since 3.2
- */
- protected function allowEdit()
- {
- return $this->app->getIdentity()->authorise('core.admin');
- }
-
- /**
- * Saves a template source file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function save()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $data = $this->input->post->get('jform', array(), 'array');
- $task = $this->getTask();
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $fileName = (string) $this->input->getCmd('file', '');
- $explodeArray = explode(':', str_replace('//', '/', base64_decode($fileName)));
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- // Match the stored id's with the submitted.
- if (empty($data['extension_id']) || empty($data['filename']))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
-
- return;
- }
- elseif ($data['extension_id'] != $model->getState('extension.id'))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
-
- return;
- }
- elseif (str_ends_with(end($explodeArray), Path::clean($data['filename'], '/')))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
-
- return;
- }
-
- // Validate the posted data.
- $form = $model->getForm();
-
- if (!$form)
- {
- $this->setMessage($model->getError(), 'error');
-
- return;
- }
-
- $data = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($data === false)
- {
- // Get the validation messages.
- $errors = $model->getErrors();
-
- // Push up to three validation messages out to the user.
- for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
- }
- else
- {
- $this->app->enqueueMessage($errors[$i], 'warning');
- }
- }
-
- // Redirect back to the edit screen.
- $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
-
- return;
- }
-
- // Attempt to save the data.
- if (!$model->save($data))
- {
- // Redirect back to the edit screen.
- $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED', $model->getError()), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
-
- return;
- }
-
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_SAVE_SUCCESS'));
-
- // Redirect the user based on the chosen task.
- switch ($task)
- {
- case 'apply':
- // Redirect back to the edit screen.
- $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- break;
-
- default:
- // Redirect to the list screen.
- $file = base64_encode('home');
- $id = (int) $this->input->get('id', 0, 'int');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- break;
- }
- }
-
- /**
- * Method for creating override.
- *
- * @return void
- *
- * @since 3.2
- */
- public function overrides()
- {
- // Check for request forgeries.
- $this->checkToken('get');
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $file = (string) $this->input->getCmd('file', '');
- $override = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('folder', '')), 'path');
- $id = (int) $this->input->get('id', 0, 'int');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- $model->createOverride($override);
-
- // Redirect back to the edit screen.
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
-
- /**
- * Method for deleting a file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function delete()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (base64_decode(urldecode($file)) == '/index.php')
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INDEX_DELETE'), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif (base64_decode(urldecode($file)) == '/joomla.asset.json')
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_ASSET_FILE_DELETE'), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->deleteFile($file))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_DELETE_SUCCESS'));
- $file = base64_encode('home');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_DELETE'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for creating a new file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function createFile()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->get('file', '', 'cmd');
- $name = (string) $this->input->get('name', '', 'cmd');
- $location = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
- $type = (string) $this->input->get('type', '', 'cmd');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if ($type == 'null')
- {
- $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_TYPE'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif (!preg_match('/^[a-zA-Z0-9-_]+$/', $name))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->createFile($name, $type, $location))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_CREATE_SUCCESS'));
- $file = urlencode(base64_encode($location . '/' . $name . '.' . $type));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_CREATE'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for uploading a file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function uploadFile()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $upload = $this->input->files->get('files');
- $location = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if ($return = $model->uploadFile($upload, $location))
- {
- $this->setMessage(Text::sprintf('COM_TEMPLATES_FILE_UPLOAD_SUCCESS', $upload['name']));
- $redirect = base64_encode($return);
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $redirect . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_UPLOAD'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for creating a new folder.
- *
- * @return void
- *
- * @since 3.2
- */
- public function createFolder()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $name = $this->input->get('name');
- $location = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (!preg_match('/^[a-zA-Z0-9-_.]+$/', $name))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FOLDER_NAME'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->createFolder($name, $location))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_CREATE_SUCCESS'));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FOLDER_CREATE'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for deleting a folder.
- *
- * @return void
- *
- * @since 3.2
- */
- public function deleteFolder()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $isMedia = (int) $this->input->get('isMedia', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $location = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (empty($location))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_ROOT_DELETE'), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->deleteFolder($location))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_SUCCESS'));
-
- if (stristr(base64_decode($file), $location) != false)
- {
- $file = base64_encode('home');
- }
-
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_ERROR'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for renaming a file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function renameFile()
- {
- // Check for request forgeries
- $this->checkToken();
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
- $id = (int) $this->input->get('id', 0, 'int');
- $isMedia = (int) $this->input->get('isMedia', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $newName = $this->input->get('new_name');
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (base64_decode(urldecode($file)) == '/index.php')
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_RENAME_INDEX'), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- elseif (base64_decode(urldecode($file)) == '/joomla.asset.json')
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_RENAME_ASSET_FILE'), 'warning');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- elseif (!preg_match('/^[a-zA-Z0-9-_]+$/', $newName))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($rename = $model->renameFile($file, $newName))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_RENAME_SUCCESS'));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $rename . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_RENAME'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for cropping an image.
- *
- * @return void
- *
- * @since 3.2
- */
- public function cropImage()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->get('file', '', 'cmd');
- $x = $this->input->get('x');
- $y = $this->input->get('y');
- $w = $this->input->get('w');
- $h = $this->input->get('h');
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (empty($w) && empty($h) && empty($x) && empty($y))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_CROP_AREA_ERROR'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->cropImage($file, $w, $h, $x, $y))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_CROP_SUCCESS'));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_CROP_ERROR'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for resizing an image.
- *
- * @return void
- *
- * @since 3.2
- */
- public function resizeImage()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $width = $this->input->get('width');
- $height = $this->input->get('height');
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if ($model->resizeImage($file, $width, $height))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_RESIZE_SUCCESS'));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_RESIZE_ERROR'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for copying a file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function copyFile()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
- $newName = $this->input->get('new_name');
- $location = (string) InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )
- ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if (!preg_match('/^[a-zA-Z0-9-_]+$/', $newName))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- elseif ($model->copyFile($newName, $location, $file))
- {
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_COPY_FAIL'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Method for extracting an archive file.
- *
- * @return void
- *
- * @since 3.2
- */
- public function extractArchive()
- {
- // Check for request forgeries
- $this->checkToken();
-
- $id = (int) $this->input->get('id', 0, 'int');
- $file = (string) $this->input->getCmd('file', '');
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return;
- }
-
- if ($model->extractArchive($file))
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_SUCCESS'));
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file;
- $this->setRedirect(Route::_($url, false));
- }
- else
- {
- $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_FAIL'), 'error');
- $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file;
- $this->setRedirect(Route::_($url, false));
- }
- }
-
- /**
- * Fetch and report updates in \JSON format, for AJAX requests
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function ajax()
- {
- $app = $this->app;
-
- if (!Session::checkToken('get'))
- {
- $app->setHeader('status', 403, true);
- $app->sendHeaders();
- echo Text::_('JINVALID_TOKEN_NOTICE');
- $app->close();
- }
-
- // Checks status of installer override plugin.
- if (!PluginHelper::isEnabled('installer', 'override'))
- {
- $error = array('installerOverride' => 'disabled');
-
- echo json_encode($error);
-
- $app->close();
- }
-
- /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel();
-
- $result = $model->getUpdatedList(true, true);
-
- echo json_encode($result);
-
- $app->close();
- }
-
-
- /**
- * Method for creating a child template.
- *
- * @return boolean true on success, false otherwise
- *
- * @since 4.1.0
- */
- public function child()
- {
- // Check for request forgeries
- $this->checkToken();
-
- // Access check.
- if (!$this->allowEdit())
- {
- $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
-
- return false;
- }
-
- $this->input->set('installtype', 'folder');
- $newNameRaw = $this->input->get('new_name', null, 'string');
-
- // Only accept letters, numbers and underscore for template name
- $newName = preg_replace('/[^a-zA-Z0-9_]/', '', $newNameRaw);
- $templateID = (int) $this->input->getInt('id', 0);
- $file = (string) $this->input->get('file', '', 'cmd');
- $extraStyles = (array) $this->input->get('style_ids', [], 'array');
-
- $this->setRedirect('index.php?option=com_templates&view=template&id=' . $templateID . '&file=' . $file);
-
- /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
- $model = $this->getModel('Template', 'Administrator');
- $model->setState('new_name', $newName);
- $model->setState('tmp_prefix', uniqid('template_child_'));
- $model->setState('to_path', $this->app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
-
- // Process only if we have a new name entered
- if (!strlen($newName)) {
- $this->setMessage(Text::sprintf('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
-
- return false;
- }
-
- // Process only if user is allowed to create child template
- if (!$this->app->getIdentity()->authorise('core.create', 'com_templates')) {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_CREATE_NOT_PERMITTED'), 'error');
-
- return false;
- }
-
- // Check that new name is valid
- if (($newNameRaw !== null) && ($newName !== $newNameRaw)) {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
-
- return false;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 1.6
+ * @see BaseController
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('apply', 'save');
+ $this->registerTask('unpublish', 'publish');
+ $this->registerTask('publish', 'publish');
+ $this->registerTask('deleteOverrideHistory', 'publish');
+ }
+
+ /**
+ * Method for closing the template.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function cancel()
+ {
+ $this->setRedirect(Route::_('index.php?option=com_templates&view=templates', false));
+ }
+
+ /**
+ * Method for closing a file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function close()
+ {
+ $file = base64_encode('home');
+ $id = (int) $this->input->get('id', 0, 'int');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' .
+ $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+
+ /**
+ * Marked as Checked/Unchecked of override history.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function publish()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $file = $this->input->get('file');
+ $id = $this->input->get('id');
+
+ $ids = (array) $this->input->get('cid', array(), 'string');
+ $values = array('publish' => 1, 'unpublish' => 0, 'deleteOverrideHistory' => -3);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+
+ if (empty($ids)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_NO_FILE_SELECTED'), 'warning');
+ } else {
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Change the state of the records.
+ if (!$model->publish($ids, $value, $id)) {
+ $this->setMessage(implode(' ', $model->getErrors()), 'warning');
+ } else {
+ if ($value === 1) {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_CHECKED';
+ } elseif ($value === 0) {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_UNCHECKED';
+ } elseif ($value === -3) {
+ $ntext = 'COM_TEMPLATES_N_OVERRIDE_DELETED';
+ }
+
+ $this->setMessage(Text::plural($ntext, count($ids)));
+ }
+ }
+
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' .
+ $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+
+ /**
+ * Method for copying the template.
+ *
+ * @return boolean true on success, false otherwise
+ *
+ * @since 3.2
+ */
+ public function copy()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $app = $this->app;
+ $this->input->set('installtype', 'folder');
+ $newNameRaw = $this->input->get('new_name', null, 'string');
+ // Only accept letters, numbers and underscore for template name
+ $newName = preg_replace('/[^a-zA-Z0-9_]/', '', $newNameRaw);
+ $templateID = (int) $this->input->getInt('id', 0);
+ $file = (string) $this->input->get('file', '', 'cmd');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return false;
+ }
+
+ $this->setRedirect('index.php?option=com_templates&view=template&id=' . $templateID . '&file=' . $file);
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel('Template', 'Administrator');
+ $model->setState('new_name', $newName);
+ $model->setState('tmp_prefix', uniqid('template_copy_'));
+ $model->setState('to_path', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
+
+ // Process only if we have a new name entered
+ if (strlen($newName) > 0) {
+ if (!$this->app->getIdentity()->authorise('core.create', 'com_templates')) {
+ // User is not authorised to delete
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_CREATE_NOT_PERMITTED'), 'error');
+
+ return false;
+ }
+
+ // Check that new name is valid
+ if (($newNameRaw !== null) && ($newName !== $newNameRaw)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
+
+ return false;
+ }
+
+ // Check that new name doesn't already exist
+ if (!$model->checkNewName()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_DUPLICATE_TEMPLATE_NAME'), 'error');
+
+ return false;
+ }
+
+ // Check that from name does exist and get the folder name
+ $fromName = $model->getFromName();
+
+ if (!$fromName) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
+
+ return false;
+ }
+
+ // Call model's copy method
+ if (!$model->copy()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_COPY'), 'error');
+
+ return false;
+ }
+
+ // Call installation model
+ $this->input->set('install_directory', $app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
+
+ /** @var \Joomla\Component\Installer\Administrator\Model\InstallModel $installModel */
+ $installModel = $this->app->bootComponent('com_installer')
+ ->getMVCFactory()->createModel('Install', 'Administrator');
+ $this->app->getLanguage()->load('com_installer');
+
+ if (!$installModel->install()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INSTALL'), 'error');
+
+ return false;
+ }
+
+ $this->setMessage(Text::sprintf('COM_TEMPLATES_COPY_SUCCESS', $newName));
+ $model->cleanup();
+
+ return true;
+ }
+
+ $this->setMessage(Text::sprintf('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
+
+ return false;
+ }
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional (note, the empty array is atypical compared to other models).
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 3.2
+ */
+ public function getModel($name = 'Template', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ protected function allowEdit()
+ {
+ return $this->app->getIdentity()->authorise('core.admin');
+ }
+
+ /**
+ * Saves a template source file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $data = $this->input->post->get('jform', array(), 'array');
+ $task = $this->getTask();
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $fileName = (string) $this->input->getCmd('file', '');
+ $explodeArray = explode(':', str_replace('//', '/', base64_decode($fileName)));
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ // Match the stored id's with the submitted.
+ if (empty($data['extension_id']) || empty($data['filename'])) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
+
+ return;
+ } elseif ($data['extension_id'] != $model->getState('extension.id')) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
+
+ return;
+ } elseif (str_ends_with(end($explodeArray), Path::clean($data['filename'], '/'))) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_ID_FILENAME_MISMATCH'), 'error');
+
+ return;
+ }
+
+ // Validate the posted data.
+ $form = $model->getForm();
+
+ if (!$form) {
+ $this->setMessage($model->getError(), 'error');
+
+ return;
+ }
+
+ $data = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($data === false) {
+ // Get the validation messages.
+ $errors = $model->getErrors();
+
+ // Push up to three validation messages out to the user.
+ for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $this->app->enqueueMessage($errors[$i]->getMessage(), 'warning');
+ } else {
+ $this->app->enqueueMessage($errors[$i], 'warning');
+ }
+ }
+
+ // Redirect back to the edit screen.
+ $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+
+ return;
+ }
+
+ // Attempt to save the data.
+ if (!$model->save($data)) {
+ // Redirect back to the edit screen.
+ $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED', $model->getError()), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+
+ return;
+ }
+
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_SAVE_SUCCESS'));
+
+ // Redirect the user based on the chosen task.
+ switch ($task) {
+ case 'apply':
+ // Redirect back to the edit screen.
+ $url = 'index.php?option=com_templates&view=template&id=' . $model->getState('extension.id') . '&file=' . $fileName . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ break;
+
+ default:
+ // Redirect to the list screen.
+ $file = base64_encode('home');
+ $id = (int) $this->input->get('id', 0, 'int');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ break;
+ }
+ }
+
+ /**
+ * Method for creating override.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function overrides()
+ {
+ // Check for request forgeries.
+ $this->checkToken('get');
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $file = (string) $this->input->getCmd('file', '');
+ $override = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('folder', '')), 'path');
+ $id = (int) $this->input->get('id', 0, 'int');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ $model->createOverride($override);
+
+ // Redirect back to the edit screen.
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+
+ /**
+ * Method for deleting a file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function delete()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (base64_decode(urldecode($file)) == '/index.php') {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INDEX_DELETE'), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif (base64_decode(urldecode($file)) == '/joomla.asset.json') {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_ASSET_FILE_DELETE'), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->deleteFile($file)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_DELETE_SUCCESS'));
+ $file = base64_encode('home');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_DELETE'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for creating a new file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function createFile()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->get('file', '', 'cmd');
+ $name = (string) $this->input->get('name', '', 'cmd');
+ $location = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
+ $type = (string) $this->input->get('type', '', 'cmd');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if ($type == 'null') {
+ $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_TYPE'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif (!preg_match('/^[a-zA-Z0-9-_]+$/', $name)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->createFile($name, $type, $location)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_CREATE_SUCCESS'));
+ $file = urlencode(base64_encode($location . '/' . $name . '.' . $type));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_CREATE'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for uploading a file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function uploadFile()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $upload = $this->input->files->get('files');
+ $location = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if ($return = $model->uploadFile($upload, $location)) {
+ $this->setMessage(Text::sprintf('COM_TEMPLATES_FILE_UPLOAD_SUCCESS', $upload['name']));
+ $redirect = base64_encode($return);
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $redirect . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_UPLOAD'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for creating a new folder.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function createFolder()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $name = $this->input->get('name');
+ $location = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9-_.]+$/', $name)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FOLDER_NAME'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->createFolder($name, $location)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_CREATE_SUCCESS'));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FOLDER_CREATE'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for deleting a folder.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function deleteFolder()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $isMedia = (int) $this->input->get('isMedia', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $location = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (empty($location)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_ROOT_DELETE'), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->deleteFolder($location)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_SUCCESS'));
+
+ if (stristr(base64_decode($file), $location) != false) {
+ $file = base64_encode('home');
+ }
+
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_ERROR'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for renaming a file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function renameFile()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+ $id = (int) $this->input->get('id', 0, 'int');
+ $isMedia = (int) $this->input->get('isMedia', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $newName = $this->input->get('new_name');
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (base64_decode(urldecode($file)) == '/index.php') {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_RENAME_INDEX'), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } elseif (base64_decode(urldecode($file)) == '/joomla.asset.json') {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_RENAME_ASSET_FILE'), 'warning');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } elseif (!preg_match('/^[a-zA-Z0-9-_]+$/', $newName)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($rename = $model->renameFile($file, $newName)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_RENAME_SUCCESS'));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $rename . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_FILE_RENAME'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $isMedia;
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for cropping an image.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function cropImage()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->get('file', '', 'cmd');
+ $x = $this->input->get('x');
+ $y = $this->input->get('y');
+ $w = $this->input->get('w');
+ $h = $this->input->get('h');
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (empty($w) && empty($h) && empty($x) && empty($y)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_CROP_AREA_ERROR'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->cropImage($file, $w, $h, $x, $y)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_CROP_SUCCESS'));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_CROP_ERROR'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for resizing an image.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function resizeImage()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $width = $this->input->get('width');
+ $height = $this->input->get('height');
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if ($model->resizeImage($file, $width, $height)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_RESIZE_SUCCESS'));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_RESIZE_ERROR'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for copying a file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function copyFile()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+ $newName = $this->input->get('new_name');
+ $location = (string) InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )
+ ->clean(base64_decode($this->input->getBase64('address', '')), 'path');
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9-_]+$/', $newName)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_INVALID_FILE_NAME'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } elseif ($model->copyFile($newName, $location, $file)) {
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_COPY_FAIL'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file . '&isMedia=' . $this->input->getInt('isMedia', 0);
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Method for extracting an archive file.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function extractArchive()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ $id = (int) $this->input->get('id', 0, 'int');
+ $file = (string) $this->input->getCmd('file', '');
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return;
+ }
+
+ if ($model->extractArchive($file)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_SUCCESS'));
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file;
+ $this->setRedirect(Route::_($url, false));
+ } else {
+ $this->setMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXTRACT_FAIL'), 'error');
+ $url = 'index.php?option=com_templates&view=template&id=' . $id . '&file=' . $file;
+ $this->setRedirect(Route::_($url, false));
+ }
+ }
+
+ /**
+ * Fetch and report updates in \JSON format, for AJAX requests
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function ajax()
+ {
+ $app = $this->app;
+
+ if (!Session::checkToken('get')) {
+ $app->setHeader('status', 403, true);
+ $app->sendHeaders();
+ echo Text::_('JINVALID_TOKEN_NOTICE');
+ $app->close();
+ }
+
+ // Checks status of installer override plugin.
+ if (!PluginHelper::isEnabled('installer', 'override')) {
+ $error = array('installerOverride' => 'disabled');
+
+ echo json_encode($error);
+
+ $app->close();
+ }
+
+ /** @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel();
+
+ $result = $model->getUpdatedList(true, true);
+
+ echo json_encode($result);
+
+ $app->close();
+ }
+
+
+ /**
+ * Method for creating a child template.
+ *
+ * @return boolean true on success, false otherwise
+ *
+ * @since 4.1.0
+ */
+ public function child()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ $this->app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
+
+ return false;
+ }
+
+ $this->input->set('installtype', 'folder');
+ $newNameRaw = $this->input->get('new_name', null, 'string');
+
+ // Only accept letters, numbers and underscore for template name
+ $newName = preg_replace('/[^a-zA-Z0-9_]/', '', $newNameRaw);
+ $templateID = (int) $this->input->getInt('id', 0);
+ $file = (string) $this->input->get('file', '', 'cmd');
+ $extraStyles = (array) $this->input->get('style_ids', [], 'array');
+
+ $this->setRedirect('index.php?option=com_templates&view=template&id=' . $templateID . '&file=' . $file);
+
+ /* @var \Joomla\Component\Templates\Administrator\Model\TemplateModel $model */
+ $model = $this->getModel('Template', 'Administrator');
+ $model->setState('new_name', $newName);
+ $model->setState('tmp_prefix', uniqid('template_child_'));
+ $model->setState('to_path', $this->app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
+
+ // Process only if we have a new name entered
+ if (!strlen($newName)) {
+ $this->setMessage(Text::sprintf('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
+
+ return false;
+ }
+
+ // Process only if user is allowed to create child template
+ if (!$this->app->getIdentity()->authorise('core.create', 'com_templates')) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_CREATE_NOT_PERMITTED'), 'error');
+
+ return false;
+ }
+
+ // Check that new name is valid
+ if (($newNameRaw !== null) && ($newName !== $newNameRaw)) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_TEMPLATE_NAME'), 'error');
+
+ return false;
+ }
- // Check that new name doesn't already exist
- if (!$model->checkNewName()) {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_DUPLICATE_TEMPLATE_NAME'), 'error');
+ // Check that new name doesn't already exist
+ if (!$model->checkNewName()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_DUPLICATE_TEMPLATE_NAME'), 'error');
- return false;
- }
+ return false;
+ }
- // Check that from name does exist and get the folder name
- $fromName = $model->getFromName();
+ // Check that from name does exist and get the folder name
+ $fromName = $model->getFromName();
- if (!$fromName)
- {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
+ if (!$fromName) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
- return false;
- }
+ return false;
+ }
- // Call model's copy method
- if (!$model->child()) {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_COPY'), 'error');
+ // Call model's copy method
+ if (!$model->child()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_COPY'), 'error');
- return false;
- }
+ return false;
+ }
- // Call installation model
- $this->input->set('install_directory', $this->app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
+ // Call installation model
+ $this->input->set('install_directory', $this->app->get('tmp_path') . '/' . $model->getState('tmp_prefix'));
- /** @var \Joomla\Component\Installer\Administrator\Model\InstallModel $installModel */
- $installModel = $this->app->bootComponent('com_installer')
- ->getMVCFactory()->createModel('Install', 'Administrator');
- $this->app->getLanguage()->load('com_installer');
+ /** @var \Joomla\Component\Installer\Administrator\Model\InstallModel $installModel */
+ $installModel = $this->app->bootComponent('com_installer')
+ ->getMVCFactory()->createModel('Install', 'Administrator');
+ $this->app->getLanguage()->load('com_installer');
- if (!$installModel->install()) {
- $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INSTALL'), 'error');
+ if (!$installModel->install()) {
+ $this->setMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_INSTALL'), 'error');
- return false;
- }
+ return false;
+ }
- $this->setMessage(Text::sprintf('COM_TEMPLATES_CHILD_SUCCESS', $newName));
- $model->cleanup();
+ $this->setMessage(Text::sprintf('COM_TEMPLATES_CHILD_SUCCESS', $newName));
+ $model->cleanup();
- if (\count($extraStyles) > 0)
- {
- $model->setState('stylesToCopy', $extraStyles);
- $model->copyStyles();
- }
+ if (\count($extraStyles) > 0) {
+ $model->setState('stylesToCopy', $extraStyles);
+ $model->copyStyles();
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/administrator/components/com_templates/src/Extension/TemplatesComponent.php b/administrator/components/com_templates/src/Extension/TemplatesComponent.php
index e0736517413f1..ee201bb096a4d 100644
--- a/administrator/components/com_templates/src/Extension/TemplatesComponent.php
+++ b/administrator/components/com_templates/src/Extension/TemplatesComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('templates', new Templates);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('templates', new Templates());
+ }
}
diff --git a/administrator/components/com_templates/src/Field/TemplatelocationField.php b/administrator/components/com_templates/src/Field/TemplatelocationField.php
index cf4793eb5506c..17b13d18ad3dc 100644
--- a/administrator/components/com_templates/src/Field/TemplatelocationField.php
+++ b/administrator/components/com_templates/src/Field/TemplatelocationField.php
@@ -1,4 +1,5 @@
getUserStateFromRequest('com_templates.styles.client_id', 'client_id', '0', 'string');
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.6
+ */
+ public function getOptions()
+ {
+ // Get the client_id filter from the user state.
+ $clientId = Factory::getApplication()->getUserStateFromRequest('com_templates.styles.client_id', 'client_id', '0', 'string');
- // Get the templates for the selected client_id.
- $options = TemplatesHelper::getTemplateOptions($clientId);
+ // Get the templates for the selected client_id.
+ $options = TemplatesHelper::getTemplateOptions($clientId);
- // Merge into the parent options.
- return array_merge(parent::getOptions(), $options);
- }
+ // Merge into the parent options.
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_templates/src/Helper/TemplateHelper.php b/administrator/components/com_templates/src/Helper/TemplateHelper.php
index dbbfd061ff811..2a666abd5ea62 100644
--- a/administrator/components/com_templates/src/Helper/TemplateHelper.php
+++ b/administrator/components/com_templates/src/Helper/TemplateHelper.php
@@ -1,4 +1,5 @@
enqueueMessage(Text::_('COM_TEMPLATES_ERROR_UPLOAD_INPUT'), 'error');
-
- return false;
- }
-
- // Media file names should never have executable extensions buried in them.
- $executable = array(
- 'exe', 'phtml','java', 'perl', 'py', 'asp','dll', 'go', 'jar',
- 'ade', 'adp', 'bat', 'chm', 'cmd', 'com', 'cpl', 'hta', 'ins', 'isp',
- 'jse', 'lib', 'mde', 'msc', 'msp', 'mst', 'pif', 'scr', 'sct', 'shb',
- 'sys', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh'
- );
- $explodedFileName = explode('.', $file['name']);
-
- if (count($explodedFileName) > 2)
- {
- foreach ($executable as $extensionName)
- {
- if (in_array($extensionName, $explodedFileName))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXECUTABLE'), 'error');
-
- return false;
- }
- }
- }
-
- if ($file['name'] !== File::makeSafe($file['name']) || preg_match('/\s/', File::makeSafe($file['name'])))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILENAME'), 'error');
-
- return false;
- }
-
- $format = strtolower(File::getExt($file['name']));
-
- $imageTypes = explode(',', $params->get('image_formats'));
- $sourceTypes = explode(',', $params->get('source_formats'));
- $fontTypes = explode(',', $params->get('font_formats'));
- $archiveTypes = explode(',', $params->get('compressed_formats'));
-
- $allowable = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
-
- if ($format == '' || $format == false || (!in_array($format, $allowable)))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETYPE'), 'error');
-
- return false;
- }
-
- if (in_array($format, $archiveTypes))
- {
- $zip = new \ZipArchive;
-
- if ($zip->open($file['tmp_name']) === true)
- {
- for ($i = 0; $i < $zip->numFiles; $i++)
- {
- $entry = $zip->getNameIndex($i);
- $endString = substr($entry, -1);
-
- if ($endString != DIRECTORY_SEPARATOR)
- {
- $explodeArray = explode('.', $entry);
- $ext = end($explodeArray);
-
- if (!in_array($ext, $allowable))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UNSUPPORTED_ARCHIVE'), 'error');
-
- return false;
- }
- }
- }
- }
- else
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
-
- return false;
- }
- }
-
- // Max upload size set to 2 MB for Template Manager
- $maxSize = (int) ($params->get('upload_limit') * 1024 * 1024);
-
- if ($maxSize > 0 && (int) $file['size'] > $maxSize)
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETOOLARGE'), 'error');
-
- return false;
- }
-
- $xss_check = file_get_contents($file['tmp_name'], false, null, -1, 256);
- $html_tags = array(
- 'abbr', 'acronym', 'address', 'applet', 'area', 'audioscope', 'base', 'basefont', 'bdo', 'bgsound', 'big', 'blackface', 'blink', 'blockquote',
- 'body', 'bq', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'comment', 'custom', 'dd', 'del', 'dfn', 'dir', 'div',
- 'dl', 'dt', 'em', 'embed', 'fieldset', 'fn', 'font', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html',
- 'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'layer', 'legend', 'li', 'limittext', 'link', 'listing',
- 'map', 'marquee', 'menu', 'meta', 'multicol', 'nobr', 'noembed', 'noframes', 'noscript', 'nosmartquotes', 'object', 'ol', 'optgroup', 'option',
- 'param', 'plaintext', 'pre', 'rt', 'ruby', 's', 'samp', 'script', 'select', 'server', 'shadow', 'sidebar', 'small', 'spacer', 'span', 'strike',
- 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 'var', 'wbr', 'xml',
- 'xmp', '!DOCTYPE', '!--'
- );
-
- foreach ($html_tags as $tag)
- {
- // A tag is ''
- if (stristr($xss_check, '<' . $tag . ' ') || stristr($xss_check, '<' . $tag . '>'))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNIEXSS'), 'error');
-
- return false;
- }
- }
-
- return true;
- }
+ /**
+ * Checks if the file is an image
+ *
+ * @param string $fileName The filename
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ public static function getTypeIcon($fileName)
+ {
+ // Get file extension
+ return strtolower(substr($fileName, strrpos($fileName, '.') + 1));
+ }
+
+ /**
+ * Checks if the file can be uploaded
+ *
+ * @param array $file File information
+ * @param string $err An error message to be returned
+ *
+ * @return boolean
+ *
+ * @since 3.2
+ */
+ public static function canUpload($file, $err = '')
+ {
+ $params = ComponentHelper::getParams('com_templates');
+
+ if (empty($file['name'])) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_UPLOAD_INPUT'), 'error');
+
+ return false;
+ }
+
+ // Media file names should never have executable extensions buried in them.
+ $executable = array(
+ 'exe', 'phtml','java', 'perl', 'py', 'asp','dll', 'go', 'jar',
+ 'ade', 'adp', 'bat', 'chm', 'cmd', 'com', 'cpl', 'hta', 'ins', 'isp',
+ 'jse', 'lib', 'mde', 'msc', 'msp', 'mst', 'pif', 'scr', 'sct', 'shb',
+ 'sys', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh'
+ );
+ $explodedFileName = explode('.', $file['name']);
+
+ if (count($explodedFileName) > 2) {
+ foreach ($executable as $extensionName) {
+ if (in_array($extensionName, $explodedFileName)) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXECUTABLE'), 'error');
+
+ return false;
+ }
+ }
+ }
+
+ if ($file['name'] !== File::makeSafe($file['name']) || preg_match('/\s/', File::makeSafe($file['name']))) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILENAME'), 'error');
+
+ return false;
+ }
+
+ $format = strtolower(File::getExt($file['name']));
+
+ $imageTypes = explode(',', $params->get('image_formats'));
+ $sourceTypes = explode(',', $params->get('source_formats'));
+ $fontTypes = explode(',', $params->get('font_formats'));
+ $archiveTypes = explode(',', $params->get('compressed_formats'));
+
+ $allowable = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
+
+ if ($format == '' || $format == false || (!in_array($format, $allowable))) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETYPE'), 'error');
+
+ return false;
+ }
+
+ if (in_array($format, $archiveTypes)) {
+ $zip = new \ZipArchive();
+
+ if ($zip->open($file['tmp_name']) === true) {
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $entry = $zip->getNameIndex($i);
+ $endString = substr($entry, -1);
+
+ if ($endString != DIRECTORY_SEPARATOR) {
+ $explodeArray = explode('.', $entry);
+ $ext = end($explodeArray);
+
+ if (!in_array($ext, $allowable)) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UNSUPPORTED_ARCHIVE'), 'error');
+
+ return false;
+ }
+ }
+ }
+ } else {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
+
+ return false;
+ }
+ }
+
+ // Max upload size set to 2 MB for Template Manager
+ $maxSize = (int) ($params->get('upload_limit') * 1024 * 1024);
+
+ if ($maxSize > 0 && (int) $file['size'] > $maxSize) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNFILETOOLARGE'), 'error');
+
+ return false;
+ }
+
+ $xss_check = file_get_contents($file['tmp_name'], false, null, -1, 256);
+ $html_tags = array(
+ 'abbr', 'acronym', 'address', 'applet', 'area', 'audioscope', 'base', 'basefont', 'bdo', 'bgsound', 'big', 'blackface', 'blink', 'blockquote',
+ 'body', 'bq', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'comment', 'custom', 'dd', 'del', 'dfn', 'dir', 'div',
+ 'dl', 'dt', 'em', 'embed', 'fieldset', 'fn', 'font', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'hr', 'html',
+ 'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'layer', 'legend', 'li', 'limittext', 'link', 'listing',
+ 'map', 'marquee', 'menu', 'meta', 'multicol', 'nobr', 'noembed', 'noframes', 'noscript', 'nosmartquotes', 'object', 'ol', 'optgroup', 'option',
+ 'param', 'plaintext', 'pre', 'rt', 'ruby', 's', 'samp', 'script', 'select', 'server', 'shadow', 'sidebar', 'small', 'spacer', 'span', 'strike',
+ 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'tt', 'ul', 'var', 'wbr', 'xml',
+ 'xmp', '!DOCTYPE', '!--'
+ );
+
+ foreach ($html_tags as $tag) {
+ // A tag is ''
+ if (stristr($xss_check, '<' . $tag . ' ') || stristr($xss_check, '<' . $tag . '>')) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_WARNIEXSS'), 'error');
+
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_templates/src/Helper/TemplatesHelper.php b/administrator/components/com_templates/src/Helper/TemplatesHelper.php
index 53a50bdb8d0a1..c5c059183c61b 100644
--- a/administrator/components/com_templates/src/Helper/TemplatesHelper.php
+++ b/administrator/components/com_templates/src/Helper/TemplatesHelper.php
@@ -1,4 +1,5 @@
getQuery(true);
-
- $query->select($db->quoteName('element', 'value'))
- ->select($db->quoteName('name', 'text'))
- ->select($db->quoteName('extension_id', 'e_id'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('type') . ' = ' . $db->quote('template'))
- ->where($db->quoteName('enabled') . ' = 1')
- ->order($db->quoteName('client_id') . ' ASC')
- ->order($db->quoteName('name') . ' ASC');
-
- if ($clientId != '*')
- {
- $clientId = (int) $clientId;
- $query->where($db->quoteName('client_id') . ' = :clientid')
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
- }
-
- $db->setQuery($query);
- $options = $db->loadObjectList();
-
- return $options;
- }
-
- /**
- * @param string $templateBaseDir
- * @param string $templateDir
- *
- * @return boolean|CMSObject
- */
- public static function parseXMLTemplateFile($templateBaseDir, $templateDir)
- {
- $data = new CMSObject;
-
- // Check of the xml file exists
- $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
-
- if (is_file($filePath))
- {
- $xml = Installer::parseXMLInstallFile($filePath);
-
- if ($xml['type'] != 'template')
- {
- return false;
- }
-
- foreach ($xml as $key => $value)
- {
- $data->set($key, $value);
- }
- }
-
- return $data;
- }
-
- /**
- * @param integer $clientId
- * @param string $templateDir
- *
- * @return boolean|array
- *
- * @since 3.0
- */
- public static function getPositions($clientId, $templateDir)
- {
- $positions = array();
-
- $templateBaseDir = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
- $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
-
- if (is_file($filePath))
- {
- // Read the file to see if it's a valid component XML file
- $xml = simplexml_load_file($filePath);
-
- if (!$xml)
- {
- return false;
- }
-
- // Check for a valid XML root tag.
-
- // Extensions use 'extension' as the root tag. Languages use 'metafile' instead
-
- if ($xml->getName() != 'extension' && $xml->getName() != 'metafile')
- {
- unset($xml);
-
- return false;
- }
-
- $positions = (array) $xml->positions;
-
- if (isset($positions['position']))
- {
- $positions = (array) $positions['position'];
- }
- else
- {
- $positions = array();
- }
- }
-
- return $positions;
- }
+ /**
+ * Get a list of filter options for the application clients.
+ *
+ * @return array An array of HtmlOption elements.
+ */
+ public static function getClientOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '0', Text::_('JSITE'));
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('JADMINISTRATOR'));
+
+ return $options;
+ }
+
+ /**
+ * Get a list of filter options for the templates with styles.
+ *
+ * @param mixed $clientId The CMS client id (0:site | 1:administrator) or '*' for all.
+ *
+ * @return array
+ */
+ public static function getTemplateOptions($clientId = '*')
+ {
+ // Build the filter options.
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('element', 'value'))
+ ->select($db->quoteName('name', 'text'))
+ ->select($db->quoteName('extension_id', 'e_id'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('type') . ' = ' . $db->quote('template'))
+ ->where($db->quoteName('enabled') . ' = 1')
+ ->order($db->quoteName('client_id') . ' ASC')
+ ->order($db->quoteName('name') . ' ASC');
+
+ if ($clientId != '*') {
+ $clientId = (int) $clientId;
+ $query->where($db->quoteName('client_id') . ' = :clientid')
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+ }
+
+ $db->setQuery($query);
+ $options = $db->loadObjectList();
+
+ return $options;
+ }
+
+ /**
+ * @param string $templateBaseDir
+ * @param string $templateDir
+ *
+ * @return boolean|CMSObject
+ */
+ public static function parseXMLTemplateFile($templateBaseDir, $templateDir)
+ {
+ $data = new CMSObject();
+
+ // Check of the xml file exists
+ $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
+
+ if (is_file($filePath)) {
+ $xml = Installer::parseXMLInstallFile($filePath);
+
+ if ($xml['type'] != 'template') {
+ return false;
+ }
+
+ foreach ($xml as $key => $value) {
+ $data->set($key, $value);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param integer $clientId
+ * @param string $templateDir
+ *
+ * @return boolean|array
+ *
+ * @since 3.0
+ */
+ public static function getPositions($clientId, $templateDir)
+ {
+ $positions = array();
+
+ $templateBaseDir = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
+ $filePath = Path::clean($templateBaseDir . '/templates/' . $templateDir . '/templateDetails.xml');
+
+ if (is_file($filePath)) {
+ // Read the file to see if it's a valid component XML file
+ $xml = simplexml_load_file($filePath);
+
+ if (!$xml) {
+ return false;
+ }
+
+ // Check for a valid XML root tag.
+
+ // Extensions use 'extension' as the root tag. Languages use 'metafile' instead
+
+ if ($xml->getName() != 'extension' && $xml->getName() != 'metafile') {
+ unset($xml);
+
+ return false;
+ }
+
+ $positions = (array) $xml->positions;
+
+ if (isset($positions['position'])) {
+ $positions = (array) $positions['position'];
+ } else {
+ $positions = array();
+ }
+ }
+
+ return $positions;
+ }
}
diff --git a/administrator/components/com_templates/src/Model/StyleModel.php b/administrator/components/com_templates/src/Model/StyleModel.php
index 427101683fa8a..605388050f2d2 100644
--- a/administrator/components/com_templates/src/Model/StyleModel.php
+++ b/administrator/components/com_templates/src/Model/StyleModel.php
@@ -1,4 +1,5 @@
'onExtensionBeforeDelete',
- 'event_after_delete' => 'onExtensionAfterDelete',
- 'event_before_save' => 'onExtensionBeforeSave',
- 'event_after_save' => 'onExtensionAfterSave',
- 'events_map' => array('delete' => 'extension', 'save' => 'extension')
- ), $config
- );
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * @note Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState('style.id', $pk);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_templates');
- $this->setState('params', $params);
- }
-
- /**
- * Method to delete rows.
- *
- * @param array &$pks An array of item ids.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function delete(&$pks)
- {
- $pks = (array) $pks;
- $user = Factory::getUser();
- $table = $this->getTable();
- $context = $this->option . '.' . $this->name;
-
- PluginHelper::importPlugin($this->events_map['delete']);
-
- // Iterate the items to delete each one.
- foreach ($pks as $pk)
- {
- if ($table->load($pk))
- {
- // Access checks.
- if (!$user->authorise('core.delete', 'com_templates'))
- {
- throw new \Exception(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'));
- }
-
- // You should not delete a default style
- if ($table->home != '0')
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_TEMPLATES_STYLE_CANNOT_DELETE_DEFAULT_STYLE'), 'error');
-
- return false;
- }
-
- // Trigger the before delete event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_delete, array($context, $table));
-
- if (in_array(false, $result, true) || !$table->delete($pk))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the after delete event.
- Factory::getApplication()->triggerEvent($this->event_after_delete, array($context, $table));
- }
- else
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Clean cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to duplicate styles.
- *
- * @param array &$pks An array of primary key IDs.
- *
- * @return boolean True if successful.
- *
- * @throws \Exception
- */
- public function duplicate(&$pks)
- {
- $user = Factory::getUser();
-
- // Access checks.
- if (!$user->authorise('core.create', 'com_templates'))
- {
- throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
- }
-
- $context = $this->option . '.' . $this->name;
-
- // Include the plugins for the save events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- $table = $this->getTable();
-
- foreach ($pks as $pk)
- {
- if ($table->load($pk, true))
- {
- // Reset the id to create a new record.
- $table->id = 0;
-
- // Reset the home (don't want dupes of that field).
- $table->home = 0;
-
- // Alter the title.
- $m = null;
- $table->title = $this->generateNewTitle(null, null, $table->title);
-
- if (!$table->check())
- {
- throw new \Exception($table->getError());
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, true));
-
- if (in_array(false, $result, true) || !$table->store())
- {
- throw new \Exception($table->getError());
- }
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, true));
- }
- else
- {
- throw new \Exception($table->getError());
- }
- }
-
- // Clean cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the title.
- *
- * @param integer $categoryId The id of the category.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return string New title.
- *
- * @since 1.7.1
- */
- protected function generateNewTitle($categoryId, $alias, $title)
- {
- // Alter the title
- $table = $this->getTable();
-
- while ($table->load(array('title' => $title)))
- {
- $title = StringHelper::increment($title);
- }
-
- return $title;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data An optional array of data for the form to interrogate.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // The folder and element vars are passed when saving the form.
- if (empty($data))
- {
- $item = $this->getItem();
- $clientId = $item->client_id;
- $template = $item->template;
- }
- else
- {
- $clientId = ArrayHelper::getValue($data, 'client_id');
- $template = ArrayHelper::getValue($data, 'template');
- }
-
- // Add the default fields directory
- $baseFolder = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
- Form::addFieldPath($baseFolder . '/templates/' . $template . '/field');
-
- // These variables are used to add data from the plugin XML files.
- $this->setState('item.client_id', $clientId);
- $this->setState('item.template', $template);
-
- // Get the form.
- $form = $this->loadForm('com_templates.style', 'style', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- // Modify the form based on access controls.
- if (!$this->canEditState((object) $data))
- {
- // Disable fields for display.
- $form->setFieldAttribute('home', 'disabled', 'true');
-
- // Disable fields while saving.
- // The controller has already verified this is a record you can edit.
- $form->setFieldAttribute('home', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_templates.edit.style.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_templates.style', $data);
-
- return $data;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- */
- public function getItem($pk = null)
- {
- $pk = (!empty($pk)) ? $pk : (int) $this->getState('style.id');
-
- if (!isset($this->_cache[$pk]))
- {
- // Get a row instance.
- $table = $this->getTable();
-
- // Attempt to load the row.
- $return = $table->load($pk);
-
- // Check for a table object error.
- if ($return === false && $table->getError())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
- $properties = $table->getProperties(1);
- $this->_cache[$pk] = ArrayHelper::toObject($properties, CMSObject::class);
-
- // Convert the params field to an array.
- $registry = new Registry($table->params);
- $this->_cache[$pk]->params = $registry->toArray();
-
- // Get the template XML.
- $client = ApplicationHelper::getClientInfo($table->client_id);
- $path = Path::clean($client->path . '/templates/' . $table->template . '/templateDetails.xml');
-
- if (file_exists($path))
- {
- $this->_cache[$pk]->xml = simplexml_load_file($path);
- }
- else
- {
- $this->_cache[$pk]->xml = null;
- }
- }
-
- return $this->_cache[$pk];
- }
-
- /**
- * Method to allow derived classes to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $clientId = $this->getState('item.client_id');
- $template = $this->getState('item.template');
- $lang = Factory::getLanguage();
- $client = ApplicationHelper::getClientInfo($clientId);
-
- if (!$form->loadFile('style_' . $client->name, true))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- $formFile = Path::clean($client->path . '/templates/' . $template . '/templateDetails.xml');
-
- // Load the core and/or local language file(s).
- $lang->load('tpl_' . $template, $client->path)
- || (!empty($data->parent) && $lang->load('tpl_' . $data->parent, $client->path))
- || (!empty($data->parent) && $lang->load('tpl_' . $data->parent, $client->path . '/templates/' . $data->parent))
- || $lang->load('tpl_' . $template, $client->path . '/templates/' . $template);
-
- if (file_exists($formFile))
- {
- // Get the template form.
- if (!$form->loadFile($formFile, false, '//config'))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
-
- // Disable home field if it is default style
-
- if ((is_array($data) && array_key_exists('home', $data) && $data['home'] == '1')
- || (is_object($data) && isset($data->home) && $data->home == '1'))
- {
- $form->setFieldAttribute('home', 'readonly', 'true');
- }
-
- if ($client->name === 'site' && !Multilanguage::isEnabled())
- {
- $form->setFieldAttribute('home', 'type', 'radio');
- $form->setFieldAttribute('home', 'layout', 'joomla.form.field.radio.switcher');
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Get the help data from the XML file if present.
- $help = $xml->xpath('/extension/help');
-
- if (!empty($help))
- {
- $helpKey = trim((string) $help[0]['key']);
- $helpURL = trim((string) $help[0]['url']);
-
- $this->helpKey = $helpKey ?: $this->helpKey;
- $this->helpURL = $helpURL ?: $this->helpURL;
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- */
- public function save($data)
- {
- // Detect disabled extension
- $extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
-
- if ($extension->load(array('enabled' => 0, 'type' => 'template', 'element' => $data['template'], 'client_id' => $data['client_id'])))
- {
- $this->setError(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
-
- return false;
- }
-
- $app = Factory::getApplication();
- $table = $this->getTable();
- $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('style.id');
- $isNew = true;
-
- // Include the extension plugins for the save events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Load the row if saving an existing record.
- if ($pk > 0)
- {
- $table->load($pk);
- $isNew = false;
- }
-
- if ($app->input->get('task') == 'save2copy')
- {
- $data['title'] = $this->generateNewTitle(null, null, $data['title']);
- $data['home'] = 0;
- $data['assigned'] = '';
- }
-
- // Bind the data.
- if (!$table->bind($data))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Prepare the row for saving
- $this->prepareTable($table);
-
- // Check the data.
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array('com_templates.style', &$table, $isNew));
-
- // Store the data.
- if (in_array(false, $result, true) || !$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- $user = Factory::getUser();
-
- if ($user->authorise('core.edit', 'com_menus') && $table->client_id == 0)
- {
- $n = 0;
- $db = $this->getDatabase();
- $user = Factory::getUser();
- $tableId = (int) $table->id;
- $userId = (int) $user->id;
-
- if (!empty($data['assigned']) && is_array($data['assigned']))
- {
- $data['assigned'] = ArrayHelper::toInteger($data['assigned']);
-
- // Update the mapping for menu items that this style IS assigned to.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('template_style_id') . ' = :newtsid')
- ->whereIn($db->quoteName('id'), $data['assigned'])
- ->where($db->quoteName('template_style_id') . ' != :tsid')
- ->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
- ->bind(':userid', $userId, ParameterType::INTEGER)
- ->bind(':newtsid', $tableId, ParameterType::INTEGER)
- ->bind(':tsid', $tableId, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
- $n += $db->getAffectedRows();
- }
-
- // Remove style mappings for menu items this style is NOT assigned to.
- // If unassigned then all existing maps will be removed.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__menu'))
- ->set($db->quoteName('template_style_id') . ' = 0');
-
- if (!empty($data['assigned']))
- {
- $query->whereNotIn($db->quoteName('id'), $data['assigned']);
- }
-
- $query->where($db->quoteName('template_style_id') . ' = :templatestyleid')
- ->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
- ->bind(':userid', $userId, ParameterType::INTEGER)
- ->bind(':templatestyleid', $tableId, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- $n += $db->getAffectedRows();
-
- if ($n > 0)
- {
- $app->enqueueMessage(Text::plural('COM_TEMPLATES_MENU_CHANGED', $n));
- }
- }
-
- // Clean the cache.
- $this->cleanCache();
-
- // Trigger the after save event.
- Factory::getApplication()->triggerEvent($this->event_after_save, array('com_templates.style', &$table, $isNew));
-
- $this->setState('style.id', $table->id);
-
- return true;
- }
-
- /**
- * Method to set a template style as home.
- *
- * @param integer $id The primary key ID for the style.
- *
- * @return boolean True if successful.
- *
- * @throws \Exception
- */
- public function setHome($id = 0)
- {
- $user = Factory::getUser();
- $db = $this->getDatabase();
-
- // Access checks.
- if (!$user->authorise('core.edit.state', 'com_templates'))
- {
- throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
- }
-
- $style = $this->getTable();
-
- if (!$style->load((int) $id))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
- }
-
- // Detect disabled extension
- $extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
-
- if ($extension->load(array('enabled' => 0, 'type' => 'template', 'element' => $style->template, 'client_id' => $style->client_id)))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
- }
-
- $clientId = (int) $style->client_id;
- $id = (int) $id;
-
- // Reset the home fields for the client_id.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__template_styles'))
- ->set($db->quoteName('home') . ' = ' . $db->quote('0'))
- ->where($db->quoteName('client_id') . ' = :clientid')
- ->where($db->quoteName('home') . ' = ' . $db->quote('1'))
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- // Set the new home style.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__template_styles'))
- ->set($db->quoteName('home') . ' = ' . $db->quote('1'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- // Clean the cache.
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to unset a template style as default for a language.
- *
- * @param integer $id The primary key ID for the style.
- *
- * @return boolean True if successful.
- *
- * @throws \Exception
- */
- public function unsetHome($id = 0)
- {
- $user = Factory::getUser();
- $db = $this->getDatabase();
-
- // Access checks.
- if (!$user->authorise('core.edit.state', 'com_templates'))
- {
- throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
- }
-
- $id = (int) $id;
-
- // Lookup the client_id.
- $query = $db->getQuery(true)
- ->select($db->quoteName(['client_id', 'home']))
- ->from($db->quoteName('#__template_styles'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $style = $db->loadObject();
-
- if (!is_numeric($style->client_id))
- {
- throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
- }
- elseif ($style->home == '1')
- {
- throw new \Exception(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
- }
-
- // Set the new home style.
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__template_styles'))
- ->set($db->quoteName('home') . ' = ' . $db->quote('0'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query);
- $db->execute();
-
- // Clean the cache.
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Get the necessary data to load an item help screen.
- *
- * @return object An object with key, url, and local properties for loading the item help screen.
- *
- * @since 1.6
- */
- public function getHelp()
- {
- return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
- }
-
- /**
- * Returns the back end template for the given style.
- *
- * @param int $styleId The style id
- *
- * @return stdClass
- *
- * @since 4.2.0
- */
- public function getAdminTemplate(int $styleId): stdClass
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['s.template', 's.params', 's.inheritable', 's.parent']))
- ->from($db->quoteName('#__template_styles', 's'))
- ->join(
- 'LEFT',
- $db->quoteName('#__extensions', 'e'),
- $db->quoteName('e.type') . ' = ' . $db->quote('template')
- . ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
- . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
- )
- ->where(
- [
- $db->quoteName('s.client_id') . ' = 1',
- $db->quoteName('s.home') . ' = ' . $db->quote('1'),
- ]
- );
-
- if ($styleId)
- {
- $query->extendWhere(
- 'OR',
- [
- $db->quoteName('s.client_id') . ' = 1',
- $db->quoteName('s.id') . ' = :style',
- $db->quoteName('e.enabled') . ' = 1',
- ]
- )
- ->bind(':style', $styleId, ParameterType::INTEGER);
- }
-
- $query->order($db->quoteName('s.home'));
- $db->setQuery($query);
-
- return $db->loadObject();
- }
-
- /**
- * Returns the front end templates.
- *
- * @return array
- *
- * @since 4.2.0
- */
- public function getSiteTemplates(): array
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['id', 'home', 'template', 's.params', 'inheritable', 'parent']))
- ->from($db->quoteName('#__template_styles', 's'))
- ->where(
- [
- $db->quoteName('s.client_id') . ' = 0',
- $db->quoteName('e.enabled') . ' = 1',
- ]
- )
- ->join(
- 'LEFT',
- $db->quoteName('#__extensions', 'e'),
- $db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
- . ' AND ' . $db->quoteName('e.type') . ' = ' . $db->quote('template')
- . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
- );
-
- $db->setQuery($query);
-
- return $db->loadObjectList('id');
- }
-
- /**
- * Custom clean cache method
- *
- * @param string $group The cache group
- * @param integer $clientId @deprecated 5.0 No longer used.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function cleanCache($group = null, $clientId = 0)
- {
- parent::cleanCache('com_templates');
- parent::cleanCache('_system');
- }
+ /**
+ * The help screen key for the module.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $helpKey = 'Templates:_Edit_Style';
+
+ /**
+ * The help screen base URL for the module.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $helpURL;
+
+ /**
+ * Item cache.
+ *
+ * @var array
+ * @since 1.6
+ */
+ private $_cache = array();
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ $config = array_merge(
+ array(
+ 'event_before_delete' => 'onExtensionBeforeDelete',
+ 'event_after_delete' => 'onExtensionAfterDelete',
+ 'event_before_save' => 'onExtensionBeforeSave',
+ 'event_after_save' => 'onExtensionAfterSave',
+ 'events_map' => array('delete' => 'extension', 'save' => 'extension')
+ ),
+ $config
+ );
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * @note Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState('style.id', $pk);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_templates');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Method to delete rows.
+ *
+ * @param array &$pks An array of item ids.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function delete(&$pks)
+ {
+ $pks = (array) $pks;
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $context = $this->option . '.' . $this->name;
+
+ PluginHelper::importPlugin($this->events_map['delete']);
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $pk) {
+ if ($table->load($pk)) {
+ // Access checks.
+ if (!$user->authorise('core.delete', 'com_templates')) {
+ throw new \Exception(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'));
+ }
+
+ // You should not delete a default style
+ if ($table->home != '0') {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_TEMPLATES_STYLE_CANNOT_DELETE_DEFAULT_STYLE'), 'error');
+
+ return false;
+ }
+
+ // Trigger the before delete event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_delete, array($context, $table));
+
+ if (in_array(false, $result, true) || !$table->delete($pk)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the after delete event.
+ Factory::getApplication()->triggerEvent($this->event_after_delete, array($context, $table));
+ } else {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Clean cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to duplicate styles.
+ *
+ * @param array &$pks An array of primary key IDs.
+ *
+ * @return boolean True if successful.
+ *
+ * @throws \Exception
+ */
+ public function duplicate(&$pks)
+ {
+ $user = Factory::getUser();
+
+ // Access checks.
+ if (!$user->authorise('core.create', 'com_templates')) {
+ throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
+ }
+
+ $context = $this->option . '.' . $this->name;
+
+ // Include the plugins for the save events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ $table = $this->getTable();
+
+ foreach ($pks as $pk) {
+ if ($table->load($pk, true)) {
+ // Reset the id to create a new record.
+ $table->id = 0;
+
+ // Reset the home (don't want dupes of that field).
+ $table->home = 0;
+
+ // Alter the title.
+ $m = null;
+ $table->title = $this->generateNewTitle(null, null, $table->title);
+
+ if (!$table->check()) {
+ throw new \Exception($table->getError());
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($context, &$table, true));
+
+ if (in_array(false, $result, true) || !$table->store()) {
+ throw new \Exception($table->getError());
+ }
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array($context, &$table, true));
+ } else {
+ throw new \Exception($table->getError());
+ }
+ }
+
+ // Clean cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the title.
+ *
+ * @param integer $categoryId The id of the category.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return string New title.
+ *
+ * @since 1.7.1
+ */
+ protected function generateNewTitle($categoryId, $alias, $title)
+ {
+ // Alter the title
+ $table = $this->getTable();
+
+ while ($table->load(array('title' => $title))) {
+ $title = StringHelper::increment($title);
+ }
+
+ return $title;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // The folder and element vars are passed when saving the form.
+ if (empty($data)) {
+ $item = $this->getItem();
+ $clientId = $item->client_id;
+ $template = $item->template;
+ } else {
+ $clientId = ArrayHelper::getValue($data, 'client_id');
+ $template = ArrayHelper::getValue($data, 'template');
+ }
+
+ // Add the default fields directory
+ $baseFolder = $clientId ? JPATH_ADMINISTRATOR : JPATH_SITE;
+ Form::addFieldPath($baseFolder . '/templates/' . $template . '/field');
+
+ // These variables are used to add data from the plugin XML files.
+ $this->setState('item.client_id', $clientId);
+ $this->setState('item.template', $template);
+
+ // Get the form.
+ $form = $this->loadForm('com_templates.style', 'style', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ // Modify the form based on access controls.
+ if (!$this->canEditState((object) $data)) {
+ // Disable fields for display.
+ $form->setFieldAttribute('home', 'disabled', 'true');
+
+ // Disable fields while saving.
+ // The controller has already verified this is a record you can edit.
+ $form->setFieldAttribute('home', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_templates.edit.style.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_templates.style', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ */
+ public function getItem($pk = null)
+ {
+ $pk = (!empty($pk)) ? $pk : (int) $this->getState('style.id');
+
+ if (!isset($this->_cache[$pk])) {
+ // Get a row instance.
+ $table = $this->getTable();
+
+ // Attempt to load the row.
+ $return = $table->load($pk);
+
+ // Check for a table object error.
+ if ($return === false && $table->getError()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Convert to the \Joomla\CMS\Object\CMSObject before adding other data.
+ $properties = $table->getProperties(1);
+ $this->_cache[$pk] = ArrayHelper::toObject($properties, CMSObject::class);
+
+ // Convert the params field to an array.
+ $registry = new Registry($table->params);
+ $this->_cache[$pk]->params = $registry->toArray();
+
+ // Get the template XML.
+ $client = ApplicationHelper::getClientInfo($table->client_id);
+ $path = Path::clean($client->path . '/templates/' . $table->template . '/templateDetails.xml');
+
+ if (file_exists($path)) {
+ $this->_cache[$pk]->xml = simplexml_load_file($path);
+ } else {
+ $this->_cache[$pk]->xml = null;
+ }
+ }
+
+ return $this->_cache[$pk];
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $clientId = $this->getState('item.client_id');
+ $template = $this->getState('item.template');
+ $lang = Factory::getLanguage();
+ $client = ApplicationHelper::getClientInfo($clientId);
+
+ if (!$form->loadFile('style_' . $client->name, true)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ $formFile = Path::clean($client->path . '/templates/' . $template . '/templateDetails.xml');
+
+ // Load the core and/or local language file(s).
+ $lang->load('tpl_' . $template, $client->path)
+ || (!empty($data->parent) && $lang->load('tpl_' . $data->parent, $client->path))
+ || (!empty($data->parent) && $lang->load('tpl_' . $data->parent, $client->path . '/templates/' . $data->parent))
+ || $lang->load('tpl_' . $template, $client->path . '/templates/' . $template);
+
+ if (file_exists($formFile)) {
+ // Get the template form.
+ if (!$form->loadFile($formFile, false, '//config')) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+
+ // Disable home field if it is default style
+
+ if (
+ (is_array($data) && array_key_exists('home', $data) && $data['home'] == '1')
+ || (is_object($data) && isset($data->home) && $data->home == '1')
+ ) {
+ $form->setFieldAttribute('home', 'readonly', 'true');
+ }
+
+ if ($client->name === 'site' && !Multilanguage::isEnabled()) {
+ $form->setFieldAttribute('home', 'type', 'radio');
+ $form->setFieldAttribute('home', 'layout', 'joomla.form.field.radio.switcher');
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Get the help data from the XML file if present.
+ $help = $xml->xpath('/extension/help');
+
+ if (!empty($help)) {
+ $helpKey = trim((string) $help[0]['key']);
+ $helpURL = trim((string) $help[0]['url']);
+
+ $this->helpKey = $helpKey ?: $this->helpKey;
+ $this->helpURL = $helpURL ?: $this->helpURL;
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ */
+ public function save($data)
+ {
+ // Detect disabled extension
+ $extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
+
+ if ($extension->load(array('enabled' => 0, 'type' => 'template', 'element' => $data['template'], 'client_id' => $data['client_id']))) {
+ $this->setError(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
+
+ return false;
+ }
+
+ $app = Factory::getApplication();
+ $table = $this->getTable();
+ $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('style.id');
+ $isNew = true;
+
+ // Include the extension plugins for the save events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Load the row if saving an existing record.
+ if ($pk > 0) {
+ $table->load($pk);
+ $isNew = false;
+ }
+
+ if ($app->input->get('task') == 'save2copy') {
+ $data['title'] = $this->generateNewTitle(null, null, $data['title']);
+ $data['home'] = 0;
+ $data['assigned'] = '';
+ }
+
+ // Bind the data.
+ if (!$table->bind($data)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Prepare the row for saving
+ $this->prepareTable($table);
+
+ // Check the data.
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array('com_templates.style', &$table, $isNew));
+
+ // Store the data.
+ if (in_array(false, $result, true) || !$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ if ($user->authorise('core.edit', 'com_menus') && $table->client_id == 0) {
+ $n = 0;
+ $db = $this->getDatabase();
+ $user = Factory::getUser();
+ $tableId = (int) $table->id;
+ $userId = (int) $user->id;
+
+ if (!empty($data['assigned']) && is_array($data['assigned'])) {
+ $data['assigned'] = ArrayHelper::toInteger($data['assigned']);
+
+ // Update the mapping for menu items that this style IS assigned to.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('template_style_id') . ' = :newtsid')
+ ->whereIn($db->quoteName('id'), $data['assigned'])
+ ->where($db->quoteName('template_style_id') . ' != :tsid')
+ ->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
+ ->bind(':userid', $userId, ParameterType::INTEGER)
+ ->bind(':newtsid', $tableId, ParameterType::INTEGER)
+ ->bind(':tsid', $tableId, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+ $n += $db->getAffectedRows();
+ }
+
+ // Remove style mappings for menu items this style is NOT assigned to.
+ // If unassigned then all existing maps will be removed.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__menu'))
+ ->set($db->quoteName('template_style_id') . ' = 0');
+
+ if (!empty($data['assigned'])) {
+ $query->whereNotIn($db->quoteName('id'), $data['assigned']);
+ }
+
+ $query->where($db->quoteName('template_style_id') . ' = :templatestyleid')
+ ->where('(' . $db->quoteName('checked_out') . ' IS NULL OR ' . $db->quoteName('checked_out') . ' = :userid)')
+ ->bind(':userid', $userId, ParameterType::INTEGER)
+ ->bind(':templatestyleid', $tableId, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ $n += $db->getAffectedRows();
+
+ if ($n > 0) {
+ $app->enqueueMessage(Text::plural('COM_TEMPLATES_MENU_CHANGED', $n));
+ }
+ }
+
+ // Clean the cache.
+ $this->cleanCache();
+
+ // Trigger the after save event.
+ Factory::getApplication()->triggerEvent($this->event_after_save, array('com_templates.style', &$table, $isNew));
+
+ $this->setState('style.id', $table->id);
+
+ return true;
+ }
+
+ /**
+ * Method to set a template style as home.
+ *
+ * @param integer $id The primary key ID for the style.
+ *
+ * @return boolean True if successful.
+ *
+ * @throws \Exception
+ */
+ public function setHome($id = 0)
+ {
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+
+ // Access checks.
+ if (!$user->authorise('core.edit.state', 'com_templates')) {
+ throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
+ }
+
+ $style = $this->getTable();
+
+ if (!$style->load((int) $id)) {
+ throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
+ }
+
+ // Detect disabled extension
+ $extension = Table::getInstance('Extension', 'Joomla\\CMS\\Table\\');
+
+ if ($extension->load(array('enabled' => 0, 'type' => 'template', 'element' => $style->template, 'client_id' => $style->client_id))) {
+ throw new \Exception(Text::_('COM_TEMPLATES_ERROR_SAVE_DISABLED_TEMPLATE'));
+ }
+
+ $clientId = (int) $style->client_id;
+ $id = (int) $id;
+
+ // Reset the home fields for the client_id.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__template_styles'))
+ ->set($db->quoteName('home') . ' = ' . $db->quote('0'))
+ ->where($db->quoteName('client_id') . ' = :clientid')
+ ->where($db->quoteName('home') . ' = ' . $db->quote('1'))
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Set the new home style.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__template_styles'))
+ ->set($db->quoteName('home') . ' = ' . $db->quote('1'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Clean the cache.
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to unset a template style as default for a language.
+ *
+ * @param integer $id The primary key ID for the style.
+ *
+ * @return boolean True if successful.
+ *
+ * @throws \Exception
+ */
+ public function unsetHome($id = 0)
+ {
+ $user = Factory::getUser();
+ $db = $this->getDatabase();
+
+ // Access checks.
+ if (!$user->authorise('core.edit.state', 'com_templates')) {
+ throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
+ }
+
+ $id = (int) $id;
+
+ // Lookup the client_id.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['client_id', 'home']))
+ ->from($db->quoteName('#__template_styles'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $style = $db->loadObject();
+
+ if (!is_numeric($style->client_id)) {
+ throw new \Exception(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'));
+ } elseif ($style->home == '1') {
+ throw new \Exception(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
+ }
+
+ // Set the new home style.
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__template_styles'))
+ ->set($db->quoteName('home') . ' = ' . $db->quote('0'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $db->execute();
+
+ // Clean the cache.
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Get the necessary data to load an item help screen.
+ *
+ * @return object An object with key, url, and local properties for loading the item help screen.
+ *
+ * @since 1.6
+ */
+ public function getHelp()
+ {
+ return (object) array('key' => $this->helpKey, 'url' => $this->helpURL);
+ }
+
+ /**
+ * Returns the back end template for the given style.
+ *
+ * @param int $styleId The style id
+ *
+ * @return stdClass
+ *
+ * @since 4.2.0
+ */
+ public function getAdminTemplate(int $styleId): stdClass
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['s.template', 's.params', 's.inheritable', 's.parent']))
+ ->from($db->quoteName('#__template_styles', 's'))
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__extensions', 'e'),
+ $db->quoteName('e.type') . ' = ' . $db->quote('template')
+ . ' AND ' . $db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
+ . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
+ )
+ ->where(
+ [
+ $db->quoteName('s.client_id') . ' = 1',
+ $db->quoteName('s.home') . ' = ' . $db->quote('1'),
+ ]
+ );
+
+ if ($styleId) {
+ $query->extendWhere(
+ 'OR',
+ [
+ $db->quoteName('s.client_id') . ' = 1',
+ $db->quoteName('s.id') . ' = :style',
+ $db->quoteName('e.enabled') . ' = 1',
+ ]
+ )
+ ->bind(':style', $styleId, ParameterType::INTEGER);
+ }
+
+ $query->order($db->quoteName('s.home'));
+ $db->setQuery($query);
+
+ return $db->loadObject();
+ }
+
+ /**
+ * Returns the front end templates.
+ *
+ * @return array
+ *
+ * @since 4.2.0
+ */
+ public function getSiteTemplates(): array
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['id', 'home', 'template', 's.params', 'inheritable', 'parent']))
+ ->from($db->quoteName('#__template_styles', 's'))
+ ->where(
+ [
+ $db->quoteName('s.client_id') . ' = 0',
+ $db->quoteName('e.enabled') . ' = 1',
+ ]
+ )
+ ->join(
+ 'LEFT',
+ $db->quoteName('#__extensions', 'e'),
+ $db->quoteName('e.element') . ' = ' . $db->quoteName('s.template')
+ . ' AND ' . $db->quoteName('e.type') . ' = ' . $db->quote('template')
+ . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('s.client_id')
+ );
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList('id');
+ }
+
+ /**
+ * Custom clean cache method
+ *
+ * @param string $group The cache group
+ * @param integer $clientId @deprecated 5.0 No longer used.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function cleanCache($group = null, $clientId = 0)
+ {
+ parent::cleanCache('com_templates');
+ parent::cleanCache('_system');
+ }
}
diff --git a/administrator/components/com_templates/src/Model/StylesModel.php b/administrator/components/com_templates/src/Model/StylesModel.php
index c635eaabb4614..96d8345970bda 100644
--- a/administrator/components/com_templates/src/Model/StylesModel.php
+++ b/administrator/components/com_templates/src/Model/StylesModel.php
@@ -1,4 +1,5 @@
isClient('api'))
- {
- // Load the filter state.
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
- $this->setState('filter.template', $this->getUserStateFromRequest($this->context . '.filter.template', 'filter_template', '', 'string'));
- $this->setState('filter.menuitem', $this->getUserStateFromRequest($this->context . '.filter.menuitem', 'filter_menuitem', '', 'cmd'));
-
- // Special case for the client id.
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $clientId = !in_array($clientId, [0, 1]) ? 0 : $clientId;
- $this->setState('client_id', $clientId);
- }
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_templates');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('client_id');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.template');
- $id .= ':' . $this->getState('filter.menuitem');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- */
- protected function getListQuery()
- {
- $clientId = (int) $this->getState('client_id');
-
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.template'),
- $db->quoteName('a.title'),
- $db->quoteName('a.home'),
- $db->quoteName('a.client_id'),
- $db->quoteName('l.title', 'language_title'),
- $db->quoteName('l.image'),
- $db->quoteName('l.sef', 'language_sef'),
- ]
- )
- )
- ->select(
- [
- 'COUNT(' . $db->quoteName('m.template_style_id') . ') AS assigned',
- $db->quoteName('extension_id', 'e_id'),
- ]
- )
- ->from($db->quoteName('#__template_styles', 'a'))
- ->where($db->quoteName('a.client_id') . ' = :clientid')
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- // Join on menus.
- $query->join('LEFT', $db->quoteName('#__menu', 'm'), $db->quoteName('m.template_style_id') . ' = ' . $db->quoteName('a.id'))
- ->group(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.template'),
- $db->quoteName('a.title'),
- $db->quoteName('a.home'),
- $db->quoteName('a.client_id'),
- $db->quoteName('l.title'),
- $db->quoteName('l.image'),
- $db->quoteName('l.sef'),
- $db->quoteName('e.extension_id'),
- ]
- );
-
- // Join over the language.
- $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.home'));
-
- // Filter by extension enabled.
- $query->join(
- 'LEFT',
- $db->quoteName('#__extensions', 'e'),
- $db->quoteName('e.element') . ' = ' . $db->quoteName('a.template')
- . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('a.client_id')
- )
- ->where(
- [
- $db->quoteName('e.enabled') . ' = 1',
- $db->quoteName('e.type') . ' = ' . $db->quote('template'),
- ]
- );
-
- // Filter by template.
- if ($template = $this->getState('filter.template'))
- {
- $query->where($db->quoteName('a.template') . ' = :template')
- ->bind(':template', $template);
- }
-
- // Filter by menuitem.
- $menuItemId = $this->getState('filter.menuitem');
-
- if ($clientId === 0 && is_numeric($menuItemId))
- {
- // If user selected the templates styles that are not assigned to any page.
- if ((int) $menuItemId === -1)
- {
- // Only custom template styles overrides not assigned to any menu item.
- $query->where(
- [
- $db->quoteName('a.home') . ' = ' . $db->quote('0'),
- $db->quoteName('m.id') . ' IS NULL',
- ]
- );
- }
- // If user selected the templates styles assigned to particular pages.
- else
- {
- // Subquery to get the language of the selected menu item.
- $menuItemId = (int) $menuItemId;
- $menuItemLanguageSubQuery = $db->getQuery(true);
- $menuItemLanguageSubQuery->select($db->quoteName('language'))
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('id') . ' = :menuitemid');
- $query->bind(':menuitemid', $menuItemId, ParameterType::INTEGER);
-
- // Subquery to get the language of the selected menu item.
- $templateStylesMenuItemsSubQuery = $db->getQuery(true);
- $templateStylesMenuItemsSubQuery->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->where($db->quoteName('template_style_id') . ' = ' . $db->quoteName('a.id'));
-
- // Main query where clause.
- $query->where('(' .
- // Default template style (fallback template style to all menu items).
- $db->quoteName('a.home') . ' = ' . $db->quote('1') . ' OR ' .
- // Default template style for specific language (fallback template style to the selected menu item language).
- $db->quoteName('a.home') . ' IN (' . $menuItemLanguageSubQuery . ') OR ' .
- // Custom template styles override (only if assigned to the selected menu item).
- '(' . $db->quoteName('a.home') . ' = ' . $db->quote('0') . ' AND ' . $menuItemId . ' IN (' . $templateStylesMenuItemsSubQuery . '))' .
- ')'
- );
- }
- }
-
- // Filter by search in title.
- if ($search = $this->getState('filter.search'))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . StringHelper::strtolower($search) . '%';
- $query->extendWhere(
- 'AND',
- [
- 'LOWER(' . $db->quoteName('a.template') . ') LIKE :template',
- 'LOWER(' . $db->quoteName('a.title') . ') LIKE :title',
- ],
- 'OR'
- )
- ->bind(':template', $search)
- ->bind(':title', $search);
- }
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.template')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'template', 'a.template',
+ 'home', 'a.home',
+ 'menuitem',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.template', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ if (!$app->isClient('api')) {
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+ $this->setState('filter.template', $this->getUserStateFromRequest($this->context . '.filter.template', 'filter_template', '', 'string'));
+ $this->setState('filter.menuitem', $this->getUserStateFromRequest($this->context . '.filter.menuitem', 'filter_menuitem', '', 'cmd'));
+
+ // Special case for the client id.
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $clientId = !in_array($clientId, [0, 1]) ? 0 : $clientId;
+ $this->setState('client_id', $clientId);
+ }
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_templates');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('client_id');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.template');
+ $id .= ':' . $this->getState('filter.menuitem');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ $clientId = (int) $this->getState('client_id');
+
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.template'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.home'),
+ $db->quoteName('a.client_id'),
+ $db->quoteName('l.title', 'language_title'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.sef', 'language_sef'),
+ ]
+ )
+ )
+ ->select(
+ [
+ 'COUNT(' . $db->quoteName('m.template_style_id') . ') AS assigned',
+ $db->quoteName('extension_id', 'e_id'),
+ ]
+ )
+ ->from($db->quoteName('#__template_styles', 'a'))
+ ->where($db->quoteName('a.client_id') . ' = :clientid')
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ // Join on menus.
+ $query->join('LEFT', $db->quoteName('#__menu', 'm'), $db->quoteName('m.template_style_id') . ' = ' . $db->quoteName('a.id'))
+ ->group(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.template'),
+ $db->quoteName('a.title'),
+ $db->quoteName('a.home'),
+ $db->quoteName('a.client_id'),
+ $db->quoteName('l.title'),
+ $db->quoteName('l.image'),
+ $db->quoteName('l.sef'),
+ $db->quoteName('e.extension_id'),
+ ]
+ );
+
+ // Join over the language.
+ $query->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.home'));
+
+ // Filter by extension enabled.
+ $query->join(
+ 'LEFT',
+ $db->quoteName('#__extensions', 'e'),
+ $db->quoteName('e.element') . ' = ' . $db->quoteName('a.template')
+ . ' AND ' . $db->quoteName('e.client_id') . ' = ' . $db->quoteName('a.client_id')
+ )
+ ->where(
+ [
+ $db->quoteName('e.enabled') . ' = 1',
+ $db->quoteName('e.type') . ' = ' . $db->quote('template'),
+ ]
+ );
+
+ // Filter by template.
+ if ($template = $this->getState('filter.template')) {
+ $query->where($db->quoteName('a.template') . ' = :template')
+ ->bind(':template', $template);
+ }
+
+ // Filter by menuitem.
+ $menuItemId = $this->getState('filter.menuitem');
+
+ if ($clientId === 0 && is_numeric($menuItemId)) {
+ // If user selected the templates styles that are not assigned to any page.
+ if ((int) $menuItemId === -1) {
+ // Only custom template styles overrides not assigned to any menu item.
+ $query->where(
+ [
+ $db->quoteName('a.home') . ' = ' . $db->quote('0'),
+ $db->quoteName('m.id') . ' IS NULL',
+ ]
+ );
+ } else {
+ // If user selected the templates styles assigned to particular pages.
+ // Subquery to get the language of the selected menu item.
+ $menuItemId = (int) $menuItemId;
+ $menuItemLanguageSubQuery = $db->getQuery(true);
+ $menuItemLanguageSubQuery->select($db->quoteName('language'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('id') . ' = :menuitemid');
+ $query->bind(':menuitemid', $menuItemId, ParameterType::INTEGER);
+
+ // Subquery to get the language of the selected menu item.
+ $templateStylesMenuItemsSubQuery = $db->getQuery(true);
+ $templateStylesMenuItemsSubQuery->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->where($db->quoteName('template_style_id') . ' = ' . $db->quoteName('a.id'));
+
+ // Main query where clause.
+ $query->where('(' .
+ // Default template style (fallback template style to all menu items).
+ $db->quoteName('a.home') . ' = ' . $db->quote('1') . ' OR ' .
+ // Default template style for specific language (fallback template style to the selected menu item language).
+ $db->quoteName('a.home') . ' IN (' . $menuItemLanguageSubQuery . ') OR ' .
+ // Custom template styles override (only if assigned to the selected menu item).
+ '(' . $db->quoteName('a.home') . ' = ' . $db->quote('0') . ' AND ' . $menuItemId . ' IN (' . $templateStylesMenuItemsSubQuery . '))' .
+ ')');
+ }
+ }
+
+ // Filter by search in title.
+ if ($search = $this->getState('filter.search')) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . StringHelper::strtolower($search) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ 'LOWER(' . $db->quoteName('a.template') . ') LIKE :template',
+ 'LOWER(' . $db->quoteName('a.title') . ') LIKE :title',
+ ],
+ 'OR'
+ )
+ ->bind(':template', $search)
+ ->bind(':title', $search);
+ }
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.template')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_templates/src/Model/TemplateModel.php b/administrator/components/com_templates/src/Model/TemplateModel.php
index 50267364f0195..f9e3c14c9870c 100644
--- a/administrator/components/com_templates/src/Model/TemplateModel.php
+++ b/administrator/components/com_templates/src/Model/TemplateModel.php
@@ -1,4 +1,5 @@
getTemplate())
- {
- $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $path);
- $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $path);
- $temp->name = $name;
- $temp->id = urlencode(base64_encode(str_replace('\\', '//', $path)));
-
- return $temp;
- }
- }
-
- /**
- * Method to store file information.
- *
- * @param string $path The base path.
- * @param string $name The file name.
- * @param stdClass $template The std class object of template.
- *
- * @return object stdClass object.
- *
- * @since 4.0.0
- */
- protected function storeFileInfo($path, $name, $template)
- {
- $temp = new \stdClass;
- $temp->id = base64_encode($path . $name);
- $temp->client = $template->client_id;
- $temp->template = $template->element;
- $temp->extension_id = $template->extension_id;
-
- if ($coreFile = $this->getCoreFile($path . $name, $template->client_id))
- {
- $temp->coreFile = md5_file($coreFile);
- }
- else
- {
- $temp->coreFile = null;
- }
-
- return $temp;
- }
-
- /**
- * Method to get all template list.
- *
- * @return object stdClass object
- *
- * @since 4.0.0
- */
- public function getTemplateList()
- {
- // Get a db connection.
- $db = $this->getDatabase();
-
- // Create a new query object.
- $query = $db->getQuery(true);
-
- // Select the required fields from the table
- $query->select(
- $this->getState(
- 'list.select',
- 'a.extension_id, a.name, a.element, a.client_id'
- )
- );
-
- $query->from($db->quoteName('#__extensions', 'a'))
- ->where($db->quoteName('a.enabled') . ' = 1')
- ->where($db->quoteName('a.type') . ' = ' . $db->quote('template'));
-
- // Reset the query.
- $db->setQuery($query);
-
- // Load the results as a list of stdClass objects.
- $results = $db->loadObjectList();
-
- return $results;
- }
-
- /**
- * Method to get all updated file list.
- *
- * @param boolean $state The optional parameter if you want unchecked list.
- * @param boolean $all The optional parameter if you want all list.
- * @param boolean $cleanup The optional parameter if you want to clean record which is no more required.
- *
- * @return object stdClass object
- *
- * @since 4.0.0
- */
- public function getUpdatedList($state = false, $all = false, $cleanup = false)
- {
- // Get a db connection.
- $db = $this->getDatabase();
-
- // Create a new query object.
- $query = $db->getQuery(true);
-
- // Select the required fields from the table
- $query->select(
- $this->getState(
- 'list.select',
- 'a.template, a.hash_id, a.extension_id, a.state, a.action, a.client_id, a.created_date, a.modified_date'
- )
- );
-
- $template = $this->getTemplate();
-
- $query->from($db->quoteName('#__template_overrides', 'a'));
-
- if (!$all)
- {
- $teid = (int) $template->extension_id;
- $query->where($db->quoteName('extension_id') . ' = :teid')
- ->bind(':teid', $teid, ParameterType::INTEGER);
- }
-
- if ($state)
- {
- $query->where($db->quoteName('state') . ' = 0');
- }
-
- $query->order($db->quoteName('a.modified_date') . ' DESC');
-
- // Reset the query.
- $db->setQuery($query);
-
- // Load the results as a list of stdClass objects.
- $pks = $db->loadObjectList();
-
- if ($state)
- {
- return $pks;
- }
-
- $results = array();
-
- foreach ($pks as $pk)
- {
- $client = ApplicationHelper::getClientInfo($pk->client_id);
- $path = Path::clean($client->path . '/templates/' . $pk->template . base64_decode($pk->hash_id));
-
- if (file_exists($path))
- {
- $results[] = $pk;
- }
- elseif ($cleanup)
- {
- $cleanupIds = array();
- $cleanupIds[] = $pk->hash_id;
- $this->publish($cleanupIds, -3, $pk->extension_id);
- }
- }
-
- return $results;
- }
-
- /**
- * Method to get a list of all the core files of override files.
- *
- * @return array An array of all core files.
- *
- * @since 4.0.0
- */
- public function getCoreList()
- {
- // Get list of all templates
- $templates = $this->getTemplateList();
-
- // Initialize the array variable to store core file list.
- $this->coreFileList = array();
-
- $app = Factory::getApplication();
-
- foreach ($templates as $template)
- {
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $element = Path::clean($client->path . '/templates/' . $template->element . '/');
- $path = Path::clean($element . 'html/');
-
- if (is_dir($path))
- {
- $this->prepareCoreFiles($path, $element, $template);
- }
- }
-
- // Sort list of stdClass array.
- usort(
- $this->coreFileList,
- function ($a, $b)
- {
- return strcmp($a->id, $b->id);
- }
- );
-
- return $this->coreFileList;
- }
-
- /**
- * Prepare core files.
- *
- * @param string $dir The path of the directory to scan.
- * @param string $element The path of the template element.
- * @param \stdClass $template The stdClass object of template.
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function prepareCoreFiles($dir, $element, $template)
- {
- $dirFiles = scandir($dir);
-
- foreach ($dirFiles as $key => $value)
- {
- if (in_array($value, array('.', '..', 'node_modules')))
- {
- continue;
- }
-
- if (is_dir($dir . $value))
- {
- $relativePath = str_replace($element, '', $dir . $value);
- $this->prepareCoreFiles($dir . $value . '/', $element, $template);
- }
- else
- {
- $ext = pathinfo($dir . $value, PATHINFO_EXTENSION);
- $allowedFormat = $this->checkFormat($ext);
-
- if ($allowedFormat === true)
- {
- $relativePath = str_replace($element, '', $dir);
- $info = $this->storeFileInfo('/' . $relativePath, $value, $template);
-
- if ($info)
- {
- $this->coreFileList[] = $info;
- }
- }
- }
- }
- }
-
- /**
- * Method to update status of list.
- *
- * @param array $ids The base path.
- * @param array $value The file name.
- * @param integer $exid The template extension id.
- *
- * @return integer Number of files changed.
- *
- * @since 4.0.0
- */
- public function publish($ids, $value, $exid)
- {
- $db = $this->getDatabase();
-
- foreach ($ids as $id)
- {
- if ($value === -3)
- {
- $deleteQuery = $db->getQuery(true)
- ->delete($db->quoteName('#__template_overrides'))
- ->where($db->quoteName('hash_id') . ' = :hashid')
- ->where($db->quoteName('extension_id') . ' = :exid')
- ->bind(':hashid', $id)
- ->bind(':exid', $exid, ParameterType::INTEGER);
-
- try
- {
- // Set the query using our newly populated query object and execute it.
- $db->setQuery($deleteQuery);
- $result = $db->execute();
- }
- catch (\RuntimeException $e)
- {
- return $e;
- }
- }
- elseif ($value === 1 || $value === 0)
- {
- $updateQuery = $db->getQuery(true)
- ->update($db->quoteName('#__template_overrides'))
- ->set($db->quoteName('state') . ' = :state')
- ->where($db->quoteName('hash_id') . ' = :hashid')
- ->where($db->quoteName('extension_id') . ' = :exid')
- ->bind(':state', $value, ParameterType::INTEGER)
- ->bind(':hashid', $id)
- ->bind(':exid', $exid, ParameterType::INTEGER);
-
- try
- {
- // Set the query using our newly populated query object and execute it.
- $db->setQuery($updateQuery);
- $result = $db->execute();
- }
- catch (\RuntimeException $e)
- {
- return $e;
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get a list of all the files to edit in a template.
- *
- * @return array A nested array of relevant files.
- *
- * @since 1.6
- */
- public function getFiles()
- {
- $result = array();
-
- if ($template = $this->getTemplate())
- {
- $app = Factory::getApplication();
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $path = Path::clean($client->path . '/templates/' . $template->element . '/');
- $lang = Factory::getLanguage();
-
- // Load the core and/or local language file(s).
- $lang->load('tpl_' . $template->element, $client->path)
- || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path))
- || $lang->load('tpl_' . $template->element, $client->path . '/templates/' . $template->element)
- || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path . '/templates/' . $template->xmldata->parent));
- $this->element = $path;
-
- if (!is_writable($path))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error');
- }
-
- if (is_dir($path))
- {
- $result = $this->getDirectoryTree($path);
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_TEMPLATE_FOLDER_NOT_FOUND'), 'error');
-
- return false;
- }
-
- // Clean up override history
- $this->getUpdatedList(false, true, true);
- }
-
- return $result;
- }
-
- /**
- * Get the directory tree.
- *
- * @param string $dir The path of the directory to scan
- *
- * @return array
- *
- * @since 3.2
- */
- public function getDirectoryTree($dir)
- {
- $result = array();
-
- $dirFiles = scandir($dir);
-
- foreach ($dirFiles as $key => $value)
- {
- if (!in_array($value, array('.', '..', 'node_modules')))
- {
- if (is_dir($dir . $value))
- {
- $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value);
- $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) .'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath);
- $result[str_replace('\\', '//', $relativePath)] = $this->getDirectoryTree($dir . $value . '/');
- }
- else
- {
- $ext = pathinfo($dir . $value, PATHINFO_EXTENSION);
- $allowedFormat = $this->checkFormat($ext);
-
- if ($allowedFormat == true)
- {
- $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media'. DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value);
- $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath);
- $result[] = $this->getFile($relativePath, $value);
- }
- }
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get the core file of override file
- *
- * @param string $file Override file
- * @param integer $client_id Client Id
- *
- * @return string $corefile The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
- *
- * @since 4.0.0
- */
- public function getCoreFile($file, $client_id)
- {
- $app = Factory::getApplication();
- $filePath = Path::clean($file);
- $explodeArray = explode(DIRECTORY_SEPARATOR, $filePath);
-
- // Only allow html/ folder
- if ($explodeArray['1'] !== 'html')
- {
- return false;
- }
-
- $fileName = basename($filePath);
- $type = $explodeArray['2'];
- $client = ApplicationHelper::getClientInfo($client_id);
-
- $componentPath = Path::clean($client->path . '/components/');
- $modulePath = Path::clean($client->path . '/modules/');
- $layoutPath = Path::clean(JPATH_ROOT . '/layouts/');
-
- // For modules
- if (stristr($type, 'mod_') !== false)
- {
- $folder = $explodeArray['2'];
- $htmlPath = Path::clean($modulePath . $folder . '/tmpl/');
- $fileName = $this->getSafeName($fileName);
- $coreFile = Path::find($htmlPath, $fileName);
-
- return $coreFile;
- }
- elseif (stristr($type, 'com_') !== false)
- {
- // For components
- $folder = $explodeArray['2'];
- $subFolder = $explodeArray['3'];
- $fileName = $this->getSafeName($fileName);
-
- // The new scheme, if a view has a tmpl folder
- $newHtmlPath = Path::clean($componentPath . $folder . '/tmpl/' . $subFolder . '/');
-
- if (!$coreFile = Path::find($newHtmlPath, $fileName))
- {
- // The old scheme, the views are directly in the component/tmpl folder
- $oldHtmlPath = Path::clean($componentPath . $folder . '/views/' . $subFolder . '/tmpl/');
- $coreFile = Path::find($oldHtmlPath, $fileName);
-
- return $coreFile;
- }
-
- return $coreFile;
- }
- elseif (stristr($type, 'layouts') !== false)
- {
- // For Layouts
- $subtype = $explodeArray['3'];
-
- if (stristr($subtype, 'com_'))
- {
- $folder = $explodeArray['3'];
- $subFolder = array_slice($explodeArray, 4, -1);
- $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
- $htmlPath = Path::clean($componentPath . $folder . '/layouts/' . $subFolder);
- $fileName = $this->getSafeName($fileName);
- $coreFile = Path::find($htmlPath, $fileName);
-
- return $coreFile;
- }
- elseif (stristr($subtype, 'joomla') || stristr($subtype, 'libraries') || stristr($subtype, 'plugins'))
- {
- $subFolder = array_slice($explodeArray, 3, -1);
- $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
- $htmlPath = Path::clean($layoutPath . $subFolder);
- $fileName = $this->getSafeName($fileName);
- $coreFile = Path::find($htmlPath, $fileName);
-
- return $coreFile;
- }
- }
-
- return false;
- }
-
- /**
- * Creates a safe file name for the given name.
- *
- * @param string $name The filename
- *
- * @return string $fileName The filtered name without Date
- *
- * @since 4.0.0
- */
- private function getSafeName($name)
- {
- if (strpos($name, '-') !== false && preg_match('/[0-9]/', $name))
- {
- // Get the extension
- $extension = File::getExt($name);
-
- // Remove ( Date ) from file
- $explodeArray = explode('-', $name);
- $size = count($explodeArray);
- $date = $explodeArray[$size - 2] . '-' . str_replace('.' . $extension, '', $explodeArray[$size - 1]);
-
- if ($this->validateDate($date))
- {
- $nameWithoutExtension = implode('-', array_slice($explodeArray, 0, -2));
-
- // Filtered name
- $name = $nameWithoutExtension . '.' . $extension;
- }
- }
-
- return $name;
- }
-
- /**
- * Validate Date in file name.
- *
- * @param string $date Date to validate.
- *
- * @return boolean Return true if date is valid and false if not.
- *
- * @since 4.0.0
- */
- private function validateDate($date)
- {
- $format = 'Ymd-His';
- $valid = Date::createFromFormat($format, $date);
-
- return $valid && $valid->format($format) === $date;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState()
- {
- $app = Factory::getApplication();
-
- // Load the User state.
- $pk = $app->input->getInt('id');
- $this->setState('extension.id', $pk);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_templates');
- $this->setState('params', $params);
- }
-
- /**
- * Method to get the template information.
- *
- * @return mixed Object if successful, false if not and internal error is set.
- *
- * @since 1.6
- */
- public function &getTemplate()
- {
- if (empty($this->template))
- {
- $pk = (int) $this->getState('extension.id');
- $db = $this->getDatabase();
- $app = Factory::getApplication();
-
- // Get the template information.
- $query = $db->getQuery(true)
- ->select($db->quoteName(['extension_id', 'client_id', 'element', 'name', 'manifest_cache']))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('extension_id') . ' = :pk')
- ->where($db->quoteName('type') . ' = ' . $db->quote('template'))
- ->bind(':pk', $pk, ParameterType::INTEGER);
- $db->setQuery($query);
-
- try
- {
- $result = $db->loadObject();
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'warning');
- $this->template = false;
-
- return false;
- }
-
- if (empty($result))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'error');
- $this->template = false;
- }
- else
- {
- $this->template = $result;
-
- // Client ID is not always an integer, so enforce here
- $this->template->client_id = (int) $this->template->client_id;
-
- if (!isset($this->template->xmldata))
- {
- $this->template->xmldata = TemplatesHelper::parseXMLTemplateFile($this->template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $this->template->name);
- }
- }
- }
-
- return $this->template;
- }
-
- /**
- * Method to check if new template name already exists
- *
- * @return boolean true if name is not used, false otherwise
- *
- * @since 2.5
- */
- public function checkNewName()
- {
- $db = $this->getDatabase();
- $name = $this->getState('new_name');
- $query = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('name') . ' = :name')
- ->bind(':name', $name);
- $db->setQuery($query);
-
- return ($db->loadResult() == 0);
- }
-
- /**
- * Method to check if new template name already exists
- *
- * @return string name of current template
- *
- * @since 2.5
- */
- public function getFromName()
- {
- return $this->getTemplate()->element;
- }
-
- /**
- * Method to check if new template name already exists
- *
- * @return boolean true if name is not used, false otherwise
- *
- * @since 2.5
- */
- public function copy()
- {
- $app = Factory::getApplication();
-
- if ($template = $this->getTemplate())
- {
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/');
-
- // Delete new folder if it exists
- $toPath = $this->getState('to_path');
-
- if (Folder::exists($toPath))
- {
- if (!Folder::delete($toPath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
-
- return false;
- }
- }
-
- // Copy all files from $fromName template to $newName folder
- if (!Folder::copy($fromPath, $toPath))
- {
- return false;
- }
-
- // Check manifest for additional files
- $manifest = simplexml_load_file($toPath . '/templateDetails.xml');
-
- // Copy language files from global folder
- if ($languages = $manifest->languages)
- {
- $folder = (string) $languages->attributes()->folder;
- $languageFiles = $languages->language;
-
- Folder::create($toPath . '/' . $folder . '/' . $languageFiles->attributes()->tag);
-
- foreach ($languageFiles as $languageFile)
- {
- $src = Path::clean($client->path . '/language/' . $languageFile);
- $dst = Path::clean($toPath . '/' . $folder . '/' . $languageFile);
-
- if (File::exists($src))
- {
- File::copy($src, $dst);
- }
- }
- }
-
- // Copy media files
- if ($media = $manifest->media)
- {
- $folder = (string) $media->attributes()->folder;
- $destination = (string) $media->attributes()->destination;
-
- Folder::copy(JPATH_SITE . '/media/' . $destination, $toPath . '/' . $folder);
- }
-
- // Adjust to new template name
- if (!$this->fixTemplateName())
- {
- return false;
- }
-
- return true;
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
-
- return false;
- }
- }
-
- /**
- * Method to delete tmp folder
- *
- * @return boolean true if delete successful, false otherwise
- *
- * @since 2.5
- */
- public function cleanup()
- {
- // Clear installation messages
- $app = Factory::getApplication();
- $app->setUserState('com_installer.message', '');
- $app->setUserState('com_installer.extension_message', '');
-
- // Delete temporary directory
- return Folder::delete($this->getState('to_path'));
- }
-
- /**
- * Method to rename the template in the XML files and rename the language files
- *
- * @return boolean true if successful, false otherwise
- *
- * @since 2.5
- */
- protected function fixTemplateName()
- {
- // Rename Language files
- // Get list of language files
- $result = true;
- $files = Folder::files($this->getState('to_path'), '\.ini$', true, true);
- $newName = strtolower($this->getState('new_name'));
- $template = $this->getTemplate();
- $oldName = $template->element;
- $manifest = json_decode($template->manifest_cache);
-
- foreach ($files as $file)
- {
- $newFile = '/' . str_replace($oldName, $newName, basename($file));
- $result = File::move($file, dirname($file) . $newFile) && $result;
- }
-
- // Edit XML file
- $xmlFile = $this->getState('to_path') . '/templateDetails.xml';
-
- if (File::exists($xmlFile))
- {
- $contents = file_get_contents($xmlFile);
- $pattern[] = '#\s*' . $manifest->name . '\s* #i';
- $replace[] = '' . $newName . ' ';
- $pattern[] = '##';
- $replace[] = '';
- $pattern[] = '##';
- $replace[] = '';
- $contents = preg_replace($pattern, $replace, $contents);
- $result = File::write($xmlFile, $contents) && $result;
- }
-
- return $result;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- $app = Factory::getApplication();
-
- // Codemirror or Editor None should be enabled
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from('#__extensions as a')
- ->where(
- '(a.name =' . $db->quote('plg_editors_codemirror') .
- ' AND a.enabled = 1) OR (a.name =' .
- $db->quote('plg_editors_none') .
- ' AND a.enabled = 1)'
- );
- $db->setQuery($query);
- $state = $db->loadResult();
-
- if ((int) $state < 1)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EDITOR_DISABLED'), 'warning');
- }
-
- // Get the form.
- $form = $this->loadForm('com_templates.source', 'source', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- */
- protected function loadFormData()
- {
- $data = $this->getSource();
-
- $this->preprocessData('com_templates.source', $data);
-
- return $data;
- }
-
- /**
- * Method to get a single record.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function &getSource()
- {
- $app = Factory::getApplication();
- $item = new \stdClass;
-
- if (!$this->template)
- {
- $this->getTemplate();
- }
-
- if ($this->template)
- {
- $input = Factory::getApplication()->input;
- $fileName = base64_decode($input->get('file'));
- $fileName = str_replace('//', '/', $fileName);
- $isMedia = $input->getInt('isMedia', 0);
-
- $fileName = $isMedia ? Path::clean(JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName)
- : Path::clean(JPATH_ROOT . ($this->template->client_id === 0 ? '' : '/administrator') . '/templates/' . $this->template->element . $fileName);
-
- try
- {
- $filePath = Path::check($fileName);
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
-
- return;
- }
-
- if (file_exists($filePath))
- {
- $item->extension_id = $this->getState('extension.id');
- $item->filename = Path::clean($fileName);
- $item->source = file_get_contents($filePath);
- $item->filePath = Path::clean($filePath);
- $ds = DIRECTORY_SEPARATOR;
- $cleanFileName = str_replace(JPATH_ROOT . ($this->template->client_id === 1 ? $ds . 'administrator' . $ds : $ds) . 'templates' . $ds . $this->template->element, '', $fileName);
-
- if ($coreFile = $this->getCoreFile($cleanFileName, $this->template->client_id))
- {
- $item->coreFile = $coreFile;
- $item->core = file_get_contents($coreFile);
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
- }
- }
-
- return $item;
- }
-
- /**
- * Method to store the source file contents.
- *
- * @param array $data The source data to save.
- *
- * @return boolean True on success, false otherwise and internal error set.
- *
- * @since 1.6
- */
- public function save($data)
- {
- // Get the template.
- $template = $this->getTemplate();
-
- if (empty($template))
- {
- return false;
- }
-
- $app = Factory::getApplication();
- $fileName = base64_decode($app->input->get('file'));
- $isMedia = $app->input->getInt('isMedia', 0);
- $fileName = $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName :
- JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element . $fileName;
-
- $filePath = Path::clean($fileName);
-
- // Include the extension plugins for the save events.
- PluginHelper::importPlugin('extension');
-
- $user = get_current_user();
- chown($filePath, $user);
- Path::setPermissions($filePath, '0644');
-
- // Try to make the template file writable.
- if (!is_writable($filePath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_WRITABLE'), 'warning');
- $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_PERMISSIONS', Path::getPermissions($filePath)), 'warning');
-
- if (!Path::isOwner($filePath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_CHECK_FILE_OWNERSHIP'), 'warning');
- }
-
- return false;
- }
-
- // Make sure EOL is Unix
- $data['source'] = str_replace(array("\r\n", "\r"), "\n", $data['source']);
-
- // If the asset file for the template ensure we have valid template so we don't instantly destroy it
- if ($fileName === '/joomla.asset.json' && json_decode($data['source']) === null)
- {
- $this->setError(Text::_('COM_TEMPLATES_ERROR_ASSET_FILE_INVALID_JSON'));
-
- return false;
- }
-
- $return = File::write($filePath, $data['source']);
-
- if (!$return)
- {
- $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_ERROR_FAILED_TO_SAVE_FILENAME', $fileName), 'error');
-
- return false;
- }
-
- // Get the extension of the changed file.
- $explodeArray = explode('.', $fileName);
- $ext = end($explodeArray);
-
- if ($ext == 'less')
- {
- $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_COMPILE_LESS', $fileName));
- }
-
- return true;
- }
-
- /**
- * Get overrides folder.
- *
- * @param string $name The name of override.
- * @param string $path Location of override.
- *
- * @return object containing override name and path.
- *
- * @since 3.2
- */
- public function getOverridesFolder($name,$path)
- {
- $folder = new \stdClass;
- $folder->name = $name;
- $folder->path = base64_encode($path . $name);
-
- return $folder;
- }
-
- /**
- * Get a list of overrides.
- *
- * @return array containing overrides.
- *
- * @since 3.2
- */
- public function getOverridesList()
- {
- if ($template = $this->getTemplate())
- {
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $componentPath = Path::clean($client->path . '/components/');
- $modulePath = Path::clean($client->path . '/modules/');
- $pluginPath = Path::clean(JPATH_ROOT . '/plugins/');
- $layoutPath = Path::clean(JPATH_ROOT . '/layouts/');
- $components = Folder::folders($componentPath);
-
- foreach ($components as $component)
- {
- // Collect the folders with views
- $folders = Folder::folders($componentPath . '/' . $component, '^view[s]?$', false, true);
- $folders = array_merge($folders, Folder::folders($componentPath . '/' . $component, '^tmpl?$', false, true));
-
- if (!$folders)
- {
- continue;
- }
-
- foreach ($folders as $folder)
- {
- // The subfolders are views
- $views = Folder::folders($folder);
-
- foreach ($views as $view)
- {
- // The old scheme, if a view has a tmpl folder
- $path = $folder . '/' . $view . '/tmpl';
-
- // The new scheme, the views are directly in the component/tmpl folder
- if (!is_dir($path) && substr($folder, -4) == 'tmpl')
- {
- $path = $folder . '/' . $view;
- }
-
- // Check if the folder exists
- if (!is_dir($path))
- {
- continue;
- }
-
- $result['components'][$component][] = $this->getOverridesFolder($view, Path::clean($folder . '/'));
- }
- }
- }
-
- foreach (Folder::folders($pluginPath) as $pluginGroup)
- {
- foreach (Folder::folders($pluginPath . '/' . $pluginGroup) as $plugin)
- {
- if (file_exists($pluginPath . '/' . $pluginGroup . '/' . $plugin . '/tmpl/'))
- {
- $pluginLayoutPath = Path::clean($pluginPath . '/' . $pluginGroup . '/');
- $result['plugins'][$pluginGroup][] = $this->getOverridesFolder($plugin, $pluginLayoutPath);
- }
- }
- }
-
- $modules = Folder::folders($modulePath);
-
- foreach ($modules as $module)
- {
- $result['modules'][] = $this->getOverridesFolder($module, $modulePath);
- }
-
- $layoutFolders = Folder::folders($layoutPath);
-
- foreach ($layoutFolders as $layoutFolder)
- {
- $layoutFolderPath = Path::clean($layoutPath . '/' . $layoutFolder . '/');
- $layouts = Folder::folders($layoutFolderPath);
-
- foreach ($layouts as $layout)
- {
- $result['layouts'][$layoutFolder][] = $this->getOverridesFolder($layout, $layoutFolderPath);
- }
- }
-
- // Check for layouts in component folders
- foreach ($components as $component)
- {
- if (file_exists($componentPath . '/' . $component . '/layouts/'))
- {
- $componentLayoutPath = Path::clean($componentPath . '/' . $component . '/layouts/');
-
- if ($componentLayoutPath)
- {
- $layouts = Folder::folders($componentLayoutPath);
-
- foreach ($layouts as $layout)
- {
- $result['layouts'][$component][] = $this->getOverridesFolder($layout, $componentLayoutPath);
- }
- }
- }
- }
- }
-
- if (!empty($result))
- {
- return $result;
- }
- }
-
- /**
- * Create overrides.
- *
- * @param string $override The override location.
- *
- * @return boolean true if override creation is successful, false otherwise
- *
- * @since 3.2
- */
- public function createOverride($override)
- {
- if ($template = $this->getTemplate())
- {
- $app = Factory::getApplication();
- $explodeArray = explode(DIRECTORY_SEPARATOR, $override);
- $name = end($explodeArray);
- $client = ApplicationHelper::getClientInfo($template->client_id);
-
- if (stristr($name, 'mod_') != false)
- {
- $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $name);
- }
- elseif (stristr($override, 'com_') != false)
- {
- $size = count($explodeArray);
-
- $url = Path::clean($explodeArray[$size - 3] . '/' . $explodeArray[$size - 1]);
-
- if ($explodeArray[$size - 2] == 'layouts')
- {
- $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $url);
- }
- else
- {
- $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $url);
- }
- }
- elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0)
- {
- $size = count($explodeArray);
- $layoutPath = Path::clean('plg_' . $explodeArray[$size - 2] . '_' . $explodeArray[$size - 1]);
- $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $layoutPath);
- }
- else
- {
- $layoutPath = implode('/', array_slice($explodeArray, -2));
- $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $layoutPath);
- }
-
- // Check Html folder, create if not exist
- if (!Folder::exists($htmlPath))
- {
- if (!Folder::create($htmlPath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_ERROR'), 'error');
-
- return false;
- }
- }
-
- if (stristr($name, 'mod_') != false)
- {
- $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath);
- }
- elseif (stristr($override, 'com_') != false && stristr($override, 'layouts') == false)
- {
- $path = $override . '/tmpl';
-
- // View can also be in the top level folder
- if (!is_dir($path))
- {
- $path = $override;
- }
-
- $return = $this->createTemplateOverride(Path::clean($path), $htmlPath);
- }
- elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0)
- {
- $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath);
- }
- else
- {
- $return = $this->createTemplateOverride($override, $htmlPath);
- }
-
- if ($return)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath));
-
- return true;
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_FAILED'), 'error');
-
- return false;
- }
- }
- }
-
- /**
- * Create override folder & file
- *
- * @param string $overridePath The override location
- * @param string $htmlPath The html location
- *
- * @return boolean True on success. False otherwise.
- */
- public function createTemplateOverride($overridePath, $htmlPath)
- {
- $return = false;
-
- if (empty($overridePath) || empty($htmlPath))
- {
- return $return;
- }
-
- // Get list of template folders
- $folders = Folder::folders($overridePath, null, true, true);
-
- if (!empty($folders))
- {
- foreach ($folders as $folder)
- {
- $htmlFolder = $htmlPath . str_replace($overridePath, '', $folder);
-
- if (!Folder::exists($htmlFolder))
- {
- Folder::create($htmlFolder);
- }
- }
- }
-
- // Get list of template files (Only get *.php file for template file)
- $files = Folder::files($overridePath, '.php', true, true);
-
- if (empty($files))
- {
- return true;
- }
-
- foreach ($files as $file)
- {
- $overrideFilePath = str_replace($overridePath, '', $file);
- $htmlFilePath = $htmlPath . $overrideFilePath;
-
- if (File::exists($htmlFilePath))
- {
- // Generate new unique file name base on current time
- $today = Factory::getDate();
- $htmlFilePath = File::stripExt($htmlFilePath) . '-' . $today->format('Ymd-His') . '.' . File::getExt($htmlFilePath);
- }
-
- $return = File::copy($file, $htmlFilePath, '', true);
- }
-
- return $return;
- }
-
- /**
- * Delete a particular file.
- *
- * @param string $file The relative location of the file.
- *
- * @return boolean True if file deletion is successful, false otherwise
- *
- * @since 3.2
- */
- public function deleteFile($file)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $filePath = $this->getBasePath() . urldecode(base64_decode($file));
-
- $return = File::delete($filePath);
-
- if (!$return)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_DELETE_ERROR'), 'error');
-
- return false;
- }
-
- return true;
- }
- }
-
- /**
- * Create new file.
- *
- * @param string $name The name of file.
- * @param string $type The extension of the file.
- * @param string $location Location for the new file.
- *
- * @return boolean true if file created successfully, false otherwise
- *
- * @since 3.2
- */
- public function createFile($name, $type, $location)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $base = $this->getBasePath();
-
- if (file_exists(Path::clean($base . '/' . $location . '/' . $name . '.' . $type)))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
-
- return false;
- }
-
- if (!fopen(Path::clean($base . '/' . $location . '/' . $name . '.' . $type), 'x'))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_CREATE_ERROR'), 'error');
-
- return false;
- }
-
- // Check if the format is allowed and will be showed in the backend
- $check = $this->checkFormat($type);
-
- // Add a message if we are not allowed to show this file in the backend.
- if (!$check)
- {
- $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_WARNING_FORMAT_WILL_NOT_BE_VISIBLE', $type), 'warning');
- }
-
- return true;
- }
- }
-
- /**
- * Upload new file.
- *
- * @param array $file The uploaded file array.
- * @param string $location Location for the new file.
- *
- * @return boolean True if file uploaded successfully, false otherwise
- *
- * @since 3.2
- */
- public function uploadFile($file, $location)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = $this->getBasePath();
- $fileName = File::makeSafe($file['name']);
-
- $err = null;
-
- if (!TemplateHelper::canUpload($file, $err))
- {
- // Can't upload the file
- return false;
- }
-
- if (file_exists(Path::clean($path . '/' . $location . '/' . $file['name'])))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
-
- return false;
- }
-
- if (!File::upload($file['tmp_name'], Path::clean($path . '/' . $location . '/' . $fileName)))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UPLOAD_ERROR'), 'error');
-
- return false;
- }
-
- $url = Path::clean($location . '/' . $fileName);
-
- return $url;
- }
- }
-
- /**
- * Create new folder.
- *
- * @param string $name The name of the new folder.
- * @param string $location Location for the new folder.
- *
- * @return boolean True if override folder is created successfully, false otherwise
- *
- * @since 3.2
- */
- public function createFolder($name, $location)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = Path::clean($location . '/');
- $base = $this->getBasePath();
-
- if (file_exists(Path::clean($base . $path . $name)))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_EXISTS'), 'error');
-
- return false;
- }
-
- if (!Folder::create(Path::clean($base . $path . $name)))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_CREATE_ERROR'), 'error');
-
- return false;
- }
-
- return true;
- }
- }
-
- /**
- * Delete a folder.
- *
- * @param string $location The name and location of the folder.
- *
- * @return boolean True if override folder is deleted successfully, false otherwise
- *
- * @since 3.2
- */
- public function deleteFolder($location)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $base = $this->getBasePath();
- $path = Path::clean($location . '/');
-
- if (!file_exists($base . $path))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_NOT_EXISTS'), 'error');
-
- return false;
- }
-
- $return = Folder::delete($base . $path);
-
- if (!$return)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_ERROR'), 'error');
-
- return false;
- }
-
- return true;
- }
- }
-
- /**
- * Rename a file.
- *
- * @param string $file The name and location of the old file
- * @param string $name The new name of the file.
- *
- * @return string Encoded string containing the new file location.
- *
- * @since 3.2
- */
- public function renameFile($file, $name)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = $this->getBasePath();
- $fileName = base64_decode($file);
- $explodeArray = explode('.', $fileName);
- $type = end($explodeArray);
- $explodeArray = explode('/', $fileName);
- $newName = str_replace(end($explodeArray), $name . '.' . $type, $fileName);
-
- if (file_exists($path . $newName))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
-
- return false;
- }
-
- if (!rename($path . $fileName, $path . $newName))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_RENAME_ERROR'), 'error');
-
- return false;
- }
-
- return base64_encode($newName);
- }
- }
-
- /**
- * Get an image address, height and width.
- *
- * @return array an associative array containing image address, height and width.
- *
- * @since 3.2
- */
- public function getImage()
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $fileName = base64_decode($app->input->get('file'));
- $path = $this->getBasePath();
-
- $uri = Uri::root(false) . ltrim(str_replace(JPATH_ROOT, '', $this->getBasePath()), '/');
-
- if (file_exists(Path::clean($path . $fileName)))
- {
- $JImage = new Image(Path::clean($path . $fileName));
- $image['address'] = $uri . $fileName;
- $image['path'] = $fileName;
- $image['height'] = $JImage->getHeight();
- $image['width'] = $JImage->getWidth();
- }
-
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_IMAGE_FILE_NOT_FOUND'), 'error');
-
- return false;
- }
-
- return $image;
- }
- }
-
- /**
- * Crop an image.
- *
- * @param string $file The name and location of the file
- * @param string $w width.
- * @param string $h height.
- * @param string $x x-coordinate.
- * @param string $y y-coordinate.
- *
- * @return boolean true if image cropped successfully, false otherwise.
- *
- * @since 3.2
- */
- public function cropImage($file, $w, $h, $x, $y)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = $this->getBasePath() . base64_decode($file);
-
- try
- {
- $image = new Image($path);
- $properties = $image->getImageFileProperties($path);
-
- switch ($properties->mime)
- {
- case 'image/webp':
- $imageType = \IMAGETYPE_WEBP;
- break;
- case 'image/png':
- $imageType = \IMAGETYPE_PNG;
- break;
- case 'image/gif':
- $imageType = \IMAGETYPE_GIF;
- break;
- default:
- $imageType = \IMAGETYPE_JPEG;
- }
-
- $image->crop($w, $h, $x, $y, false);
- $image->toFile($path, $imageType);
-
- return true;
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
- }
- }
- }
-
- /**
- * Resize an image.
- *
- * @param string $file The name and location of the file
- * @param string $width The new width of the image.
- * @param string $height The new height of the image.
- *
- * @return boolean true if image resize successful, false otherwise.
- *
- * @since 3.2
- */
- public function resizeImage($file, $width, $height)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = $this->getBasePath() . base64_decode($file);
-
- try
- {
- $image = new Image($path);
- $properties = $image->getImageFileProperties($path);
-
- switch ($properties->mime)
- {
- case 'image/webp':
- $imageType = \IMAGETYPE_WEBP;
- break;
- case 'image/png':
- $imageType = \IMAGETYPE_PNG;
- break;
- case 'image/gif':
- $imageType = \IMAGETYPE_GIF;
- break;
- default:
- $imageType = \IMAGETYPE_JPEG;
- }
-
- $image->resize($width, $height, false, Image::SCALE_FILL);
- $image->toFile($path, $imageType);
-
- return true;
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
- }
- }
- }
-
- /**
- * Template preview.
- *
- * @return object object containing the id of the template.
- *
- * @since 3.2
- */
- public function getPreview()
- {
- $app = Factory::getApplication();
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName(['id', 'client_id']));
- $query->from($db->quoteName('#__template_styles'));
- $query->where($db->quoteName('template') . ' = :template')
- ->bind(':template', $this->template->element);
-
- $db->setQuery($query);
-
- try
- {
- $result = $db->loadObject();
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'warning');
- }
-
- if (empty($result))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'warning');
- }
- else
- {
- return $result;
- }
- }
-
- /**
- * Rename a file.
- *
- * @return mixed array on success, false on failure
- *
- * @since 3.2
- */
- public function getFont()
- {
- if ($template = $this->getTemplate())
- {
- $app = Factory::getApplication();
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $relPath = base64_decode($app->input->get('file'));
- $explodeArray = explode('/', $relPath);
- $fileName = end($explodeArray);
- $path = $this->getBasePath() . base64_decode($app->input->get('file'));
-
- if (stristr($client->path, 'administrator') == false)
- {
- $folder = '/templates/';
- }
- else
- {
- $folder = '/administrator/templates/';
- }
-
- $uri = Uri::root(true) . $folder . $template->element;
-
- if (file_exists(Path::clean($path)))
- {
- $font['address'] = $uri . $relPath;
-
- $font['rel_path'] = $relPath;
-
- $font['name'] = $fileName;
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
-
- return false;
- }
-
- return $font;
- }
- }
-
- /**
- * Copy a file.
- *
- * @param string $newName The name of the copied file
- * @param string $location The final location where the file is to be copied
- * @param string $file The name and location of the file
- *
- * @return boolean true if image resize successful, false otherwise.
- *
- * @since 3.2
- */
- public function copyFile($newName, $location, $file)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $relPath = base64_decode($file);
- $explodeArray = explode('.', $relPath);
- $ext = end($explodeArray);
- $path = $this->getBasePath();
- $newPath = Path::clean($path . $location . '/' . $newName . '.' . $ext);
-
- if (file_exists($newPath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
-
- return false;
- }
-
- if (File::copy($path . $relPath, $newPath))
- {
- $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_COPY_SUCCESS', $newName . '.' . $ext));
-
- return true;
- }
- else
- {
- return false;
- }
- }
- }
-
- /**
- * Get the compressed files.
- *
- * @return array if file exists, false otherwise
- *
- * @since 3.2
- */
- public function getArchive()
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $path = $this->getBasePath() . base64_decode($app->input->get('file'));
-
- if (file_exists(Path::clean($path)))
- {
- $files = array();
- $zip = new \ZipArchive;
-
- if ($zip->open($path) === true)
- {
- for ($i = 0; $i < $zip->numFiles; $i++)
- {
- $entry = $zip->getNameIndex($i);
- $files[] = $entry;
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
-
- return false;
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
-
- return false;
- }
-
- return $files;
- }
- }
-
- /**
- * Extract contents of an archive file.
- *
- * @param string $file The name and location of the file
- *
- * @return boolean true if image extraction is successful, false otherwise.
- *
- * @since 3.2
- */
- public function extractArchive($file)
- {
- if ($this->getTemplate())
- {
- $app = Factory::getApplication();
- $relPath = base64_decode($file);
- $explodeArray = explode('/', $relPath);
- $fileName = end($explodeArray);
- $path = $this->getBasePath() . base64_decode($file);
-
- if (file_exists(Path::clean($path . '/' . $fileName)))
- {
- $zip = new \ZipArchive;
-
- if ($zip->open(Path::clean($path . '/' . $fileName)) === true)
- {
- for ($i = 0; $i < $zip->numFiles; $i++)
- {
- $entry = $zip->getNameIndex($i);
-
- if (file_exists(Path::clean($path . '/' . $entry)))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXISTS'), 'error');
-
- return false;
- }
- }
-
- $zip->extractTo($path);
-
- return true;
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
-
- return false;
- }
- }
- else
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_NOT_FOUND'), 'error');
-
- return false;
- }
- }
- }
-
- /**
- * Check if the extension is allowed and will be shown in the template manager
- *
- * @param string $ext The extension to check if it is allowed
- *
- * @return boolean true if the extension is allowed false otherwise
- *
- * @since 3.6.0
- */
- protected function checkFormat($ext)
- {
- if (!isset($this->allowedFormats))
- {
- $params = ComponentHelper::getParams('com_templates');
- $imageTypes = explode(',', $params->get('image_formats'));
- $sourceTypes = explode(',', $params->get('source_formats'));
- $fontTypes = explode(',', $params->get('font_formats'));
- $archiveTypes = explode(',', $params->get('compressed_formats'));
-
- $this->allowedFormats = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
- $this->allowedFormats = array_map('strtolower', $this->allowedFormats);
- }
-
- return in_array(strtolower($ext), $this->allowedFormats);
- }
-
- /**
- * Method to get a list of all the files to edit in a template's media folder.
- *
- * @return array A nested array of relevant files.
- *
- * @since 4.1.0
- */
- public function getMediaFiles()
- {
- $result = [];
- $template = $this->getTemplate();
-
- if (!isset($template->xmldata))
- {
- $template->xmldata = TemplatesHelper::parseXMLTemplateFile($template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
- }
-
- if (!isset($template->xmldata->inheritable) || (isset($template->xmldata->parent) && $template->xmldata->parent === ''))
- {
- return $result;
- }
-
- $app = Factory::getApplication();
- $path = Path::clean(JPATH_ROOT . '/media/templates/' . ($template->client_id === 0 ? 'site' : 'administrator') . '/' . $template->element . '/');
- $this->mediaElement = $path;
-
- if (!is_writable($path))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error');
- }
-
- if (is_dir($path))
- {
- $result = $this->getDirectoryTree($path);
- }
-
- return $result;
- }
-
- /**
- * Method to resolve the base folder.
- *
- * @return string The absolute path for the base.
- *
- * @since 4.1.0
- */
- private function getBasePath()
- {
- $app = Factory::getApplication();
- $isMedia = $app->input->getInt('isMedia', 0);
-
- return $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element :
- JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element;
- }
-
- /**
- * Method to create the templateDetails.xml for the child template
- *
- * @return boolean true if name is not used, false otherwise
- *
- * @since 4.1.0
- */
- public function child()
- {
- $app = Factory::getApplication();
- $template = $this->getTemplate();
-
- if (!(array) $template)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
-
- return false;
- }
-
- $client = ApplicationHelper::getClientInfo($template->client_id);
- $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/templateDetails.xml');
-
- // Delete new folder if it exists
- $toPath = $this->getState('to_path');
-
- if (Folder::exists($toPath))
- {
- if (!Folder::delete($toPath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
-
- return false;
- }
- }
- else
- {
- if (!Folder::create($toPath))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
-
- return false;
- }
- }
-
- // Copy the template definition from the parent template
- if (!File::copy($fromPath, $toPath . '/templateDetails.xml'))
- {
- return false;
- }
-
- // Check manifest for additional files
- $newName = strtolower($this->getState('new_name'));
- $template = $this->getTemplate();
-
- // Edit XML file
- $xmlFile = Path::clean($this->getState('to_path') . '/templateDetails.xml');
-
- if (!File::exists($xmlFile))
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
-
- return false;
- }
-
- try
- {
- $xml = simplexml_load_string(file_get_contents($xmlFile));
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error');
-
- return false;
- }
-
- $user = Factory::getUser();
- unset($xml->languages);
- unset($xml->media);
- unset($xml->files);
- unset($xml->parent);
- unset($xml->inheritable);
-
- // Remove the update parts
- unset($xml->update);
- unset($xml->updateservers);
-
- if (isset($xml->creationDate))
- {
- $xml->creationDate = (new Date('now'))->format('F Y');
- }
- else
- {
- $xml->addChild('creationDate', (new Date('now'))->format('F Y'));
- }
-
- if (isset($xml->author))
- {
- $xml->author = $user->name;
- }
- else
- {
- $xml->addChild('author', $user->name);
- }
-
- if (isset($xml->authorEmail))
- {
- $xml->authorEmail = $user->email;
- }
- else
- {
- $xml->addChild('authorEmail', $user->email);
- }
-
- $files = $xml->addChild('files');
- $files->addChild('filename', 'templateDetails.xml');
-
- // Media folder
- $media = $xml->addChild('media');
- $media->addAttribute('folder', 'media');
- $media->addAttribute('destination', 'templates/' . ($template->client_id === 0 ? 'site/' : 'administrator/') . $template->element . '_' . $newName);
- $media->addChild('folder', 'css');
- $media->addChild('folder', 'js');
- $media->addChild('folder', 'images');
- $media->addChild('folder', 'html');
- $media->addChild('folder', 'scss');
-
- $xml->name = $template->element . '_' . $newName;
- $xml->inheritable = 0;
- $files = $xml->addChild('parent', $template->element);
-
- $dom = new \DOMDocument;
- $dom->preserveWhiteSpace = false;
- $dom->formatOutput = true;
- $dom->loadXML($xml->asXML());
-
- $result = File::write($xmlFile, $dom->saveXML());
-
- if (!$result)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
-
- return false;
- }
-
- // Create an empty media folder structure
- if (!Folder::create($toPath . '/media')
- || !Folder::create($toPath . '/media/css')
- || !Folder::create($toPath . '/media/js')
- || !Folder::create($toPath . '/media/images')
- || !Folder::create($toPath . '/media/html/tinymce')
- || !Folder::create($toPath . '/media/scss'))
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Method to get the parent template existing styles
- *
- * @return array array of id,titles of the styles
- *
- * @since 4.1.3
- */
- public function getAllTemplateStyles()
- {
- $template = $this->getTemplate();
-
- if (empty($template->xmldata->inheritable))
- {
- return [];
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName(['id', 'title']))
- ->from($db->quoteName('#__template_styles'))
- ->where($db->quoteName('client_id') . ' = :client_id', 'AND')
- ->where($db->quoteName('template') . ' = :template')
- ->orWhere($db->quoteName('parent') . ' = :parent')
- ->bind(':client_id', $template->client_id, ParameterType::INTEGER)
- ->bind(':template', $template->element)
- ->bind(':parent', $template->element);
-
- $db->setQuery($query);
-
- return $db->loadObjectList();
- }
-
- /**
- * Method to copy selected styles to the child template
- *
- * @return boolean true if name is not used, false otherwise
- *
- * @since 4.1.3
- */
- public function copyStyles()
- {
- $app = Factory::getApplication();
- $template = $this->getTemplate();
- $newName = strtolower($this->getState('new_name'));
- $applyStyles = $this->getState('stylesToCopy');
-
- // Get a db connection.
- $db = $this->getDatabase();
-
- // Create a new query object.
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName(['title', 'params']))
- ->from($db->quoteName('#__template_styles'))
- ->whereIn($db->quoteName('id'), ArrayHelper::toInteger($applyStyles));
- // Reset the query using our newly populated query object.
- $db->setQuery($query);
-
- try
- {
- $parentStyle = $db->loadObjectList();
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'), 'error');
-
- return false;
- }
-
- foreach ($parentStyle as $style)
- {
- $query = $db->getQuery(true);
- $styleName = Text::sprintf('COM_TEMPLATES_COPY_CHILD_TEMPLATE_STYLES', ucfirst($template->element . '_' . $newName), $style->title);
-
- // Insert columns and values
- $columns = ['id', 'template', 'client_id', 'home', 'title', 'inheritable', 'parent', 'params'];
- $values = [0, $db->quote($template->element . '_' . $newName), (int) $template->client_id, $db->quote('0'), $db->quote($styleName), 0, $db->quote($template->element), $db->quote($style->params)];
-
- $query
- ->insert($db->quoteName('#__template_styles'))
- ->columns($db->quoteName($columns))
- ->values(implode(',', $values));
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\Exception $e)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error');
-
- return false;
- }
- }
-
- return true;
- }
+ /**
+ * The information in a template
+ *
+ * @var \stdClass
+ * @since 1.6
+ */
+ protected $template = null;
+
+ /**
+ * The path to the template
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $element = null;
+
+ /**
+ * The path to the static assets
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected $mediaElement = null;
+
+ /**
+ * Internal method to get file properties.
+ *
+ * @param string $path The base path.
+ * @param string $name The file name.
+ *
+ * @return object
+ *
+ * @since 1.6
+ */
+ protected function getFile($path, $name)
+ {
+ $temp = new \stdClass();
+
+ if ($this->getTemplate()) {
+ $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $path);
+ $path = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $path);
+ $temp->name = $name;
+ $temp->id = urlencode(base64_encode(str_replace('\\', '//', $path)));
+
+ return $temp;
+ }
+ }
+
+ /**
+ * Method to store file information.
+ *
+ * @param string $path The base path.
+ * @param string $name The file name.
+ * @param stdClass $template The std class object of template.
+ *
+ * @return object stdClass object.
+ *
+ * @since 4.0.0
+ */
+ protected function storeFileInfo($path, $name, $template)
+ {
+ $temp = new \stdClass();
+ $temp->id = base64_encode($path . $name);
+ $temp->client = $template->client_id;
+ $temp->template = $template->element;
+ $temp->extension_id = $template->extension_id;
+
+ if ($coreFile = $this->getCoreFile($path . $name, $template->client_id)) {
+ $temp->coreFile = md5_file($coreFile);
+ } else {
+ $temp->coreFile = null;
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Method to get all template list.
+ *
+ * @return object stdClass object
+ *
+ * @since 4.0.0
+ */
+ public function getTemplateList()
+ {
+ // Get a db connection.
+ $db = $this->getDatabase();
+
+ // Create a new query object.
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.extension_id, a.name, a.element, a.client_id'
+ )
+ );
+
+ $query->from($db->quoteName('#__extensions', 'a'))
+ ->where($db->quoteName('a.enabled') . ' = 1')
+ ->where($db->quoteName('a.type') . ' = ' . $db->quote('template'));
+
+ // Reset the query.
+ $db->setQuery($query);
+
+ // Load the results as a list of stdClass objects.
+ $results = $db->loadObjectList();
+
+ return $results;
+ }
+
+ /**
+ * Method to get all updated file list.
+ *
+ * @param boolean $state The optional parameter if you want unchecked list.
+ * @param boolean $all The optional parameter if you want all list.
+ * @param boolean $cleanup The optional parameter if you want to clean record which is no more required.
+ *
+ * @return object stdClass object
+ *
+ * @since 4.0.0
+ */
+ public function getUpdatedList($state = false, $all = false, $cleanup = false)
+ {
+ // Get a db connection.
+ $db = $this->getDatabase();
+
+ // Create a new query object.
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.template, a.hash_id, a.extension_id, a.state, a.action, a.client_id, a.created_date, a.modified_date'
+ )
+ );
+
+ $template = $this->getTemplate();
+
+ $query->from($db->quoteName('#__template_overrides', 'a'));
+
+ if (!$all) {
+ $teid = (int) $template->extension_id;
+ $query->where($db->quoteName('extension_id') . ' = :teid')
+ ->bind(':teid', $teid, ParameterType::INTEGER);
+ }
+
+ if ($state) {
+ $query->where($db->quoteName('state') . ' = 0');
+ }
+
+ $query->order($db->quoteName('a.modified_date') . ' DESC');
+
+ // Reset the query.
+ $db->setQuery($query);
+
+ // Load the results as a list of stdClass objects.
+ $pks = $db->loadObjectList();
+
+ if ($state) {
+ return $pks;
+ }
+
+ $results = array();
+
+ foreach ($pks as $pk) {
+ $client = ApplicationHelper::getClientInfo($pk->client_id);
+ $path = Path::clean($client->path . '/templates/' . $pk->template . base64_decode($pk->hash_id));
+
+ if (file_exists($path)) {
+ $results[] = $pk;
+ } elseif ($cleanup) {
+ $cleanupIds = array();
+ $cleanupIds[] = $pk->hash_id;
+ $this->publish($cleanupIds, -3, $pk->extension_id);
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Method to get a list of all the core files of override files.
+ *
+ * @return array An array of all core files.
+ *
+ * @since 4.0.0
+ */
+ public function getCoreList()
+ {
+ // Get list of all templates
+ $templates = $this->getTemplateList();
+
+ // Initialize the array variable to store core file list.
+ $this->coreFileList = array();
+
+ $app = Factory::getApplication();
+
+ foreach ($templates as $template) {
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $element = Path::clean($client->path . '/templates/' . $template->element . '/');
+ $path = Path::clean($element . 'html/');
+
+ if (is_dir($path)) {
+ $this->prepareCoreFiles($path, $element, $template);
+ }
+ }
+
+ // Sort list of stdClass array.
+ usort(
+ $this->coreFileList,
+ function ($a, $b) {
+ return strcmp($a->id, $b->id);
+ }
+ );
+
+ return $this->coreFileList;
+ }
+
+ /**
+ * Prepare core files.
+ *
+ * @param string $dir The path of the directory to scan.
+ * @param string $element The path of the template element.
+ * @param \stdClass $template The stdClass object of template.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function prepareCoreFiles($dir, $element, $template)
+ {
+ $dirFiles = scandir($dir);
+
+ foreach ($dirFiles as $key => $value) {
+ if (in_array($value, array('.', '..', 'node_modules'))) {
+ continue;
+ }
+
+ if (is_dir($dir . $value)) {
+ $relativePath = str_replace($element, '', $dir . $value);
+ $this->prepareCoreFiles($dir . $value . '/', $element, $template);
+ } else {
+ $ext = pathinfo($dir . $value, PATHINFO_EXTENSION);
+ $allowedFormat = $this->checkFormat($ext);
+
+ if ($allowedFormat === true) {
+ $relativePath = str_replace($element, '', $dir);
+ $info = $this->storeFileInfo('/' . $relativePath, $value, $template);
+
+ if ($info) {
+ $this->coreFileList[] = $info;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to update status of list.
+ *
+ * @param array $ids The base path.
+ * @param array $value The file name.
+ * @param integer $exid The template extension id.
+ *
+ * @return integer Number of files changed.
+ *
+ * @since 4.0.0
+ */
+ public function publish($ids, $value, $exid)
+ {
+ $db = $this->getDatabase();
+
+ foreach ($ids as $id) {
+ if ($value === -3) {
+ $deleteQuery = $db->getQuery(true)
+ ->delete($db->quoteName('#__template_overrides'))
+ ->where($db->quoteName('hash_id') . ' = :hashid')
+ ->where($db->quoteName('extension_id') . ' = :exid')
+ ->bind(':hashid', $id)
+ ->bind(':exid', $exid, ParameterType::INTEGER);
+
+ try {
+ // Set the query using our newly populated query object and execute it.
+ $db->setQuery($deleteQuery);
+ $result = $db->execute();
+ } catch (\RuntimeException $e) {
+ return $e;
+ }
+ } elseif ($value === 1 || $value === 0) {
+ $updateQuery = $db->getQuery(true)
+ ->update($db->quoteName('#__template_overrides'))
+ ->set($db->quoteName('state') . ' = :state')
+ ->where($db->quoteName('hash_id') . ' = :hashid')
+ ->where($db->quoteName('extension_id') . ' = :exid')
+ ->bind(':state', $value, ParameterType::INTEGER)
+ ->bind(':hashid', $id)
+ ->bind(':exid', $exid, ParameterType::INTEGER);
+
+ try {
+ // Set the query using our newly populated query object and execute it.
+ $db->setQuery($updateQuery);
+ $result = $db->execute();
+ } catch (\RuntimeException $e) {
+ return $e;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a list of all the files to edit in a template.
+ *
+ * @return array A nested array of relevant files.
+ *
+ * @since 1.6
+ */
+ public function getFiles()
+ {
+ $result = array();
+
+ if ($template = $this->getTemplate()) {
+ $app = Factory::getApplication();
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $path = Path::clean($client->path . '/templates/' . $template->element . '/');
+ $lang = Factory::getLanguage();
+
+ // Load the core and/or local language file(s).
+ $lang->load('tpl_' . $template->element, $client->path)
+ || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path))
+ || $lang->load('tpl_' . $template->element, $client->path . '/templates/' . $template->element)
+ || (!empty($template->xmldata->parent) && $lang->load('tpl_' . $template->xmldata->parent, $client->path . '/templates/' . $template->xmldata->parent));
+ $this->element = $path;
+
+ if (!is_writable($path)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error');
+ }
+
+ if (is_dir($path)) {
+ $result = $this->getDirectoryTree($path);
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_TEMPLATE_FOLDER_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ // Clean up override history
+ $this->getUpdatedList(false, true, true);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the directory tree.
+ *
+ * @param string $dir The path of the directory to scan
+ *
+ * @return array
+ *
+ * @since 3.2
+ */
+ public function getDirectoryTree($dir)
+ {
+ $result = array();
+
+ $dirFiles = scandir($dir);
+
+ foreach ($dirFiles as $key => $value) {
+ if (!in_array($value, array('.', '..', 'node_modules'))) {
+ if (is_dir($dir . $value)) {
+ $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value);
+ $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath);
+ $result[str_replace('\\', '//', $relativePath)] = $this->getDirectoryTree($dir . $value . '/');
+ } else {
+ $ext = pathinfo($dir . $value, PATHINFO_EXTENSION);
+ $allowedFormat = $this->checkFormat($ext);
+
+ if ($allowedFormat == true) {
+ $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? 'site' : 'administrator') . DIRECTORY_SEPARATOR . $this->template->element, '', $dir . $value);
+ $relativePath = str_replace(JPATH_ROOT . DIRECTORY_SEPARATOR . ($this->template->client_id === 0 ? '' : 'administrator' . DIRECTORY_SEPARATOR) . 'templates' . DIRECTORY_SEPARATOR . $this->template->element, '', $relativePath);
+ $result[] = $this->getFile($relativePath, $value);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the core file of override file
+ *
+ * @param string $file Override file
+ * @param integer $client_id Client Id
+ *
+ * @return string $corefile The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
+ *
+ * @since 4.0.0
+ */
+ public function getCoreFile($file, $client_id)
+ {
+ $app = Factory::getApplication();
+ $filePath = Path::clean($file);
+ $explodeArray = explode(DIRECTORY_SEPARATOR, $filePath);
+
+ // Only allow html/ folder
+ if ($explodeArray['1'] !== 'html') {
+ return false;
+ }
+
+ $fileName = basename($filePath);
+ $type = $explodeArray['2'];
+ $client = ApplicationHelper::getClientInfo($client_id);
+
+ $componentPath = Path::clean($client->path . '/components/');
+ $modulePath = Path::clean($client->path . '/modules/');
+ $layoutPath = Path::clean(JPATH_ROOT . '/layouts/');
+
+ // For modules
+ if (stristr($type, 'mod_') !== false) {
+ $folder = $explodeArray['2'];
+ $htmlPath = Path::clean($modulePath . $folder . '/tmpl/');
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ } elseif (stristr($type, 'com_') !== false) {
+ // For components
+ $folder = $explodeArray['2'];
+ $subFolder = $explodeArray['3'];
+ $fileName = $this->getSafeName($fileName);
+
+ // The new scheme, if a view has a tmpl folder
+ $newHtmlPath = Path::clean($componentPath . $folder . '/tmpl/' . $subFolder . '/');
+
+ if (!$coreFile = Path::find($newHtmlPath, $fileName)) {
+ // The old scheme, the views are directly in the component/tmpl folder
+ $oldHtmlPath = Path::clean($componentPath . $folder . '/views/' . $subFolder . '/tmpl/');
+ $coreFile = Path::find($oldHtmlPath, $fileName);
+
+ return $coreFile;
+ }
+
+ return $coreFile;
+ } elseif (stristr($type, 'layouts') !== false) {
+ // For Layouts
+ $subtype = $explodeArray['3'];
+
+ if (stristr($subtype, 'com_')) {
+ $folder = $explodeArray['3'];
+ $subFolder = array_slice($explodeArray, 4, -1);
+ $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
+ $htmlPath = Path::clean($componentPath . $folder . '/layouts/' . $subFolder);
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ } elseif (stristr($subtype, 'joomla') || stristr($subtype, 'libraries') || stristr($subtype, 'plugins')) {
+ $subFolder = array_slice($explodeArray, 3, -1);
+ $subFolder = implode(DIRECTORY_SEPARATOR, $subFolder);
+ $htmlPath = Path::clean($layoutPath . $subFolder);
+ $fileName = $this->getSafeName($fileName);
+ $coreFile = Path::find($htmlPath, $fileName);
+
+ return $coreFile;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a safe file name for the given name.
+ *
+ * @param string $name The filename
+ *
+ * @return string $fileName The filtered name without Date
+ *
+ * @since 4.0.0
+ */
+ private function getSafeName($name)
+ {
+ if (strpos($name, '-') !== false && preg_match('/[0-9]/', $name)) {
+ // Get the extension
+ $extension = File::getExt($name);
+
+ // Remove ( Date ) from file
+ $explodeArray = explode('-', $name);
+ $size = count($explodeArray);
+ $date = $explodeArray[$size - 2] . '-' . str_replace('.' . $extension, '', $explodeArray[$size - 1]);
+
+ if ($this->validateDate($date)) {
+ $nameWithoutExtension = implode('-', array_slice($explodeArray, 0, -2));
+
+ // Filtered name
+ $name = $nameWithoutExtension . '.' . $extension;
+ }
+ }
+
+ return $name;
+ }
+
+ /**
+ * Validate Date in file name.
+ *
+ * @param string $date Date to validate.
+ *
+ * @return boolean Return true if date is valid and false if not.
+ *
+ * @since 4.0.0
+ */
+ private function validateDate($date)
+ {
+ $format = 'Ymd-His';
+ $valid = Date::createFromFormat($format, $date);
+
+ return $valid && $valid->format($format) === $date;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+ $this->setState('extension.id', $pk);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_templates');
+ $this->setState('params', $params);
+ }
+
+ /**
+ * Method to get the template information.
+ *
+ * @return mixed Object if successful, false if not and internal error is set.
+ *
+ * @since 1.6
+ */
+ public function &getTemplate()
+ {
+ if (empty($this->template)) {
+ $pk = (int) $this->getState('extension.id');
+ $db = $this->getDatabase();
+ $app = Factory::getApplication();
+
+ // Get the template information.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['extension_id', 'client_id', 'element', 'name', 'manifest_cache']))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('extension_id') . ' = :pk')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('template'))
+ ->bind(':pk', $pk, ParameterType::INTEGER);
+ $db->setQuery($query);
+
+ try {
+ $result = $db->loadObject();
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'warning');
+ $this->template = false;
+
+ return false;
+ }
+
+ if (empty($result)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'error');
+ $this->template = false;
+ } else {
+ $this->template = $result;
+
+ // Client ID is not always an integer, so enforce here
+ $this->template->client_id = (int) $this->template->client_id;
+
+ if (!isset($this->template->xmldata)) {
+ $this->template->xmldata = TemplatesHelper::parseXMLTemplateFile($this->template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $this->template->name);
+ }
+ }
+ }
+
+ return $this->template;
+ }
+
+ /**
+ * Method to check if new template name already exists
+ *
+ * @return boolean true if name is not used, false otherwise
+ *
+ * @since 2.5
+ */
+ public function checkNewName()
+ {
+ $db = $this->getDatabase();
+ $name = $this->getState('new_name');
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('name') . ' = :name')
+ ->bind(':name', $name);
+ $db->setQuery($query);
+
+ return ($db->loadResult() == 0);
+ }
+
+ /**
+ * Method to check if new template name already exists
+ *
+ * @return string name of current template
+ *
+ * @since 2.5
+ */
+ public function getFromName()
+ {
+ return $this->getTemplate()->element;
+ }
+
+ /**
+ * Method to check if new template name already exists
+ *
+ * @return boolean true if name is not used, false otherwise
+ *
+ * @since 2.5
+ */
+ public function copy()
+ {
+ $app = Factory::getApplication();
+
+ if ($template = $this->getTemplate()) {
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/');
+
+ // Delete new folder if it exists
+ $toPath = $this->getState('to_path');
+
+ if (Folder::exists($toPath)) {
+ if (!Folder::delete($toPath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
+
+ return false;
+ }
+ }
+
+ // Copy all files from $fromName template to $newName folder
+ if (!Folder::copy($fromPath, $toPath)) {
+ return false;
+ }
+
+ // Check manifest for additional files
+ $manifest = simplexml_load_file($toPath . '/templateDetails.xml');
+
+ // Copy language files from global folder
+ if ($languages = $manifest->languages) {
+ $folder = (string) $languages->attributes()->folder;
+ $languageFiles = $languages->language;
+
+ Folder::create($toPath . '/' . $folder . '/' . $languageFiles->attributes()->tag);
+
+ foreach ($languageFiles as $languageFile) {
+ $src = Path::clean($client->path . '/language/' . $languageFile);
+ $dst = Path::clean($toPath . '/' . $folder . '/' . $languageFile);
+
+ if (File::exists($src)) {
+ File::copy($src, $dst);
+ }
+ }
+ }
+
+ // Copy media files
+ if ($media = $manifest->media) {
+ $folder = (string) $media->attributes()->folder;
+ $destination = (string) $media->attributes()->destination;
+
+ Folder::copy(JPATH_SITE . '/media/' . $destination, $toPath . '/' . $folder);
+ }
+
+ // Adjust to new template name
+ if (!$this->fixTemplateName()) {
+ return false;
+ }
+
+ return true;
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
+
+ return false;
+ }
+ }
+
+ /**
+ * Method to delete tmp folder
+ *
+ * @return boolean true if delete successful, false otherwise
+ *
+ * @since 2.5
+ */
+ public function cleanup()
+ {
+ // Clear installation messages
+ $app = Factory::getApplication();
+ $app->setUserState('com_installer.message', '');
+ $app->setUserState('com_installer.extension_message', '');
+
+ // Delete temporary directory
+ return Folder::delete($this->getState('to_path'));
+ }
+
+ /**
+ * Method to rename the template in the XML files and rename the language files
+ *
+ * @return boolean true if successful, false otherwise
+ *
+ * @since 2.5
+ */
+ protected function fixTemplateName()
+ {
+ // Rename Language files
+ // Get list of language files
+ $result = true;
+ $files = Folder::files($this->getState('to_path'), '\.ini$', true, true);
+ $newName = strtolower($this->getState('new_name'));
+ $template = $this->getTemplate();
+ $oldName = $template->element;
+ $manifest = json_decode($template->manifest_cache);
+
+ foreach ($files as $file) {
+ $newFile = '/' . str_replace($oldName, $newName, basename($file));
+ $result = File::move($file, dirname($file) . $newFile) && $result;
+ }
+
+ // Edit XML file
+ $xmlFile = $this->getState('to_path') . '/templateDetails.xml';
+
+ if (File::exists($xmlFile)) {
+ $contents = file_get_contents($xmlFile);
+ $pattern[] = '#\s*' . $manifest->name . '\s* #i';
+ $replace[] = '' . $newName . ' ';
+ $pattern[] = '##';
+ $replace[] = '';
+ $pattern[] = '##';
+ $replace[] = '';
+ $contents = preg_replace($pattern, $replace, $contents);
+ $result = File::write($xmlFile, $contents) && $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ $app = Factory::getApplication();
+
+ // Codemirror or Editor None should be enabled
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from('#__extensions as a')
+ ->where(
+ '(a.name =' . $db->quote('plg_editors_codemirror') .
+ ' AND a.enabled = 1) OR (a.name =' .
+ $db->quote('plg_editors_none') .
+ ' AND a.enabled = 1)'
+ );
+ $db->setQuery($query);
+ $state = $db->loadResult();
+
+ if ((int) $state < 1) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EDITOR_DISABLED'), 'warning');
+ }
+
+ // Get the form.
+ $form = $this->loadForm('com_templates.source', 'source', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ */
+ protected function loadFormData()
+ {
+ $data = $this->getSource();
+
+ $this->preprocessData('com_templates.source', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function &getSource()
+ {
+ $app = Factory::getApplication();
+ $item = new \stdClass();
+
+ if (!$this->template) {
+ $this->getTemplate();
+ }
+
+ if ($this->template) {
+ $input = Factory::getApplication()->input;
+ $fileName = base64_decode($input->get('file'));
+ $fileName = str_replace('//', '/', $fileName);
+ $isMedia = $input->getInt('isMedia', 0);
+
+ $fileName = $isMedia ? Path::clean(JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName)
+ : Path::clean(JPATH_ROOT . ($this->template->client_id === 0 ? '' : '/administrator') . '/templates/' . $this->template->element . $fileName);
+
+ try {
+ $filePath = Path::check($fileName);
+ } catch (\Exception $e) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
+
+ return;
+ }
+
+ if (file_exists($filePath)) {
+ $item->extension_id = $this->getState('extension.id');
+ $item->filename = Path::clean($fileName);
+ $item->source = file_get_contents($filePath);
+ $item->filePath = Path::clean($filePath);
+ $ds = DIRECTORY_SEPARATOR;
+ $cleanFileName = str_replace(JPATH_ROOT . ($this->template->client_id === 1 ? $ds . 'administrator' . $ds : $ds) . 'templates' . $ds . $this->template->element, '', $fileName);
+
+ if ($coreFile = $this->getCoreFile($cleanFileName, $this->template->client_id)) {
+ $item->coreFile = $coreFile;
+ $item->core = file_get_contents($coreFile);
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_FOUND'), 'error');
+ }
+ }
+
+ return $item;
+ }
+
+ /**
+ * Method to store the source file contents.
+ *
+ * @param array $data The source data to save.
+ *
+ * @return boolean True on success, false otherwise and internal error set.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ // Get the template.
+ $template = $this->getTemplate();
+
+ if (empty($template)) {
+ return false;
+ }
+
+ $app = Factory::getApplication();
+ $fileName = base64_decode($app->input->get('file'));
+ $isMedia = $app->input->getInt('isMedia', 0);
+ $fileName = $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . $fileName :
+ JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element . $fileName;
+
+ $filePath = Path::clean($fileName);
+
+ // Include the extension plugins for the save events.
+ PluginHelper::importPlugin('extension');
+
+ $user = get_current_user();
+ chown($filePath, $user);
+ Path::setPermissions($filePath, '0644');
+
+ // Try to make the template file writable.
+ if (!is_writable($filePath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_SOURCE_FILE_NOT_WRITABLE'), 'warning');
+ $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_PERMISSIONS', Path::getPermissions($filePath)), 'warning');
+
+ if (!Path::isOwner($filePath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_CHECK_FILE_OWNERSHIP'), 'warning');
+ }
+
+ return false;
+ }
+
+ // Make sure EOL is Unix
+ $data['source'] = str_replace(array("\r\n", "\r"), "\n", $data['source']);
+
+ // If the asset file for the template ensure we have valid template so we don't instantly destroy it
+ if ($fileName === '/joomla.asset.json' && json_decode($data['source']) === null) {
+ $this->setError(Text::_('COM_TEMPLATES_ERROR_ASSET_FILE_INVALID_JSON'));
+
+ return false;
+ }
+
+ $return = File::write($filePath, $data['source']);
+
+ if (!$return) {
+ $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_ERROR_FAILED_TO_SAVE_FILENAME', $fileName), 'error');
+
+ return false;
+ }
+
+ // Get the extension of the changed file.
+ $explodeArray = explode('.', $fileName);
+ $ext = end($explodeArray);
+
+ if ($ext == 'less') {
+ $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_COMPILE_LESS', $fileName));
+ }
+
+ return true;
+ }
+
+ /**
+ * Get overrides folder.
+ *
+ * @param string $name The name of override.
+ * @param string $path Location of override.
+ *
+ * @return object containing override name and path.
+ *
+ * @since 3.2
+ */
+ public function getOverridesFolder($name, $path)
+ {
+ $folder = new \stdClass();
+ $folder->name = $name;
+ $folder->path = base64_encode($path . $name);
+
+ return $folder;
+ }
+
+ /**
+ * Get a list of overrides.
+ *
+ * @return array containing overrides.
+ *
+ * @since 3.2
+ */
+ public function getOverridesList()
+ {
+ if ($template = $this->getTemplate()) {
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $componentPath = Path::clean($client->path . '/components/');
+ $modulePath = Path::clean($client->path . '/modules/');
+ $pluginPath = Path::clean(JPATH_ROOT . '/plugins/');
+ $layoutPath = Path::clean(JPATH_ROOT . '/layouts/');
+ $components = Folder::folders($componentPath);
+
+ foreach ($components as $component) {
+ // Collect the folders with views
+ $folders = Folder::folders($componentPath . '/' . $component, '^view[s]?$', false, true);
+ $folders = array_merge($folders, Folder::folders($componentPath . '/' . $component, '^tmpl?$', false, true));
+
+ if (!$folders) {
+ continue;
+ }
+
+ foreach ($folders as $folder) {
+ // The subfolders are views
+ $views = Folder::folders($folder);
+
+ foreach ($views as $view) {
+ // The old scheme, if a view has a tmpl folder
+ $path = $folder . '/' . $view . '/tmpl';
+
+ // The new scheme, the views are directly in the component/tmpl folder
+ if (!is_dir($path) && substr($folder, -4) == 'tmpl') {
+ $path = $folder . '/' . $view;
+ }
+
+ // Check if the folder exists
+ if (!is_dir($path)) {
+ continue;
+ }
+
+ $result['components'][$component][] = $this->getOverridesFolder($view, Path::clean($folder . '/'));
+ }
+ }
+ }
+
+ foreach (Folder::folders($pluginPath) as $pluginGroup) {
+ foreach (Folder::folders($pluginPath . '/' . $pluginGroup) as $plugin) {
+ if (file_exists($pluginPath . '/' . $pluginGroup . '/' . $plugin . '/tmpl/')) {
+ $pluginLayoutPath = Path::clean($pluginPath . '/' . $pluginGroup . '/');
+ $result['plugins'][$pluginGroup][] = $this->getOverridesFolder($plugin, $pluginLayoutPath);
+ }
+ }
+ }
+
+ $modules = Folder::folders($modulePath);
+
+ foreach ($modules as $module) {
+ $result['modules'][] = $this->getOverridesFolder($module, $modulePath);
+ }
+
+ $layoutFolders = Folder::folders($layoutPath);
+
+ foreach ($layoutFolders as $layoutFolder) {
+ $layoutFolderPath = Path::clean($layoutPath . '/' . $layoutFolder . '/');
+ $layouts = Folder::folders($layoutFolderPath);
+
+ foreach ($layouts as $layout) {
+ $result['layouts'][$layoutFolder][] = $this->getOverridesFolder($layout, $layoutFolderPath);
+ }
+ }
+
+ // Check for layouts in component folders
+ foreach ($components as $component) {
+ if (file_exists($componentPath . '/' . $component . '/layouts/')) {
+ $componentLayoutPath = Path::clean($componentPath . '/' . $component . '/layouts/');
+
+ if ($componentLayoutPath) {
+ $layouts = Folder::folders($componentLayoutPath);
+
+ foreach ($layouts as $layout) {
+ $result['layouts'][$component][] = $this->getOverridesFolder($layout, $componentLayoutPath);
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($result)) {
+ return $result;
+ }
+ }
+
+ /**
+ * Create overrides.
+ *
+ * @param string $override The override location.
+ *
+ * @return boolean true if override creation is successful, false otherwise
+ *
+ * @since 3.2
+ */
+ public function createOverride($override)
+ {
+ if ($template = $this->getTemplate()) {
+ $app = Factory::getApplication();
+ $explodeArray = explode(DIRECTORY_SEPARATOR, $override);
+ $name = end($explodeArray);
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+
+ if (stristr($name, 'mod_') != false) {
+ $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $name);
+ } elseif (stristr($override, 'com_') != false) {
+ $size = count($explodeArray);
+
+ $url = Path::clean($explodeArray[$size - 3] . '/' . $explodeArray[$size - 1]);
+
+ if ($explodeArray[$size - 2] == 'layouts') {
+ $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $url);
+ } else {
+ $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $url);
+ }
+ } elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0) {
+ $size = count($explodeArray);
+ $layoutPath = Path::clean('plg_' . $explodeArray[$size - 2] . '_' . $explodeArray[$size - 1]);
+ $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $layoutPath);
+ } else {
+ $layoutPath = implode('/', array_slice($explodeArray, -2));
+ $htmlPath = Path::clean($client->path . '/templates/' . $template->element . '/html/layouts/' . $layoutPath);
+ }
+
+ // Check Html folder, create if not exist
+ if (!Folder::exists($htmlPath)) {
+ if (!Folder::create($htmlPath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_ERROR'), 'error');
+
+ return false;
+ }
+ }
+
+ if (stristr($name, 'mod_') != false) {
+ $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath);
+ } elseif (stristr($override, 'com_') != false && stristr($override, 'layouts') == false) {
+ $path = $override . '/tmpl';
+
+ // View can also be in the top level folder
+ if (!is_dir($path)) {
+ $path = $override;
+ }
+
+ $return = $this->createTemplateOverride(Path::clean($path), $htmlPath);
+ } elseif (stripos($override, Path::clean(JPATH_ROOT . '/plugins/')) === 0) {
+ $return = $this->createTemplateOverride(Path::clean($override . '/tmpl'), $htmlPath);
+ } else {
+ $return = $this->createTemplateOverride($override, $htmlPath);
+ }
+
+ if ($return) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_CREATED') . str_replace(JPATH_ROOT, '', $htmlPath));
+
+ return true;
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_OVERRIDE_FAILED'), 'error');
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Create override folder & file
+ *
+ * @param string $overridePath The override location
+ * @param string $htmlPath The html location
+ *
+ * @return boolean True on success. False otherwise.
+ */
+ public function createTemplateOverride($overridePath, $htmlPath)
+ {
+ $return = false;
+
+ if (empty($overridePath) || empty($htmlPath)) {
+ return $return;
+ }
+
+ // Get list of template folders
+ $folders = Folder::folders($overridePath, null, true, true);
+
+ if (!empty($folders)) {
+ foreach ($folders as $folder) {
+ $htmlFolder = $htmlPath . str_replace($overridePath, '', $folder);
+
+ if (!Folder::exists($htmlFolder)) {
+ Folder::create($htmlFolder);
+ }
+ }
+ }
+
+ // Get list of template files (Only get *.php file for template file)
+ $files = Folder::files($overridePath, '.php', true, true);
+
+ if (empty($files)) {
+ return true;
+ }
+
+ foreach ($files as $file) {
+ $overrideFilePath = str_replace($overridePath, '', $file);
+ $htmlFilePath = $htmlPath . $overrideFilePath;
+
+ if (File::exists($htmlFilePath)) {
+ // Generate new unique file name base on current time
+ $today = Factory::getDate();
+ $htmlFilePath = File::stripExt($htmlFilePath) . '-' . $today->format('Ymd-His') . '.' . File::getExt($htmlFilePath);
+ }
+
+ $return = File::copy($file, $htmlFilePath, '', true);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Delete a particular file.
+ *
+ * @param string $file The relative location of the file.
+ *
+ * @return boolean True if file deletion is successful, false otherwise
+ *
+ * @since 3.2
+ */
+ public function deleteFile($file)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $filePath = $this->getBasePath() . urldecode(base64_decode($file));
+
+ $return = File::delete($filePath);
+
+ if (!$return) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_DELETE_ERROR'), 'error');
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Create new file.
+ *
+ * @param string $name The name of file.
+ * @param string $type The extension of the file.
+ * @param string $location Location for the new file.
+ *
+ * @return boolean true if file created successfully, false otherwise
+ *
+ * @since 3.2
+ */
+ public function createFile($name, $type, $location)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $base = $this->getBasePath();
+
+ if (file_exists(Path::clean($base . '/' . $location . '/' . $name . '.' . $type))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
+
+ return false;
+ }
+
+ if (!fopen(Path::clean($base . '/' . $location . '/' . $name . '.' . $type), 'x')) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_CREATE_ERROR'), 'error');
+
+ return false;
+ }
+
+ // Check if the format is allowed and will be showed in the backend
+ $check = $this->checkFormat($type);
+
+ // Add a message if we are not allowed to show this file in the backend.
+ if (!$check) {
+ $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_WARNING_FORMAT_WILL_NOT_BE_VISIBLE', $type), 'warning');
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Upload new file.
+ *
+ * @param array $file The uploaded file array.
+ * @param string $location Location for the new file.
+ *
+ * @return boolean True if file uploaded successfully, false otherwise
+ *
+ * @since 3.2
+ */
+ public function uploadFile($file, $location)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = $this->getBasePath();
+ $fileName = File::makeSafe($file['name']);
+
+ $err = null;
+
+ if (!TemplateHelper::canUpload($file, $err)) {
+ // Can't upload the file
+ return false;
+ }
+
+ if (file_exists(Path::clean($path . '/' . $location . '/' . $file['name']))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
+
+ return false;
+ }
+
+ if (!File::upload($file['tmp_name'], Path::clean($path . '/' . $location . '/' . $fileName))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_UPLOAD_ERROR'), 'error');
+
+ return false;
+ }
+
+ $url = Path::clean($location . '/' . $fileName);
+
+ return $url;
+ }
+ }
+
+ /**
+ * Create new folder.
+ *
+ * @param string $name The name of the new folder.
+ * @param string $location Location for the new folder.
+ *
+ * @return boolean True if override folder is created successfully, false otherwise
+ *
+ * @since 3.2
+ */
+ public function createFolder($name, $location)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = Path::clean($location . '/');
+ $base = $this->getBasePath();
+
+ if (file_exists(Path::clean($base . $path . $name))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_EXISTS'), 'error');
+
+ return false;
+ }
+
+ if (!Folder::create(Path::clean($base . $path . $name))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_CREATE_ERROR'), 'error');
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Delete a folder.
+ *
+ * @param string $location The name and location of the folder.
+ *
+ * @return boolean True if override folder is deleted successfully, false otherwise
+ *
+ * @since 3.2
+ */
+ public function deleteFolder($location)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $base = $this->getBasePath();
+ $path = Path::clean($location . '/');
+
+ if (!file_exists($base . $path)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_NOT_EXISTS'), 'error');
+
+ return false;
+ }
+
+ $return = Folder::delete($base . $path);
+
+ if (!$return) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FOLDER_DELETE_ERROR'), 'error');
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Rename a file.
+ *
+ * @param string $file The name and location of the old file
+ * @param string $name The new name of the file.
+ *
+ * @return string Encoded string containing the new file location.
+ *
+ * @since 3.2
+ */
+ public function renameFile($file, $name)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = $this->getBasePath();
+ $fileName = base64_decode($file);
+ $explodeArray = explode('.', $fileName);
+ $type = end($explodeArray);
+ $explodeArray = explode('/', $fileName);
+ $newName = str_replace(end($explodeArray), $name . '.' . $type, $fileName);
+
+ if (file_exists($path . $newName)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
+
+ return false;
+ }
+
+ if (!rename($path . $fileName, $path . $newName)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_RENAME_ERROR'), 'error');
+
+ return false;
+ }
+
+ return base64_encode($newName);
+ }
+ }
+
+ /**
+ * Get an image address, height and width.
+ *
+ * @return array an associative array containing image address, height and width.
+ *
+ * @since 3.2
+ */
+ public function getImage()
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $fileName = base64_decode($app->input->get('file'));
+ $path = $this->getBasePath();
+
+ $uri = Uri::root(false) . ltrim(str_replace(JPATH_ROOT, '', $this->getBasePath()), '/');
+
+ if (file_exists(Path::clean($path . $fileName))) {
+ $JImage = new Image(Path::clean($path . $fileName));
+ $image['address'] = $uri . $fileName;
+ $image['path'] = $fileName;
+ $image['height'] = $JImage->getHeight();
+ $image['width'] = $JImage->getWidth();
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_IMAGE_FILE_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ return $image;
+ }
+ }
+
+ /**
+ * Crop an image.
+ *
+ * @param string $file The name and location of the file
+ * @param string $w width.
+ * @param string $h height.
+ * @param string $x x-coordinate.
+ * @param string $y y-coordinate.
+ *
+ * @return boolean true if image cropped successfully, false otherwise.
+ *
+ * @since 3.2
+ */
+ public function cropImage($file, $w, $h, $x, $y)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = $this->getBasePath() . base64_decode($file);
+
+ try {
+ $image = new Image($path);
+ $properties = $image->getImageFileProperties($path);
+
+ switch ($properties->mime) {
+ case 'image/webp':
+ $imageType = \IMAGETYPE_WEBP;
+ break;
+ case 'image/png':
+ $imageType = \IMAGETYPE_PNG;
+ break;
+ case 'image/gif':
+ $imageType = \IMAGETYPE_GIF;
+ break;
+ default:
+ $imageType = \IMAGETYPE_JPEG;
+ }
+
+ $image->crop($w, $h, $x, $y, false);
+ $image->toFile($path, $imageType);
+
+ return true;
+ } catch (\Exception $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+ }
+
+ /**
+ * Resize an image.
+ *
+ * @param string $file The name and location of the file
+ * @param string $width The new width of the image.
+ * @param string $height The new height of the image.
+ *
+ * @return boolean true if image resize successful, false otherwise.
+ *
+ * @since 3.2
+ */
+ public function resizeImage($file, $width, $height)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = $this->getBasePath() . base64_decode($file);
+
+ try {
+ $image = new Image($path);
+ $properties = $image->getImageFileProperties($path);
+
+ switch ($properties->mime) {
+ case 'image/webp':
+ $imageType = \IMAGETYPE_WEBP;
+ break;
+ case 'image/png':
+ $imageType = \IMAGETYPE_PNG;
+ break;
+ case 'image/gif':
+ $imageType = \IMAGETYPE_GIF;
+ break;
+ default:
+ $imageType = \IMAGETYPE_JPEG;
+ }
+
+ $image->resize($width, $height, false, Image::SCALE_FILL);
+ $image->toFile($path, $imageType);
+
+ return true;
+ } catch (\Exception $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
+ }
+
+ /**
+ * Template preview.
+ *
+ * @return object object containing the id of the template.
+ *
+ * @since 3.2
+ */
+ public function getPreview()
+ {
+ $app = Factory::getApplication();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName(['id', 'client_id']));
+ $query->from($db->quoteName('#__template_styles'));
+ $query->where($db->quoteName('template') . ' = :template')
+ ->bind(':template', $this->template->element);
+
+ $db->setQuery($query);
+
+ try {
+ $result = $db->loadObject();
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'warning');
+ }
+
+ if (empty($result)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_EXTENSION_RECORD_NOT_FOUND'), 'warning');
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * Rename a file.
+ *
+ * @return mixed array on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function getFont()
+ {
+ if ($template = $this->getTemplate()) {
+ $app = Factory::getApplication();
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $relPath = base64_decode($app->input->get('file'));
+ $explodeArray = explode('/', $relPath);
+ $fileName = end($explodeArray);
+ $path = $this->getBasePath() . base64_decode($app->input->get('file'));
+
+ if (stristr($client->path, 'administrator') == false) {
+ $folder = '/templates/';
+ } else {
+ $folder = '/administrator/templates/';
+ }
+
+ $uri = Uri::root(true) . $folder . $template->element;
+
+ if (file_exists(Path::clean($path))) {
+ $font['address'] = $uri . $relPath;
+
+ $font['rel_path'] = $relPath;
+
+ $font['name'] = $fileName;
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ return $font;
+ }
+ }
+
+ /**
+ * Copy a file.
+ *
+ * @param string $newName The name of the copied file
+ * @param string $location The final location where the file is to be copied
+ * @param string $file The name and location of the file
+ *
+ * @return boolean true if image resize successful, false otherwise.
+ *
+ * @since 3.2
+ */
+ public function copyFile($newName, $location, $file)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $relPath = base64_decode($file);
+ $explodeArray = explode('.', $relPath);
+ $ext = end($explodeArray);
+ $path = $this->getBasePath();
+ $newPath = Path::clean($path . $location . '/' . $newName . '.' . $ext);
+
+ if (file_exists($newPath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_EXISTS'), 'error');
+
+ return false;
+ }
+
+ if (File::copy($path . $relPath, $newPath)) {
+ $app->enqueueMessage(Text::sprintf('COM_TEMPLATES_FILE_COPY_SUCCESS', $newName . '.' . $ext));
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Get the compressed files.
+ *
+ * @return array if file exists, false otherwise
+ *
+ * @since 3.2
+ */
+ public function getArchive()
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $path = $this->getBasePath() . base64_decode($app->input->get('file'));
+
+ if (file_exists(Path::clean($path))) {
+ $files = array();
+ $zip = new \ZipArchive();
+
+ if ($zip->open($path) === true) {
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $entry = $zip->getNameIndex($i);
+ $files[] = $entry;
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
+
+ return false;
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_FONT_FILE_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ return $files;
+ }
+ }
+
+ /**
+ * Extract contents of an archive file.
+ *
+ * @param string $file The name and location of the file
+ *
+ * @return boolean true if image extraction is successful, false otherwise.
+ *
+ * @since 3.2
+ */
+ public function extractArchive($file)
+ {
+ if ($this->getTemplate()) {
+ $app = Factory::getApplication();
+ $relPath = base64_decode($file);
+ $explodeArray = explode('/', $relPath);
+ $fileName = end($explodeArray);
+ $path = $this->getBasePath() . base64_decode($file);
+
+ if (file_exists(Path::clean($path . '/' . $fileName))) {
+ $zip = new \ZipArchive();
+
+ if ($zip->open(Path::clean($path . '/' . $fileName)) === true) {
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $entry = $zip->getNameIndex($i);
+
+ if (file_exists(Path::clean($path . '/' . $entry))) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_EXISTS'), 'error');
+
+ return false;
+ }
+ }
+
+ $zip->extractTo($path);
+
+ return true;
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_OPEN_FAIL'), 'error');
+
+ return false;
+ }
+ } else {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_FILE_ARCHIVE_NOT_FOUND'), 'error');
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Check if the extension is allowed and will be shown in the template manager
+ *
+ * @param string $ext The extension to check if it is allowed
+ *
+ * @return boolean true if the extension is allowed false otherwise
+ *
+ * @since 3.6.0
+ */
+ protected function checkFormat($ext)
+ {
+ if (!isset($this->allowedFormats)) {
+ $params = ComponentHelper::getParams('com_templates');
+ $imageTypes = explode(',', $params->get('image_formats'));
+ $sourceTypes = explode(',', $params->get('source_formats'));
+ $fontTypes = explode(',', $params->get('font_formats'));
+ $archiveTypes = explode(',', $params->get('compressed_formats'));
+
+ $this->allowedFormats = array_merge($imageTypes, $sourceTypes, $fontTypes, $archiveTypes);
+ $this->allowedFormats = array_map('strtolower', $this->allowedFormats);
+ }
+
+ return in_array(strtolower($ext), $this->allowedFormats);
+ }
+
+ /**
+ * Method to get a list of all the files to edit in a template's media folder.
+ *
+ * @return array A nested array of relevant files.
+ *
+ * @since 4.1.0
+ */
+ public function getMediaFiles()
+ {
+ $result = [];
+ $template = $this->getTemplate();
+
+ if (!isset($template->xmldata)) {
+ $template->xmldata = TemplatesHelper::parseXMLTemplateFile($template->client_id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
+ }
+
+ if (!isset($template->xmldata->inheritable) || (isset($template->xmldata->parent) && $template->xmldata->parent === '')) {
+ return $result;
+ }
+
+ $app = Factory::getApplication();
+ $path = Path::clean(JPATH_ROOT . '/media/templates/' . ($template->client_id === 0 ? 'site' : 'administrator') . '/' . $template->element . '/');
+ $this->mediaElement = $path;
+
+ if (!is_writable($path)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_DIRECTORY_NOT_WRITABLE'), 'error');
+ }
+
+ if (is_dir($path)) {
+ $result = $this->getDirectoryTree($path);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to resolve the base folder.
+ *
+ * @return string The absolute path for the base.
+ *
+ * @since 4.1.0
+ */
+ private function getBasePath()
+ {
+ $app = Factory::getApplication();
+ $isMedia = $app->input->getInt('isMedia', 0);
+
+ return $isMedia ? JPATH_ROOT . '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element :
+ JPATH_ROOT . '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element;
+ }
+
+ /**
+ * Method to create the templateDetails.xml for the child template
+ *
+ * @return boolean true if name is not used, false otherwise
+ *
+ * @since 4.1.0
+ */
+ public function child()
+ {
+ $app = Factory::getApplication();
+ $template = $this->getTemplate();
+
+ if (!(array) $template) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
+
+ return false;
+ }
+
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+ $fromPath = Path::clean($client->path . '/templates/' . $template->element . '/templateDetails.xml');
+
+ // Delete new folder if it exists
+ $toPath = $this->getState('to_path');
+
+ if (Folder::exists($toPath)) {
+ if (!Folder::delete($toPath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
+
+ return false;
+ }
+ } else {
+ if (!Folder::create($toPath)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
+
+ return false;
+ }
+ }
+
+ // Copy the template definition from the parent template
+ if (!File::copy($fromPath, $toPath . '/templateDetails.xml')) {
+ return false;
+ }
+
+ // Check manifest for additional files
+ $newName = strtolower($this->getState('new_name'));
+ $template = $this->getTemplate();
+
+ // Edit XML file
+ $xmlFile = Path::clean($this->getState('to_path') . '/templateDetails.xml');
+
+ if (!File::exists($xmlFile)) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_INVALID_FROM_NAME'), 'error');
+
+ return false;
+ }
+
+ try {
+ $xml = simplexml_load_string(file_get_contents($xmlFile));
+ } catch (\Exception $e) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error');
+
+ return false;
+ }
+
+ $user = Factory::getUser();
+ unset($xml->languages);
+ unset($xml->media);
+ unset($xml->files);
+ unset($xml->parent);
+ unset($xml->inheritable);
+
+ // Remove the update parts
+ unset($xml->update);
+ unset($xml->updateservers);
+
+ if (isset($xml->creationDate)) {
+ $xml->creationDate = (new Date('now'))->format('F Y');
+ } else {
+ $xml->addChild('creationDate', (new Date('now'))->format('F Y'));
+ }
+
+ if (isset($xml->author)) {
+ $xml->author = $user->name;
+ } else {
+ $xml->addChild('author', $user->name);
+ }
+
+ if (isset($xml->authorEmail)) {
+ $xml->authorEmail = $user->email;
+ } else {
+ $xml->addChild('authorEmail', $user->email);
+ }
+
+ $files = $xml->addChild('files');
+ $files->addChild('filename', 'templateDetails.xml');
+
+ // Media folder
+ $media = $xml->addChild('media');
+ $media->addAttribute('folder', 'media');
+ $media->addAttribute('destination', 'templates/' . ($template->client_id === 0 ? 'site/' : 'administrator/') . $template->element . '_' . $newName);
+ $media->addChild('folder', 'css');
+ $media->addChild('folder', 'js');
+ $media->addChild('folder', 'images');
+ $media->addChild('folder', 'html');
+ $media->addChild('folder', 'scss');
+
+ $xml->name = $template->element . '_' . $newName;
+ $xml->inheritable = 0;
+ $files = $xml->addChild('parent', $template->element);
+
+ $dom = new \DOMDocument();
+ $dom->preserveWhiteSpace = false;
+ $dom->formatOutput = true;
+ $dom->loadXML($xml->asXML());
+
+ $result = File::write($xmlFile, $dom->saveXML());
+
+ if (!$result) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_WRITE'), 'error');
+
+ return false;
+ }
+
+ // Create an empty media folder structure
+ if (
+ !Folder::create($toPath . '/media')
+ || !Folder::create($toPath . '/media/css')
+ || !Folder::create($toPath . '/media/js')
+ || !Folder::create($toPath . '/media/images')
+ || !Folder::create($toPath . '/media/html/tinymce')
+ || !Folder::create($toPath . '/media/scss')
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to get the parent template existing styles
+ *
+ * @return array array of id,titles of the styles
+ *
+ * @since 4.1.3
+ */
+ public function getAllTemplateStyles()
+ {
+ $template = $this->getTemplate();
+
+ if (empty($template->xmldata->inheritable)) {
+ return [];
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName(['id', 'title']))
+ ->from($db->quoteName('#__template_styles'))
+ ->where($db->quoteName('client_id') . ' = :client_id', 'AND')
+ ->where($db->quoteName('template') . ' = :template')
+ ->orWhere($db->quoteName('parent') . ' = :parent')
+ ->bind(':client_id', $template->client_id, ParameterType::INTEGER)
+ ->bind(':template', $template->element)
+ ->bind(':parent', $template->element);
+
+ $db->setQuery($query);
+
+ return $db->loadObjectList();
+ }
+
+ /**
+ * Method to copy selected styles to the child template
+ *
+ * @return boolean true if name is not used, false otherwise
+ *
+ * @since 4.1.3
+ */
+ public function copyStyles()
+ {
+ $app = Factory::getApplication();
+ $template = $this->getTemplate();
+ $newName = strtolower($this->getState('new_name'));
+ $applyStyles = $this->getState('stylesToCopy');
+
+ // Get a db connection.
+ $db = $this->getDatabase();
+
+ // Create a new query object.
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName(['title', 'params']))
+ ->from($db->quoteName('#__template_styles'))
+ ->whereIn($db->quoteName('id'), ArrayHelper::toInteger($applyStyles));
+ // Reset the query using our newly populated query object.
+ $db->setQuery($query);
+
+ try {
+ $parentStyle = $db->loadObjectList();
+ } catch (\Exception $e) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_STYLE_NOT_FOUND'), 'error');
+
+ return false;
+ }
+
+ foreach ($parentStyle as $style) {
+ $query = $db->getQuery(true);
+ $styleName = Text::sprintf('COM_TEMPLATES_COPY_CHILD_TEMPLATE_STYLES', ucfirst($template->element . '_' . $newName), $style->title);
+
+ // Insert columns and values
+ $columns = ['id', 'template', 'client_id', 'home', 'title', 'inheritable', 'parent', 'params'];
+ $values = [0, $db->quote($template->element . '_' . $newName), (int) $template->client_id, $db->quote('0'), $db->quote($styleName), 0, $db->quote($template->element), $db->quote($style->params)];
+
+ $query
+ ->insert($db->quoteName('#__template_styles'))
+ ->columns($db->quoteName($columns))
+ ->values(implode(',', $values));
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\Exception $e) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_ERROR_COULD_NOT_READ'), 'error');
+
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_templates/src/Model/TemplatesModel.php b/administrator/components/com_templates/src/Model/TemplatesModel.php
index 13ef6e3644480..2e02145a99c05 100644
--- a/administrator/components/com_templates/src/Model/TemplatesModel.php
+++ b/administrator/components/com_templates/src/Model/TemplatesModel.php
@@ -1,4 +1,5 @@
client_id);
- $item->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $item->element);
- $num = $this->updated($item->extension_id);
-
- if ($num)
- {
- $item->updated = $num;
- }
- }
-
- return $items;
- }
-
- /**
- * Check if template extension have any updated override.
- *
- * @param integer $exid Extension id of template.
- *
- * @return boolean False if records not found/else integer.
- *
- * @since 4.0.0
- */
- public function updated($exid)
- {
- $db = $this->getDatabase();
-
- // Select the required fields from the table
- $query = $db->getQuery(true)
- ->select($db->quoteName('template'))
- ->from($db->quoteName('#__template_overrides'))
- ->where($db->quoteName('extension_id') . ' = :extensionid')
- ->where($db->quoteName('state') . ' = 0')
- ->bind(':extensionid', $exid, ParameterType::INTEGER);
-
- // Reset the query.
- $db->setQuery($query);
-
- // Load the results as a list of stdClass objects.
- $num = count($db->loadObjectList());
-
- if ($num > 0)
- {
- return $num;
- }
-
- return false;
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return \Joomla\Database\DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.extension_id, a.name, a.element, a.client_id'
- )
- );
- $clientId = (int) $this->getState('client_id');
- $query->from($db->quoteName('#__extensions', 'a'))
- ->where($db->quoteName('a.client_id') . ' = :clientid')
- ->where($db->quoteName('a.enabled') . ' = 1')
- ->where($db->quoteName('a.type') . ' = ' . $db->quote('template'))
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
-
- // Filter by search in title.
- if ($search = $this->getState('filter.search'))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . StringHelper::strtolower($search) . '%';
- $query->extendWhere(
- 'AND',
- [
- 'LOWER(' . $db->quoteName('a.element') . ') LIKE :element',
- 'LOWER(' . $db->quoteName('a.name') . ') LIKE :name',
- ],
- 'OR'
- )
- ->bind(':element', $search)
- ->bind(':name', $search);
- }
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.element')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('client_id');
- $id .= ':' . $this->getState('filter.search');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.element', $direction = 'asc')
- {
- // Load the filter state.
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
-
- // Special case for the client id.
- $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
- $clientId = (!in_array($clientId, array (0, 1))) ? 0 : $clientId;
- $this->setState('client_id', $clientId);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_templates');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'name', 'a.name',
+ 'folder', 'a.folder',
+ 'element', 'a.element',
+ 'checked_out', 'a.checked_out',
+ 'checked_out_time', 'a.checked_out_time',
+ 'state', 'a.state',
+ 'enabled', 'a.enabled',
+ 'ordering', 'a.ordering',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Override parent getItems to add extra XML metadata.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ foreach ($items as &$item) {
+ $client = ApplicationHelper::getClientInfo($item->client_id);
+ $item->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $item->element);
+ $num = $this->updated($item->extension_id);
+
+ if ($num) {
+ $item->updated = $num;
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Check if template extension have any updated override.
+ *
+ * @param integer $exid Extension id of template.
+ *
+ * @return boolean False if records not found/else integer.
+ *
+ * @since 4.0.0
+ */
+ public function updated($exid)
+ {
+ $db = $this->getDatabase();
+
+ // Select the required fields from the table
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('template'))
+ ->from($db->quoteName('#__template_overrides'))
+ ->where($db->quoteName('extension_id') . ' = :extensionid')
+ ->where($db->quoteName('state') . ' = 0')
+ ->bind(':extensionid', $exid, ParameterType::INTEGER);
+
+ // Reset the query.
+ $db->setQuery($query);
+
+ // Load the results as a list of stdClass objects.
+ $num = count($db->loadObjectList());
+
+ if ($num > 0) {
+ return $num;
+ }
+
+ return false;
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return \Joomla\Database\DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.extension_id, a.name, a.element, a.client_id'
+ )
+ );
+ $clientId = (int) $this->getState('client_id');
+ $query->from($db->quoteName('#__extensions', 'a'))
+ ->where($db->quoteName('a.client_id') . ' = :clientid')
+ ->where($db->quoteName('a.enabled') . ' = 1')
+ ->where($db->quoteName('a.type') . ' = ' . $db->quote('template'))
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+
+ // Filter by search in title.
+ if ($search = $this->getState('filter.search')) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . StringHelper::strtolower($search) . '%';
+ $query->extendWhere(
+ 'AND',
+ [
+ 'LOWER(' . $db->quoteName('a.element') . ') LIKE :element',
+ 'LOWER(' . $db->quoteName('a.name') . ') LIKE :name',
+ ],
+ 'OR'
+ )
+ ->bind(':element', $search)
+ ->bind(':name', $search);
+ }
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.element')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('client_id');
+ $id .= ':' . $this->getState('filter.search');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.element', $direction = 'asc')
+ {
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+
+ // Special case for the client id.
+ $clientId = (int) $this->getUserStateFromRequest($this->context . '.client_id', 'client_id', 0, 'int');
+ $clientId = (!in_array($clientId, array (0, 1))) ? 0 : $clientId;
+ $this->setState('client_id', $clientId);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_templates');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
}
diff --git a/administrator/components/com_templates/src/Service/HTML/Templates.php b/administrator/components/com_templates/src/Service/HTML/Templates.php
index 6d7fd3632d220..45a38d1e0439a 100644
--- a/administrator/components/com_templates/src/Service/HTML/Templates.php
+++ b/administrator/components/com_templates/src/Service/HTML/Templates.php
@@ -1,4 +1,5 @@
client_id);
-
- if (!isset($template->xmldata))
- {
- $template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
- }
-
- if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent))
- {
- if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '' && file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png'))
- {
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png'))
- {
- $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
- $html = '' . $html . ' ';
- }
- elseif ((file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png')))
- {
- $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
- $html = '' . $html . ' ';
- }
- else
- {
- $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
- }
- }
- elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png'))
- {
- $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
-
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png'))
- {
- $html = '' . $html . ' ';
- }
- }
- else
- {
- $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
- }
- }
- elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png'))
- {
- $html = HTMLHelper::_('image', (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator/') . '/templates/' . $template->element . '/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], false, -1);
-
- if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png'))
- {
- $html = '' . $html . ' ';
- }
- }
- else
- {
- $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
- }
-
- return $html;
- }
-
- /**
- * Renders the html for the modal linked to thumb.
- *
- * @param string|object $template The name of the template or the template object.
- * @param integer $clientId The application client ID the template applies to
- *
- * @return string The html string
- *
- * @since 3.4
- *
- * @deprecated 5.0 The argument $template should be object and $clientId will be removed
- */
- public function thumbModal($template, $clientId = 0)
- {
- if (is_string($template))
- {
- return HTMLHelper::_('image', 'template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], true, -1);
- }
-
- $html = '';
- $thumb = '';
- $preview = '';
- $client = ApplicationHelper::getClientInfo($template->client_id);
-
- if (!isset($template->xmldata))
- {
- $template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
- }
-
- if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent))
- {
- if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '')
- {
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png'))
- {
- $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
-
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element. '/images/template_preview.png'))
- {
- $preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element. '/images/template_preview.png';
- }
- }
- else
- {
- $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png';
-
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent. '/images/template_preview.png'))
- {
- $preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent. '/images/template_preview.png';
- }
- }
- }
- elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png'))
- {
- $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
-
- if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png'))
- {
- $preview = Uri::root(true) . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png';
- }
- }
- }
- elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png'))
- {
- $thumb = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . 'administrator') . '/templates/' . $template->element . '/template_thumbnail.png';
-
- if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png'))
- {
- $preview = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator') . '/templates/' . $template->element . '/template_preview.png';
- }
- }
-
- if ($thumb !== '' && $preview !== '')
- {
- $footer = ''
- . Text::_('JTOOLBAR_CLOSE') . ' ';
-
- $html .= HTMLHelper::_(
- 'bootstrap.renderModal',
- $template->name . '-Modal',
- array(
- 'title' => Text::sprintf('COM_TEMPLATES_SCREENSHOT', ucfirst($template->name)),
- 'height' => '500px',
- 'width' => '800px',
- 'footer' => $footer,
- ),
- ''
- );
- }
-
- return $html;
- }
+ /**
+ * Display the thumb for the template.
+ *
+ * @param string|object $template The name of the template or the template object.
+ * @param integer $clientId The application client ID the template applies to
+ *
+ * @return string The html string
+ *
+ * @since 1.6
+ *
+ * @deprecated 5.0 The argument $template should be object and $clientId will be removed
+ */
+ public function thumb($template, $clientId = 0)
+ {
+ if (is_string($template)) {
+ return HTMLHelper::_('image', 'template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], true, -1);
+ }
+
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+
+ if (!isset($template->xmldata)) {
+ $template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
+ }
+
+ if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent)) {
+ if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '' && file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png')) {
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
+ $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
+ $html = '' . $html . ' ';
+ } elseif ((file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png'))) {
+ $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
+ $html = '' . $html . ' ';
+ } else {
+ $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
+ }
+ } elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
+ $html = HTMLHelper::_('image', 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'));
+
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
+ $html = '' . $html . ' ';
+ }
+ } else {
+ $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
+ }
+ } elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png')) {
+ $html = HTMLHelper::_('image', (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator/') . '/templates/' . $template->element . '/template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], false, -1);
+
+ if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png')) {
+ $html = '' . $html . ' ';
+ }
+ } else {
+ $html = HTMLHelper::_('image', 'template_thumb.svg', Text::_('COM_TEMPLATES_PREVIEW'), ['style' => 'width:200px; height:120px;']);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Renders the html for the modal linked to thumb.
+ *
+ * @param string|object $template The name of the template or the template object.
+ * @param integer $clientId The application client ID the template applies to
+ *
+ * @return string The html string
+ *
+ * @since 3.4
+ *
+ * @deprecated 5.0 The argument $template should be object and $clientId will be removed
+ */
+ public function thumbModal($template, $clientId = 0)
+ {
+ if (is_string($template)) {
+ return HTMLHelper::_('image', 'template_thumbnail.png', Text::_('COM_TEMPLATES_PREVIEW'), [], true, -1);
+ }
+
+ $html = '';
+ $thumb = '';
+ $preview = '';
+ $client = ApplicationHelper::getClientInfo($template->client_id);
+
+ if (!isset($template->xmldata)) {
+ $template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->id === 0 ? JPATH_ROOT : JPATH_ROOT . '/administrator', $template->name);
+ }
+
+ if ((isset($template->xmldata->inheritable) && (bool) $template->xmldata->inheritable) || isset($template->xmldata->parent)) {
+ if (isset($template->xmldata->parent) && (string) $template->xmldata->parent !== '') {
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
+ $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
+
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
+ $preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png';
+ }
+ } else {
+ $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . 'administrator') . 'media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_thumbnail.png';
+
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png')) {
+ $preview = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . (string) $template->xmldata->parent . '/images/template_preview.png';
+ }
+ }
+ } elseif (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png')) {
+ $thumb = ($template->client_id == 0 ? Uri::root(true) : Uri::root(true) . '/administrator') . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_thumbnail.png';
+
+ if (file_exists(JPATH_ROOT . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png')) {
+ $preview = Uri::root(true) . '/media/templates/' . $client->name . '/' . $template->element . '/images/template_preview.png';
+ }
+ }
+ } elseif (file_exists($client->path . '/templates/' . $template->element . '/template_thumbnail.png')) {
+ $thumb = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . 'administrator') . '/templates/' . $template->element . '/template_thumbnail.png';
+
+ if (file_exists($client->path . '/templates/' . $template->element . '/template_preview.png')) {
+ $preview = (($template->client_id == 0) ? Uri::root(true) : Uri::root(true) . '/administrator') . '/templates/' . $template->element . '/template_preview.png';
+ }
+ }
+
+ if ($thumb !== '' && $preview !== '') {
+ $footer = ''
+ . Text::_('JTOOLBAR_CLOSE') . ' ';
+
+ $html .= HTMLHelper::_(
+ 'bootstrap.renderModal',
+ $template->name . '-Modal',
+ array(
+ 'title' => Text::sprintf('COM_TEMPLATES_SCREENSHOT', ucfirst($template->name)),
+ 'height' => '500px',
+ 'width' => '800px',
+ 'footer' => $footer,
+ ),
+ ''
+ );
+ }
+
+ return $html;
+ }
}
diff --git a/administrator/components/com_templates/src/Table/StyleTable.php b/administrator/components/com_templates/src/Table/StyleTable.php
index 857dd4f01f3c4..f0a7a5c298145 100644
--- a/administrator/components/com_templates/src/Table/StyleTable.php
+++ b/administrator/components/com_templates/src/Table/StyleTable.php
@@ -1,4 +1,5 @@
home == '1')
- {
- $this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
-
- return false;
- }
-
- return parent::bind($array, $ignore);
- }
-
- /**
- * Overloaded check method to ensure data integrity.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (empty($this->title))
- {
- $this->setError(Text::_('COM_TEMPLATES_ERROR_STYLE_REQUIRES_TITLE'));
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Overloaded store method to ensure unicity of default style.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function store($updateNulls = false)
- {
- if ($this->home != '0')
- {
- $clientId = (int) $this->client_id;
- $query = $this->_db->getQuery(true)
- ->update($this->_db->quoteName('#__template_styles'))
- ->set($this->_db->quoteName('home') . ' = ' . $this->_db->quote('0'))
- ->where($this->_db->quoteName('client_id') . ' = :clientid')
- ->where($this->_db->quoteName('home') . ' = :home')
- ->bind(':clientid', $clientId, ParameterType::INTEGER)
- ->bind(':home', $this->home);
- $this->_db->setQuery($query);
- $this->_db->execute();
- }
-
- return parent::store($updateNulls);
- }
-
- /**
- * Overloaded store method to unsure existence of a default style for a template.
- *
- * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function delete($pk = null)
- {
- $k = $this->_tbl_key;
- $pk = is_null($pk) ? $this->$k : $pk;
-
- if (!is_null($pk))
- {
- $clientId = (int) $this->client_id;
- $query = $this->_db->getQuery(true)
- ->select($this->_db->quoteName('id'))
- ->from($this->_db->quoteName('#__template_styles'))
- ->where($this->_db->quoteName('client_id') . ' = :clientid')
- ->where($this->_db->quoteName('template') . ' = :template')
- ->bind(':template', $this->template)
- ->bind(':clientid', $clientId, ParameterType::INTEGER);
- $this->_db->setQuery($query);
- $results = $this->_db->loadColumn();
-
- if (count($results) == 1 && $results[0] == $pk)
- {
- $this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_DELETE_LAST_STYLE'));
-
- return false;
- }
- }
-
- return parent::delete($pk);
- }
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db A database connector object
+ *
+ * @since 1.6
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ parent::__construct('#__template_styles', 'id', $db);
+ }
+
+ /**
+ * Overloaded bind function to pre-process the params.
+ *
+ * @param array $array Named array
+ * @param mixed $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return null|string null if operation was satisfactory, otherwise returns an error
+ *
+ * @since 1.6
+ */
+ public function bind($array, $ignore = '')
+ {
+ if (isset($array['params']) && is_array($array['params'])) {
+ $registry = new Registry($array['params']);
+ $array['params'] = (string) $registry;
+ }
+
+ // Verify that the default style is not unset
+ if ($array['home'] == '0' && $this->home == '1') {
+ $this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_UNSET_DEFAULT_STYLE'));
+
+ return false;
+ }
+
+ return parent::bind($array, $ignore);
+ }
+
+ /**
+ * Overloaded check method to ensure data integrity.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (empty($this->title)) {
+ $this->setError(Text::_('COM_TEMPLATES_ERROR_STYLE_REQUIRES_TITLE'));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded store method to ensure unicity of default style.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function store($updateNulls = false)
+ {
+ if ($this->home != '0') {
+ $clientId = (int) $this->client_id;
+ $query = $this->_db->getQuery(true)
+ ->update($this->_db->quoteName('#__template_styles'))
+ ->set($this->_db->quoteName('home') . ' = ' . $this->_db->quote('0'))
+ ->where($this->_db->quoteName('client_id') . ' = :clientid')
+ ->where($this->_db->quoteName('home') . ' = :home')
+ ->bind(':clientid', $clientId, ParameterType::INTEGER)
+ ->bind(':home', $this->home);
+ $this->_db->setQuery($query);
+ $this->_db->execute();
+ }
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Overloaded store method to unsure existence of a default style for a template.
+ *
+ * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function delete($pk = null)
+ {
+ $k = $this->_tbl_key;
+ $pk = is_null($pk) ? $this->$k : $pk;
+
+ if (!is_null($pk)) {
+ $clientId = (int) $this->client_id;
+ $query = $this->_db->getQuery(true)
+ ->select($this->_db->quoteName('id'))
+ ->from($this->_db->quoteName('#__template_styles'))
+ ->where($this->_db->quoteName('client_id') . ' = :clientid')
+ ->where($this->_db->quoteName('template') . ' = :template')
+ ->bind(':template', $this->template)
+ ->bind(':clientid', $clientId, ParameterType::INTEGER);
+ $this->_db->setQuery($query);
+ $results = $this->_db->loadColumn();
+
+ if (count($results) == 1 && $results[0] == $pk) {
+ $this->setError(Text::_('COM_TEMPLATES_ERROR_CANNOT_DELETE_LAST_STYLE'));
+
+ return false;
+ }
+ }
+
+ return parent::delete($pk);
+ }
}
diff --git a/administrator/components/com_templates/src/View/Style/HtmlView.php b/administrator/components/com_templates/src/View/Style/HtmlView.php
index 8f315cd13deed..3a80a7735b8fc 100644
--- a/administrator/components/com_templates/src/View/Style/HtmlView.php
+++ b/administrator/components/com_templates/src/View/Style/HtmlView.php
@@ -1,4 +1,5 @@
item = $this->get('Item');
- $this->state = $this->get('State');
- $this->form = $this->get('Form');
- $this->canDo = ContentHelper::getActions('com_templates');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $isNew = ($this->item->id == 0);
- $canDo = $this->canDo;
-
- ToolbarHelper::title(
- $isNew ? Text::_('COM_TEMPLATES_MANAGER_ADD_STYLE')
- : Text::_('COM_TEMPLATES_MANAGER_EDIT_STYLE'), 'paint-brush thememanager'
- );
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if ($canDo->get('core.edit'))
- {
- ToolbarHelper::apply('style.apply');
- $toolbarButtons[] = ['save', 'style.save'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'style.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('style.cancel');
- }
- else
- {
- ToolbarHelper::cancel('style.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
-
- // Get the help information for the template item.
- $lang = Factory::getLanguage();
- $help = $this->get('Help');
-
- if ($lang->hasKey($help->url))
- {
- $debug = $lang->setDebug(false);
- $url = Text::_($help->url);
- $lang->setDebug($debug);
- }
- else
- {
- $url = null;
- }
-
- ToolbarHelper::help($help->key, false, $url);
- }
+ /**
+ * The CMSObject (on success, false on failure)
+ *
+ * @var CMSObject
+ */
+ protected $item;
+
+ /**
+ * The form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * The actions the user is authorised to perform
+ *
+ * @var CMSObject
+ *
+ * @since 4.0.0
+ */
+ protected $canDo;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+ $this->form = $this->get('Form');
+ $this->canDo = ContentHelper::getActions('com_templates');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $isNew = ($this->item->id == 0);
+ $canDo = $this->canDo;
+
+ ToolbarHelper::title(
+ $isNew ? Text::_('COM_TEMPLATES_MANAGER_ADD_STYLE')
+ : Text::_('COM_TEMPLATES_MANAGER_EDIT_STYLE'),
+ 'paint-brush thememanager'
+ );
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if ($canDo->get('core.edit')) {
+ ToolbarHelper::apply('style.apply');
+ $toolbarButtons[] = ['save', 'style.save'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'style.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('style.cancel');
+ } else {
+ ToolbarHelper::cancel('style.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+
+ // Get the help information for the template item.
+ $lang = Factory::getLanguage();
+ $help = $this->get('Help');
+
+ if ($lang->hasKey($help->url)) {
+ $debug = $lang->setDebug(false);
+ $url = Text::_($help->url);
+ $lang->setDebug($debug);
+ } else {
+ $url = null;
+ }
+
+ ToolbarHelper::help($help->key, false, $url);
+ }
}
diff --git a/administrator/components/com_templates/src/View/Style/JsonView.php b/administrator/components/com_templates/src/View/Style/JsonView.php
index edc7dd229f897..f019d9cd92796 100644
--- a/administrator/components/com_templates/src/View/Style/JsonView.php
+++ b/administrator/components/com_templates/src/View/Style/JsonView.php
@@ -1,4 +1,5 @@
item = $this->get('Item');
- }
- catch (\Exception $e)
- {
- $app = Factory::getApplication();
- $app->enqueueMessage($e->getMessage(), 'error');
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return mixed A string if successful, otherwise an Error object.
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ try {
+ $this->item = $this->get('Item');
+ } catch (\Exception $e) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage($e->getMessage(), 'error');
- return false;
- }
+ return false;
+ }
- $paramsList = $this->item->getProperties();
+ $paramsList = $this->item->getProperties();
- unset($paramsList['xml']);
+ unset($paramsList['xml']);
- $paramsList = json_encode($paramsList);
+ $paramsList = json_encode($paramsList);
- return $paramsList;
- }
+ return $paramsList;
+ }
}
diff --git a/administrator/components/com_templates/src/View/Styles/HtmlView.php b/administrator/components/com_templates/src/View/Styles/HtmlView.php
index 15783e618dfa4..5dfdb107dd08d 100644
--- a/administrator/components/com_templates/src/View/Styles/HtmlView.php
+++ b/administrator/components/com_templates/src/View/Styles/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->total = $this->get('Total');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
-
- // Remove the menu item filter for administrator styles.
- if ((int) $this->state->get('client_id') !== 0)
- {
- unset($this->activeFilters['menuitem']);
- $this->filterForm->removeField('menuitem', 'filter');
- }
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_templates');
- $clientId = (int) $this->get('State')->get('client_id');
-
- // Add a shortcut to the templates list view.
- ToolbarHelper::link('index.php?option=com_templates&view=templates&client_id=' . $clientId, 'COM_TEMPLATES_MANAGER_TEMPLATES', 'icon-code thememanager');
-
- // Set the title.
- if ($clientId === 1)
- {
- ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_ADMIN'), 'paint-brush thememanager');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_SITE'), 'paint-brush thememanager');
- }
-
- if ($canDo->get('core.edit.state'))
- {
- ToolbarHelper::makeDefault('styles.setDefault', 'COM_TEMPLATES_TOOLBAR_SET_HOME');
- ToolbarHelper::divider();
- }
-
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::custom('styles.duplicate', 'copy', '', 'JTOOLBAR_DUPLICATE', true);
- ToolbarHelper::divider();
- }
-
- if ($canDo->get('core.delete'))
- {
- ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'styles.delete', 'JTOOLBAR_DELETE');
- ToolbarHelper::divider();
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_templates');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Templates:_Styles');
- }
+ /**
+ * An array of items
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var \Joomla\CMS\Object\CMSObject
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is the parameter enabled to show template positions in the frontend?
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ public $preview;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->total = $this->get('Total');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
+
+ // Remove the menu item filter for administrator styles.
+ if ((int) $this->state->get('client_id') !== 0) {
+ unset($this->activeFilters['menuitem']);
+ $this->filterForm->removeField('menuitem', 'filter');
+ }
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_templates');
+ $clientId = (int) $this->get('State')->get('client_id');
+
+ // Add a shortcut to the templates list view.
+ ToolbarHelper::link('index.php?option=com_templates&view=templates&client_id=' . $clientId, 'COM_TEMPLATES_MANAGER_TEMPLATES', 'icon-code thememanager');
+
+ // Set the title.
+ if ($clientId === 1) {
+ ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_ADMIN'), 'paint-brush thememanager');
+ } else {
+ ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_STYLES_SITE'), 'paint-brush thememanager');
+ }
+
+ if ($canDo->get('core.edit.state')) {
+ ToolbarHelper::makeDefault('styles.setDefault', 'COM_TEMPLATES_TOOLBAR_SET_HOME');
+ ToolbarHelper::divider();
+ }
+
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::custom('styles.duplicate', 'copy', '', 'JTOOLBAR_DUPLICATE', true);
+ ToolbarHelper::divider();
+ }
+
+ if ($canDo->get('core.delete')) {
+ ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'styles.delete', 'JTOOLBAR_DELETE');
+ ToolbarHelper::divider();
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_templates');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Templates:_Styles');
+ }
}
diff --git a/administrator/components/com_templates/src/View/Template/HtmlView.php b/administrator/components/com_templates/src/View/Template/HtmlView.php
index 84992c390493c..9b430a038deda 100644
--- a/administrator/components/com_templates/src/View/Template/HtmlView.php
+++ b/administrator/components/com_templates/src/View/Template/HtmlView.php
@@ -1,4 +1,5 @@
file = $app->input->get('file');
- $this->fileName = InputFilter::getInstance()->clean(base64_decode($this->file), 'string');
- $explodeArray = explode('.', $this->fileName);
- $ext = end($explodeArray);
- $this->files = $this->get('Files');
- $this->mediaFiles = $this->get('MediaFiles');
- $this->state = $this->get('State');
- $this->template = $this->get('Template');
- $this->preview = $this->get('Preview');
- $this->pluginState = PluginHelper::isEnabled('installer', 'override');
- $this->updatedList = $this->get('UpdatedList');
- $this->styles = $this->get('AllTemplateStyles');
- $this->stylesHTML = '';
-
- $params = ComponentHelper::getParams('com_templates');
- $imageTypes = explode(',', $params->get('image_formats'));
- $sourceTypes = explode(',', $params->get('source_formats'));
- $fontTypes = explode(',', $params->get('font_formats'));
- $archiveTypes = explode(',', $params->get('compressed_formats'));
-
- if (in_array($ext, $sourceTypes))
- {
- $this->form = $this->get('Form');
- $this->form->setFieldAttribute('source', 'syntax', $ext);
- $this->source = $this->get('Source');
- $this->type = 'file';
- }
- elseif (in_array($ext, $imageTypes))
- {
- try
- {
- $this->image = $this->get('Image');
- $this->type = 'image';
- }
- catch (\RuntimeException $exception)
- {
- $app->enqueueMessage(Text::_('COM_TEMPLATES_GD_EXTENSION_NOT_AVAILABLE'));
- $this->type = 'home';
- }
- }
- elseif (in_array($ext, $fontTypes))
- {
- $this->font = $this->get('Font');
- $this->type = 'font';
- }
- elseif (in_array($ext, $archiveTypes))
- {
- $this->archive = $this->get('Archive');
- $this->type = 'archive';
- }
- else
- {
- $this->type = 'home';
- }
-
- $this->overridesList = $this->get('OverridesList');
- $this->id = $this->state->get('extension.id');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- $app->enqueueMessage(implode("\n", $errors));
-
- return false;
- }
-
- $this->addToolbar();
-
- if (!$this->getCurrentUser()->authorise('core.admin'))
- {
- $this->setLayout('readonly');
- }
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @since 1.6
- *
- * @return void
- */
- protected function addToolbar()
- {
- $app = Factory::getApplication();
- $user = $this->getCurrentUser();
- $app->input->set('hidemainmenu', true);
-
- // User is global SuperUser
- $isSuperUser = $user->authorise('core.admin');
-
- // Get the toolbar object instance
- $bar = Toolbar::getInstance('toolbar');
- $explodeArray = explode('.', $this->fileName);
- $ext = end($explodeArray);
-
- ToolbarHelper::title(Text::sprintf('COM_TEMPLATES_MANAGER_VIEW_TEMPLATE', ucfirst($this->template->name)), 'icon-code thememanager');
-
- // Only show file edit buttons for global SuperUser
- if ($isSuperUser)
- {
- // Add an Apply and save button
- if ($this->type === 'file')
- {
- ToolbarHelper::apply('template.apply');
- ToolbarHelper::save('template.save');
- }
- // Add a Crop and Resize button
- elseif ($this->type === 'image')
- {
- ToolbarHelper::custom('template.cropImage', 'icon-crop', '', 'COM_TEMPLATES_BUTTON_CROP', false);
- ToolbarHelper::modal('resizeModal', 'icon-expand', 'COM_TEMPLATES_BUTTON_RESIZE');
- }
- // Add an extract button
- elseif ($this->type === 'archive')
- {
- ToolbarHelper::custom('template.extractArchive', 'chevron-down', '', 'COM_TEMPLATES_BUTTON_EXTRACT_ARCHIVE', false);
- }
- // Add a copy/child template button
- elseif ($this->type === 'home')
- {
- if (isset($this->template->xmldata->inheritable) && (string) $this->template->xmldata->inheritable === '1')
- {
- ToolbarHelper::modal('childModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_TEMPLATE_CHILD', false);
- }
- elseif (!isset($this->template->xmldata->parent) || $this->template->xmldata->parent == '')
- {
- ToolbarHelper::modal('copyModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_COPY_TEMPLATE', false);
- }
- }
- }
-
- // Add a Template preview button
- if ($this->type === 'home')
- {
- $client = (int) $this->preview->client_id === 1 ? 'administrator/' : '';
- $bar->linkButton('preview')
- ->icon('icon-image')
- ->text('COM_TEMPLATES_BUTTON_PREVIEW')
- ->url(Uri::root() . $client . 'index.php?tp=1&templateStyle=' . $this->preview->id)
- ->attributes(['target' => '_new']);
- }
-
- // Only show file manage buttons for global SuperUser
- if ($isSuperUser)
- {
- if ($this->type === 'home')
- {
- // Add Manage folders button
- ToolbarHelper::modal('folderModal', 'icon-folder icon white', 'COM_TEMPLATES_BUTTON_FOLDERS');
-
- // Add a new file button
- ToolbarHelper::modal('fileModal', 'icon-file', 'COM_TEMPLATES_BUTTON_FILE');
- }
- else
- {
- // Add a Rename file Button
- ToolbarHelper::modal('renameModal', 'icon-sync', 'COM_TEMPLATES_BUTTON_RENAME_FILE');
-
- // Add a Delete file Button
- ToolbarHelper::modal('deleteModal', 'icon-times', 'COM_TEMPLATES_BUTTON_DELETE_FILE', 'btn-danger');
- }
- }
-
- if (count($this->updatedList) !== 0 && $this->pluginState)
- {
- ToolbarHelper::custom('template.deleteOverrideHistory', 'times', '', 'COM_TEMPLATES_BUTTON_DELETE_LIST_ENTRY', true, 'updateForm');
- }
-
- if ($this->type === 'home')
- {
- ToolbarHelper::cancel('template.cancel', 'JTOOLBAR_CLOSE');
- }
- else
- {
- ToolbarHelper::cancel('template.close', 'COM_TEMPLATES_BUTTON_CLOSE_FILE');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Templates:_Customise');
- }
-
- /**
- * Method for creating the collapsible tree.
- *
- * @param array $array The value of the present node for recursion
- *
- * @return string
- *
- * @note Uses recursion
- * @since 3.2
- */
- protected function directoryTree($array)
- {
- $temp = $this->files;
- $this->files = $array;
- $txt = $this->loadTemplate('tree');
- $this->files = $temp;
-
- return $txt;
- }
-
- /**
- * Method for listing the folder tree in modals.
- *
- * @param array $array The value of the present node for recursion
- *
- * @return string
- *
- * @note Uses recursion
- * @since 3.2
- */
- protected function folderTree($array)
- {
- $temp = $this->files;
- $this->files = $array;
- $txt = $this->loadTemplate('folders');
- $this->files = $temp;
-
- return $txt;
- }
-
- /**
- * Method for creating the collapsible tree.
- *
- * @param array $array The value of the present node for recursion
- *
- * @return string
- *
- * @note Uses recursion
- * @since 4.1.0
- */
- protected function mediaTree($array)
- {
- $temp = $this->mediaFiles;
- $this->mediaFiles = $array;
- $txt = $this->loadTemplate('tree_media');
- $this->mediaFiles = $temp;
-
- return $txt;
- }
-
- /**
- * Method for listing the folder tree in modals.
- *
- * @param array $array The value of the present node for recursion
- *
- * @return string
- *
- * @note Uses recursion
- * @since 4.1.0
- */
- protected function mediaFolderTree($array)
- {
- $temp = $this->mediaFiles;
- $this->mediaFiles = $array;
- $txt = $this->loadTemplate('media_folders');
- $this->mediaFiles = $temp;
-
- return $txt;
- }
+ /**
+ * The Model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * The template details
+ *
+ * @var \stdClass|false
+ */
+ protected $template;
+
+ /**
+ * For loading the source form
+ *
+ * @var Form
+ */
+ protected $form;
+
+ /**
+ * For loading source file contents
+ *
+ * @var array
+ */
+ protected $source;
+
+ /**
+ * Extension id
+ *
+ * @var integer
+ */
+ protected $id;
+
+ /**
+ * Encrypted file path
+ *
+ * @var string
+ */
+ protected $file;
+
+ /**
+ * List of available overrides
+ *
+ * @var array
+ */
+ protected $overridesList;
+
+ /**
+ * Name of the present file
+ *
+ * @var string
+ */
+ protected $fileName;
+
+ /**
+ * Type of the file - image, source, font
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * For loading image information
+ *
+ * @var array
+ */
+ protected $image;
+
+ /**
+ * Template id for showing preview button
+ *
+ * @var \stdClass
+ */
+ protected $preview;
+
+ /**
+ * For loading font information
+ *
+ * @var array
+ */
+ protected $font;
+
+ /**
+ * A nested array containing list of files and folders
+ *
+ * @var array
+ */
+ protected $files;
+
+ /**
+ * An array containing a list of compressed files
+ *
+ * @var array
+ */
+ protected $archive;
+
+ /**
+ * The state of installer override plugin.
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected $pluginState;
+
+ /**
+ * A nested array containing list of files and folders in the media folder
+ *
+ * @var array
+ *
+ * @since 4.1.0
+ */
+ protected $mediaFiles;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void|boolean
+ */
+ public function display($tpl = null)
+ {
+ $app = Factory::getApplication();
+ $this->file = $app->input->get('file');
+ $this->fileName = InputFilter::getInstance()->clean(base64_decode($this->file), 'string');
+ $explodeArray = explode('.', $this->fileName);
+ $ext = end($explodeArray);
+ $this->files = $this->get('Files');
+ $this->mediaFiles = $this->get('MediaFiles');
+ $this->state = $this->get('State');
+ $this->template = $this->get('Template');
+ $this->preview = $this->get('Preview');
+ $this->pluginState = PluginHelper::isEnabled('installer', 'override');
+ $this->updatedList = $this->get('UpdatedList');
+ $this->styles = $this->get('AllTemplateStyles');
+ $this->stylesHTML = '';
+
+ $params = ComponentHelper::getParams('com_templates');
+ $imageTypes = explode(',', $params->get('image_formats'));
+ $sourceTypes = explode(',', $params->get('source_formats'));
+ $fontTypes = explode(',', $params->get('font_formats'));
+ $archiveTypes = explode(',', $params->get('compressed_formats'));
+
+ if (in_array($ext, $sourceTypes)) {
+ $this->form = $this->get('Form');
+ $this->form->setFieldAttribute('source', 'syntax', $ext);
+ $this->source = $this->get('Source');
+ $this->type = 'file';
+ } elseif (in_array($ext, $imageTypes)) {
+ try {
+ $this->image = $this->get('Image');
+ $this->type = 'image';
+ } catch (\RuntimeException $exception) {
+ $app->enqueueMessage(Text::_('COM_TEMPLATES_GD_EXTENSION_NOT_AVAILABLE'));
+ $this->type = 'home';
+ }
+ } elseif (in_array($ext, $fontTypes)) {
+ $this->font = $this->get('Font');
+ $this->type = 'font';
+ } elseif (in_array($ext, $archiveTypes)) {
+ $this->archive = $this->get('Archive');
+ $this->type = 'archive';
+ } else {
+ $this->type = 'home';
+ }
+
+ $this->overridesList = $this->get('OverridesList');
+ $this->id = $this->state->get('extension.id');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ $app->enqueueMessage(implode("\n", $errors));
+
+ return false;
+ }
+
+ $this->addToolbar();
+
+ if (!$this->getCurrentUser()->authorise('core.admin')) {
+ $this->setLayout('readonly');
+ }
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @since 1.6
+ *
+ * @return void
+ */
+ protected function addToolbar()
+ {
+ $app = Factory::getApplication();
+ $user = $this->getCurrentUser();
+ $app->input->set('hidemainmenu', true);
+
+ // User is global SuperUser
+ $isSuperUser = $user->authorise('core.admin');
+
+ // Get the toolbar object instance
+ $bar = Toolbar::getInstance('toolbar');
+ $explodeArray = explode('.', $this->fileName);
+ $ext = end($explodeArray);
+
+ ToolbarHelper::title(Text::sprintf('COM_TEMPLATES_MANAGER_VIEW_TEMPLATE', ucfirst($this->template->name)), 'icon-code thememanager');
+
+ // Only show file edit buttons for global SuperUser
+ if ($isSuperUser) {
+ // Add an Apply and save button
+ if ($this->type === 'file') {
+ ToolbarHelper::apply('template.apply');
+ ToolbarHelper::save('template.save');
+ } elseif ($this->type === 'image') {
+ // Add a Crop and Resize button
+ ToolbarHelper::custom('template.cropImage', 'icon-crop', '', 'COM_TEMPLATES_BUTTON_CROP', false);
+ ToolbarHelper::modal('resizeModal', 'icon-expand', 'COM_TEMPLATES_BUTTON_RESIZE');
+ } elseif ($this->type === 'archive') {
+ // Add an extract button
+ ToolbarHelper::custom('template.extractArchive', 'chevron-down', '', 'COM_TEMPLATES_BUTTON_EXTRACT_ARCHIVE', false);
+ } elseif ($this->type === 'home') {
+ // Add a copy/child template button
+ if (isset($this->template->xmldata->inheritable) && (string) $this->template->xmldata->inheritable === '1') {
+ ToolbarHelper::modal('childModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_TEMPLATE_CHILD', false);
+ } elseif (!isset($this->template->xmldata->parent) || $this->template->xmldata->parent == '') {
+ ToolbarHelper::modal('copyModal', 'icon-copy', 'COM_TEMPLATES_BUTTON_COPY_TEMPLATE', false);
+ }
+ }
+ }
+
+ // Add a Template preview button
+ if ($this->type === 'home') {
+ $client = (int) $this->preview->client_id === 1 ? 'administrator/' : '';
+ $bar->linkButton('preview')
+ ->icon('icon-image')
+ ->text('COM_TEMPLATES_BUTTON_PREVIEW')
+ ->url(Uri::root() . $client . 'index.php?tp=1&templateStyle=' . $this->preview->id)
+ ->attributes(['target' => '_new']);
+ }
+
+ // Only show file manage buttons for global SuperUser
+ if ($isSuperUser) {
+ if ($this->type === 'home') {
+ // Add Manage folders button
+ ToolbarHelper::modal('folderModal', 'icon-folder icon white', 'COM_TEMPLATES_BUTTON_FOLDERS');
+
+ // Add a new file button
+ ToolbarHelper::modal('fileModal', 'icon-file', 'COM_TEMPLATES_BUTTON_FILE');
+ } else {
+ // Add a Rename file Button
+ ToolbarHelper::modal('renameModal', 'icon-sync', 'COM_TEMPLATES_BUTTON_RENAME_FILE');
+
+ // Add a Delete file Button
+ ToolbarHelper::modal('deleteModal', 'icon-times', 'COM_TEMPLATES_BUTTON_DELETE_FILE', 'btn-danger');
+ }
+ }
+
+ if (count($this->updatedList) !== 0 && $this->pluginState) {
+ ToolbarHelper::custom('template.deleteOverrideHistory', 'times', '', 'COM_TEMPLATES_BUTTON_DELETE_LIST_ENTRY', true, 'updateForm');
+ }
+
+ if ($this->type === 'home') {
+ ToolbarHelper::cancel('template.cancel', 'JTOOLBAR_CLOSE');
+ } else {
+ ToolbarHelper::cancel('template.close', 'COM_TEMPLATES_BUTTON_CLOSE_FILE');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Templates:_Customise');
+ }
+
+ /**
+ * Method for creating the collapsible tree.
+ *
+ * @param array $array The value of the present node for recursion
+ *
+ * @return string
+ *
+ * @note Uses recursion
+ * @since 3.2
+ */
+ protected function directoryTree($array)
+ {
+ $temp = $this->files;
+ $this->files = $array;
+ $txt = $this->loadTemplate('tree');
+ $this->files = $temp;
+
+ return $txt;
+ }
+
+ /**
+ * Method for listing the folder tree in modals.
+ *
+ * @param array $array The value of the present node for recursion
+ *
+ * @return string
+ *
+ * @note Uses recursion
+ * @since 3.2
+ */
+ protected function folderTree($array)
+ {
+ $temp = $this->files;
+ $this->files = $array;
+ $txt = $this->loadTemplate('folders');
+ $this->files = $temp;
+
+ return $txt;
+ }
+
+ /**
+ * Method for creating the collapsible tree.
+ *
+ * @param array $array The value of the present node for recursion
+ *
+ * @return string
+ *
+ * @note Uses recursion
+ * @since 4.1.0
+ */
+ protected function mediaTree($array)
+ {
+ $temp = $this->mediaFiles;
+ $this->mediaFiles = $array;
+ $txt = $this->loadTemplate('tree_media');
+ $this->mediaFiles = $temp;
+
+ return $txt;
+ }
+
+ /**
+ * Method for listing the folder tree in modals.
+ *
+ * @param array $array The value of the present node for recursion
+ *
+ * @return string
+ *
+ * @note Uses recursion
+ * @since 4.1.0
+ */
+ protected function mediaFolderTree($array)
+ {
+ $temp = $this->mediaFiles;
+ $this->mediaFiles = $array;
+ $txt = $this->loadTemplate('media_folders');
+ $this->mediaFiles = $temp;
+
+ return $txt;
+ }
}
diff --git a/administrator/components/com_templates/src/View/Templates/HtmlView.php b/administrator/components/com_templates/src/View/Templates/HtmlView.php
index fbbed69c8023c..127f5bfe781f3 100644
--- a/administrator/components/com_templates/src/View/Templates/HtmlView.php
+++ b/administrator/components/com_templates/src/View/Templates/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->total = $this->get('Total');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
- $this->file = base64_encode('home');
- $this->pluginState = PluginHelper::isEnabled('installer', 'override');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_templates');
- $clientId = (int) $this->get('State')->get('client_id');
-
- // Add a shortcut to the styles list view.
- ToolbarHelper::link('index.php?option=com_templates&view=styles&client_id=' . $clientId, 'COM_TEMPLATES_MANAGER_STYLES_BUTTON', 'brush thememanager');
-
- // Set the title.
- if ($clientId === 1)
- {
- ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_ADMIN'), 'icon-code thememanager');
- }
- else
- {
- ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_SITE'), 'icon-code thememanager');
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_templates');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Templates:_Templates');
- }
+ /**
+ * The list of templates
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * @var string
+ * @since 3.2
+ */
+ protected $file;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is the parameter enabled to show template positions in the frontend?
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ public $preview;
+
+ /**
+ * The state of installer override plugin.
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected $pluginState;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->total = $this->get('Total');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->preview = ComponentHelper::getParams('com_templates')->get('template_positions_display');
+ $this->file = base64_encode('home');
+ $this->pluginState = PluginHelper::isEnabled('installer', 'override');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_templates');
+ $clientId = (int) $this->get('State')->get('client_id');
+
+ // Add a shortcut to the styles list view.
+ ToolbarHelper::link('index.php?option=com_templates&view=styles&client_id=' . $clientId, 'COM_TEMPLATES_MANAGER_STYLES_BUTTON', 'brush thememanager');
+
+ // Set the title.
+ if ($clientId === 1) {
+ ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_ADMIN'), 'icon-code thememanager');
+ } else {
+ ToolbarHelper::title(Text::_('COM_TEMPLATES_MANAGER_TEMPLATES_SITE'), 'icon-code thememanager');
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_templates');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Templates:_Templates');
+ }
}
diff --git a/administrator/components/com_templates/tmpl/style/edit.php b/administrator/components/com_templates/tmpl/style/edit.php
index f0aa6a48d4c67..92103a45ea775 100644
--- a/administrator/components/com_templates/tmpl/style/edit.php
+++ b/administrator/components/com_templates/tmpl/style/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$this->useCoreUI = true;
@@ -27,90 +28,90 @@
diff --git a/administrator/components/com_templates/tmpl/style/edit_assignment.php b/administrator/components/com_templates/tmpl/style/edit_assignment.php
index b0a03841f953e..bd3cae0f46d2d 100644
--- a/administrator/components/com_templates/tmpl/style/edit_assignment.php
+++ b/administrator/components/com_templates/tmpl/style/edit_assignment.php
@@ -1,4 +1,5 @@
-
-
-
+
+
+
diff --git a/administrator/components/com_templates/tmpl/styles/default.php b/administrator/components/com_templates/tmpl/styles/default.php
index a610932134949..371221698e528 100644
--- a/administrator/components/com_templates/tmpl/styles/default.php
+++ b/administrator/components/com_templates/tmpl/styles/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$clientId = (int) $this->state->get('client_id', 0);
@@ -27,127 +28,127 @@
$listDirn = $this->escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_templates/tmpl/template/default.php b/administrator/components/com_templates/tmpl/template/default.php
index 4320ad44335b3..7985896073716 100644
--- a/administrator/components/com_templates/tmpl/template/default.php
+++ b/administrator/components/com_templates/tmpl/template/default.php
@@ -1,4 +1,5 @@
useScript('form.validate')
- ->useScript('keepalive')
- ->useScript('diff')
- ->useScript('com_templates.admin-template-compare')
- ->useScript('com_templates.admin-template-toggle-switch');
+ ->useScript('keepalive')
+ ->useScript('diff')
+ ->useScript('com_templates.admin-template-compare')
+ ->useScript('com_templates.admin-template-toggle-switch');
// No access if not global SuperUser
-if (!Factory::getUser()->authorise('core.admin'))
-{
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'danger');
+if (!Factory::getUser()->authorise('core.admin')) {
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'danger');
}
-if ($this->type == 'image')
-{
- $wa->usePreset('cropperjs');
+if ($this->type == 'image') {
+ $wa->usePreset('cropperjs');
}
$wa->useStyle('com_templates.admin-templates')
- ->useScript('com_templates.admin-templates');
+ ->useScript('com_templates.admin-templates');
-if ($this->type == 'font')
-{
- $wa->addInlineStyle("
+if ($this->type == 'font') {
+ $wa->addInlineStyle("
@font-face {
font-family: previewFont;
src: url('" . $this->font['address'] . "')
@@ -59,411 +57,411 @@
?>
- 'editor', 'recall' => true, 'breakpoint' => 768]); ?>
-
-
-
- type == 'file') : ?>
-
get('isMedia', 0) ? '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . str_replace('//', '/', base64_decode($this->file)) : '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element . str_replace('//', '/', base64_decode($this->file))), $this->template->element); ?>
-
source->filename; ?>
-
- type == 'image') : ?>
-
image['path'], $this->template->element); ?>
-
image['path']; ?>
-
- type == 'font') : ?>
-
font['rel_path'], $this->template->element); ?>
-
font['rel_path']; ?>
-
-
- type == 'file' && !empty($this->source->coreFile)) : ?>
-
-
- form->renderField('show_core'); ?>
- form->renderField('show_diff'); ?>
-
-
-
-
-
-
-
-
- mediaFiles)) : ?>
-
-
-
-
-
-
- type == 'home') : ?>
-
-
- type == 'file') : ?>
-
-
- source->filename); ?>
- source->coreFile)) : ?>
-
-
-
-
- source->coreFile)) : ?>
- source->coreFile); ?>
- source->filePath); ?>
-
-
-
- form->getInput('core'); ?>
-
-
-
-
-
- type == 'archive') : ?>
-
-
- type == 'image') : ?>
- escape(basename($this->image['address'])); ?>
-
-
- type == 'font') : ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
- overridesList['modules'] as $module) : ?>
-
- path
- . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
- ?>
-
- name; ?>
-
-
-
-
-
-
-
-
-
-
-
- overridesList['components'] as $key => $value) : ?>
-
-
-
-
-
-
-
- path
- . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
- ?>
-
- name; ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- overridesList['plugins'] as $key => $group) : ?>
-
-
-
-
-
-
-
- path
- . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
- ?>
-
- name; ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- overridesList['layouts'] as $key => $value) : ?>
-
-
-
-
-
-
-
- path
- . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&' . $token . '&isMedia=' . $input->get('isMedia', 0);
- ?>
-
- name; ?>
-
-
-
-
-
-
-
-
-
-
-
+ 'editor', 'recall' => true, 'breakpoint' => 768]); ?>
+
+
+
+ type == 'file') : ?>
+
get('isMedia', 0) ? '/media/templates/' . ($this->template->client_id === 0 ? 'site' : 'administrator') . '/' . $this->template->element . str_replace('//', '/', base64_decode($this->file)) : '/' . ($this->template->client_id === 0 ? '' : 'administrator/') . 'templates/' . $this->template->element . str_replace('//', '/', base64_decode($this->file))), $this->template->element); ?>
+
source->filename; ?>
+
+ type == 'image') : ?>
+
image['path'], $this->template->element); ?>
+
image['path']; ?>
+
+ type == 'font') : ?>
+
font['rel_path'], $this->template->element); ?>
+
font['rel_path']; ?>
+
+
+ type == 'file' && !empty($this->source->coreFile)) : ?>
+
+
+ form->renderField('show_core'); ?>
+ form->renderField('show_diff'); ?>
+
+
+
+
+
+
+
+
+ mediaFiles)) : ?>
+
+
+
+
+
+
+ type == 'home') : ?>
+
+
+ type == 'file') : ?>
+
+
+ source->filename); ?>
+ source->coreFile)) : ?>
+
+
+
+
+ source->coreFile)) : ?>
+ source->coreFile); ?>
+ source->filePath); ?>
+
+
+
+ form->getInput('core'); ?>
+
+
+
+
+
+ type == 'archive') : ?>
+
+
+ type == 'image') : ?>
+ escape(basename($this->image['address'])); ?>
+
+
+ type == 'font') : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ overridesList['modules'] as $module) : ?>
+
+ path
+ . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
+ ?>
+
+ name; ?>
+
+
+
+
+
+
+
+
+
+
+
+ overridesList['components'] as $key => $value) : ?>
+
+
+
+
+
+
+
+ path
+ . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
+ ?>
+
+ name; ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ overridesList['plugins'] as $key => $group) : ?>
+
+
+
+
+
+
+
+ path
+ . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&isMedia=' . $input->get('isMedia', 0) . '&' . $token;
+ ?>
+
+ name; ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ overridesList['layouts'] as $key => $value) : ?>
+
+
+
+
+
+
+
+ path
+ . '&id=' . $input->getInt('id') . '&file=' . $this->file . '&' . $token . '&isMedia=' . $input->get('isMedia', 0);
+ ?>
+
+ name; ?>
+
+
+
+
+
+
+
+
+
+
+
- pluginState) : ?>
-
- loadTemplate('updated_files'); ?>
-
-
+ pluginState) : ?>
+
+ loadTemplate('updated_files'); ?>
+
+
-
-
-
- loadTemplate('description'); ?>
-
-
-
+
+
+
+ loadTemplate('description'); ?>
+
+
+
-
+
- template->xmldata->inheritable) && (string) $this->template->xmldata->inheritable === '1' ? 'child' : 'copy';
- $copyModalData = array(
- 'selector' => $taskName . 'Modal',
- 'params' => array(
- 'title' => Text::_('COM_TEMPLATES_TEMPLATE_' . strtoupper($taskName)),
- 'footer' => $this->loadTemplate('modal_' . $taskName . '_footer')
- ),
- 'body' => $this->loadTemplate('modal_' . $taskName . '_body')
- );
- ?>
-
- type != 'home') : ?>
- 'renameModal',
- 'params' => array(
- 'title' => Text::sprintf('COM_TEMPLATES_RENAME_FILE', str_replace('//', '/', $this->fileName)),
- 'footer' => $this->loadTemplate('modal_rename_footer')
- ),
- 'body' => $this->loadTemplate('modal_rename_body')
- );
- ?>
-
-
- type != 'home') : ?>
- 'deleteModal',
- 'params' => array(
- 'title' => Text::_('COM_TEMPLATES_ARE_YOU_SURE'),
- 'footer' => $this->loadTemplate('modal_delete_footer')
- ),
- 'body' => $this->loadTemplate('modal_delete_body')
- );
- ?>
-
-
- 'fileModal',
- 'params' => array(
- 'title' => Text::_('COM_TEMPLATES_NEW_FILE_HEADER'),
- 'footer' => $this->loadTemplate('modal_file_footer'),
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- ),
- 'body' => $this->loadTemplate('modal_file_body')
- );
- ?>
-
- 'folderModal',
- 'params' => array(
- 'title' => Text::_('COM_TEMPLATES_MANAGE_FOLDERS'),
- 'footer' => $this->loadTemplate('modal_folder_footer'),
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- ),
- 'body' => $this->loadTemplate('modal_folder_body')
- );
- ?>
-
- type == 'image') : ?>
- 'resizeModal',
- 'params' => array(
- 'title' => Text::_('COM_TEMPLATES_RESIZE_IMAGE'),
- 'footer' => $this->loadTemplate('modal_resize_footer')
- ),
- 'body' => $this->loadTemplate('modal_resize_body')
- );
- ?>
-
-
+ template->xmldata->inheritable) && (string) $this->template->xmldata->inheritable === '1' ? 'child' : 'copy';
+ $copyModalData = array(
+ 'selector' => $taskName . 'Modal',
+ 'params' => array(
+ 'title' => Text::_('COM_TEMPLATES_TEMPLATE_' . strtoupper($taskName)),
+ 'footer' => $this->loadTemplate('modal_' . $taskName . '_footer')
+ ),
+ 'body' => $this->loadTemplate('modal_' . $taskName . '_body')
+ );
+ ?>
+
+ type != 'home') : ?>
+ 'renameModal',
+ 'params' => array(
+ 'title' => Text::sprintf('COM_TEMPLATES_RENAME_FILE', str_replace('//', '/', $this->fileName)),
+ 'footer' => $this->loadTemplate('modal_rename_footer')
+ ),
+ 'body' => $this->loadTemplate('modal_rename_body')
+ );
+ ?>
+
+
+ type != 'home') : ?>
+ 'deleteModal',
+ 'params' => array(
+ 'title' => Text::_('COM_TEMPLATES_ARE_YOU_SURE'),
+ 'footer' => $this->loadTemplate('modal_delete_footer')
+ ),
+ 'body' => $this->loadTemplate('modal_delete_body')
+ );
+ ?>
+
+
+ 'fileModal',
+ 'params' => array(
+ 'title' => Text::_('COM_TEMPLATES_NEW_FILE_HEADER'),
+ 'footer' => $this->loadTemplate('modal_file_footer'),
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ ),
+ 'body' => $this->loadTemplate('modal_file_body')
+ );
+ ?>
+
+ 'folderModal',
+ 'params' => array(
+ 'title' => Text::_('COM_TEMPLATES_MANAGE_FOLDERS'),
+ 'footer' => $this->loadTemplate('modal_folder_footer'),
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ ),
+ 'body' => $this->loadTemplate('modal_folder_body')
+ );
+ ?>
+
+ type == 'image') : ?>
+ 'resizeModal',
+ 'params' => array(
+ 'title' => Text::_('COM_TEMPLATES_RESIZE_IMAGE'),
+ 'footer' => $this->loadTemplate('modal_resize_footer')
+ ),
+ 'body' => $this->loadTemplate('modal_resize_body')
+ );
+ ?>
+
+
diff --git a/administrator/components/com_templates/tmpl/template/default_description.php b/administrator/components/com_templates/tmpl/template/default_description.php
index 24119159bcbb6..72afa2cbde22c 100644
--- a/administrator/components/com_templates/tmpl/template/default_description.php
+++ b/administrator/components/com_templates/tmpl/template/default_description.php
@@ -1,4 +1,5 @@
-
- template); ?>
- template); ?>
-
-
template->element); ?>
- template->client_id); ?>
-
template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $this->template->element); ?>
-
template->xmldata->get('description')); ?>
+
+ template); ?>
+ template); ?>
+
+
template->element); ?>
+ template->client_id); ?>
+
template->xmldata = TemplatesHelper::parseXMLTemplateFile($client->path, $this->template->element); ?>
+
template->xmldata->get('description')); ?>
diff --git a/administrator/components/com_templates/tmpl/template/default_folders.php b/administrator/components/com_templates/tmpl/template/default_folders.php
index 400efb3cb1f6f..1675dab6e7b1f 100644
--- a/administrator/components/com_templates/tmpl/template/default_folders.php
+++ b/administrator/components/com_templates/tmpl/template/default_folders.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/default_media_folders.php b/administrator/components/com_templates/tmpl/template/default_media_folders.php
index 38ea9c2d0fafa..f3491ab3a8fa8 100644
--- a/administrator/components/com_templates/tmpl/template/default_media_folders.php
+++ b/administrator/components/com_templates/tmpl/template/default_media_folders.php
@@ -1,4 +1,5 @@
mediaFiles))
-{
- return;
+if (!count($this->mediaFiles)) {
+ return;
}
ksort($this->mediaFiles, SORT_STRING);
?>
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_child_body.php b/administrator/components/com_templates/tmpl/template/default_modal_child_body.php
index 3b9b83e6414a4..507b0767372af 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_child_body.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_child_body.php
@@ -1,4 +1,5 @@
styles) > 0)
-{
- foreach ($this->styles as $style)
- {
- $options[] = HTMLHelper::_('select.option', $style->id, $style->title, 'value', 'text');
- }
+if (count($this->styles) > 0) {
+ foreach ($this->styles as $style) {
+ $options[] = HTMLHelper::_('select.option', $style->id, $style->title, 'value', 'text');
+ }
}
$fancySelectData = [
- 'autocomplete' => 'off',
- 'autofocus' => false,
- 'class' => '',
- 'description' => '',
- 'disabled' => false,
- 'group' => false,
- 'id' => 'style_ids',
- 'hidden' => false,
- 'hint' => '',
- 'label' => '',
- 'labelclass' => '',
- 'onchange' => '',
- 'onclick' => '',
- 'multiple' => true,
- 'pattern' => '',
- 'readonly' => false,
- 'repeat' => false,
- 'required' => false,
- 'size' => 4,
- 'spellcheck' => false,
- 'validate' => '',
- 'value' => '0',
- 'options' => $options,
- 'dataAttributes' => [],
- 'dataAttribute' => '',
- 'name' => 'style_ids[]',
+ 'autocomplete' => 'off',
+ 'autofocus' => false,
+ 'class' => '',
+ 'description' => '',
+ 'disabled' => false,
+ 'group' => false,
+ 'id' => 'style_ids',
+ 'hidden' => false,
+ 'hint' => '',
+ 'label' => '',
+ 'labelclass' => '',
+ 'onchange' => '',
+ 'onclick' => '',
+ 'multiple' => true,
+ 'pattern' => '',
+ 'readonly' => false,
+ 'repeat' => false,
+ 'required' => false,
+ 'size' => 4,
+ 'spellcheck' => false,
+ 'validate' => '',
+ 'value' => '0',
+ 'options' => $options,
+ 'dataAttributes' => [],
+ 'dataAttribute' => '',
+ 'name' => 'style_ids[]',
];
?>
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_child_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_child_footer.php
index 8f0188e2952ec..f1c4dfb6d72f3 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_child_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_child_footer.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_copy_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_copy_footer.php
index 9eeeff74b40a7..17d0f3f17a4de 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_copy_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_copy_footer.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_delete_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_delete_footer.php
index 89a1b12fcd307..9895aadd69b81 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_delete_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_delete_footer.php
@@ -1,4 +1,5 @@
input;
?>
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_file_body.php b/administrator/components/com_templates/tmpl/template/default_modal_file_body.php
index d3dbea84b4f32..926cd1fec345d 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_file_body.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_file_body.php
@@ -19,87 +19,87 @@
$input = Factory::getApplication()->input;
?>
-
-
-
-
-
- mediaFiles)) : ?>
-
-
-
-
-
-
-
-
- type != 'home') : ?>
-
-
-
-
-
-
+
+
+
+
+
+ mediaFiles)) : ?>
+
+
+
+
+
+
+
+
+ type != 'home') : ?>
+
+
+
+
+
+
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_file_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_file_footer.php
index 25a77c25295b8..2a58ea613436e 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_file_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_file_footer.php
@@ -1,4 +1,5 @@
input;
?>
-
-
-
-
-
- mediaFiles)) : ?>
-
-
-
-
-
-
-
+
+
+
+
+
+ mediaFiles)) : ?>
+
+
+
+
+
+
+
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_folder_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_folder_footer.php
index 3f0c071ceb171..1fbf670f9b7b3 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_folder_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_folder_footer.php
@@ -1,4 +1,5 @@
input;
?>
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_rename_body.php b/administrator/components/com_templates/tmpl/template/default_modal_rename_body.php
index d954ebb64d839..531e6d7f49c57 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_rename_body.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_rename_body.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_rename_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_rename_footer.php
index fd6c82fb5222e..d0bd1b966997b 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_rename_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_rename_footer.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/default_modal_resize_footer.php b/administrator/components/com_templates/tmpl/template/default_modal_resize_footer.php
index 204efeeaca772..9b4601b2ea1f9 100644
--- a/administrator/components/com_templates/tmpl/template/default_modal_resize_footer.php
+++ b/administrator/components/com_templates/tmpl/template/default_modal_resize_footer.php
@@ -1,4 +1,5 @@
- files as $key => $value) : ?>
-
- fileName);
- $count = 0;
-
- $keyArrayCount = count($keyArray);
-
- if (count($fileArray) >= $keyArrayCount)
- {
- for ($i = 0; $i < $keyArrayCount; $i++)
- {
- if ($keyArray[$i] === $fileArray[$i])
- {
- $count++;
- }
- }
-
- if ($count === $keyArrayCount)
- {
- $class = 'folder show';
- }
- else
- {
- $class = 'folder';
- }
- }
- else
- {
- $class = 'folder';
- }
-
- ?>
-
-
- escape(end($explodeArray)); ?>
-
- directoryTree($value); ?>
-
-
-
-
- id . '&file=' . $value->id . '&isMedia=0'); ?>'>
- escape($value->name); ?>
-
-
-
-
+ files as $key => $value) : ?>
+
+ fileName);
+ $count = 0;
+
+ $keyArrayCount = count($keyArray);
+
+ if (count($fileArray) >= $keyArrayCount) {
+ for ($i = 0; $i < $keyArrayCount; $i++) {
+ if ($keyArray[$i] === $fileArray[$i]) {
+ $count++;
+ }
+ }
+
+ if ($count === $keyArrayCount) {
+ $class = 'folder show';
+ } else {
+ $class = 'folder';
+ }
+ } else {
+ $class = 'folder';
+ }
+
+ ?>
+
+
+ escape(end($explodeArray)); ?>
+
+ directoryTree($value); ?>
+
+
+
+
+ id . '&file=' . $value->id . '&isMedia=0'); ?>'>
+ escape($value->name); ?>
+
+
+
+
diff --git a/administrator/components/com_templates/tmpl/template/default_tree_media.php b/administrator/components/com_templates/tmpl/template/default_tree_media.php
index c32114297f9e1..32c014bda09fd 100644
--- a/administrator/components/com_templates/tmpl/template/default_tree_media.php
+++ b/administrator/components/com_templates/tmpl/template/default_tree_media.php
@@ -1,4 +1,5 @@
mediaFiles))
-{
- return;
+if (!count($this->mediaFiles)) {
+ return;
}
ksort($this->mediaFiles, SORT_STRING);
?>
- mediaFiles as $key => $value) : ?>
-
- fileName);
- $count = 0;
+ mediaFiles as $key => $value) : ?>
+
+ fileName);
+ $count = 0;
- $keyArrayCount = count($keyArray);
+ $keyArrayCount = count($keyArray);
- if (count($fileArray) >= $keyArrayCount)
- {
- for ($i = 0; $i < $keyArrayCount; $i++)
- {
- if ($keyArray[$i] === $fileArray[$i])
- {
- $count++;
- }
- }
+ if (count($fileArray) >= $keyArrayCount) {
+ for ($i = 0; $i < $keyArrayCount; $i++) {
+ if ($keyArray[$i] === $fileArray[$i]) {
+ $count++;
+ }
+ }
- if ($count === $keyArrayCount)
- {
- $class = 'folder show';
- }
- else
- {
- $class = 'folder';
- }
- }
- else
- {
- $class = 'folder';
- }
+ if ($count === $keyArrayCount) {
+ $class = 'folder show';
+ } else {
+ $class = 'folder';
+ }
+ } else {
+ $class = 'folder';
+ }
- ?>
-
-
- escape(end($explodeArray)); ?>
-
- mediaTree($value); ?>
-
-
-
-
-
- escape($value->name); ?>
-
-
-
-
+ ?>
+
+
+ escape(end($explodeArray)); ?>
+
+ mediaTree($value); ?>
+
+
+
+
+
+ escape($value->name); ?>
+
+
+
+
diff --git a/administrator/components/com_templates/tmpl/template/default_updated_files.php b/administrator/components/com_templates/tmpl/template/default_updated_files.php
index 385122cbd610f..699340f344efa 100644
--- a/administrator/components/com_templates/tmpl/template/default_updated_files.php
+++ b/administrator/components/com_templates/tmpl/template/default_updated_files.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_templates/tmpl/template/readonly.php b/administrator/components/com_templates/tmpl/template/readonly.php
index 157cff97362c5..0d9f62bcce178 100644
--- a/administrator/components/com_templates/tmpl/template/readonly.php
+++ b/administrator/components/com_templates/tmpl/template/readonly.php
@@ -1,4 +1,5 @@
input;
?>
diff --git a/administrator/components/com_templates/tmpl/templates/default.php b/administrator/components/com_templates/tmpl/templates/default.php
index 42959af23cca7..231f899bccd57 100644
--- a/administrator/components/com_templates/tmpl/templates/default.php
+++ b/administrator/components/com_templates/tmpl/templates/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -26,117 +27,117 @@
?>
diff --git a/administrator/components/com_users/helpers/debug.php b/administrator/components/com_users/helpers/debug.php
index d1dc18a415293..f5f81670928f8 100644
--- a/administrator/components/com_users/helpers/debug.php
+++ b/administrator/components/com_users/helpers/debug.php
@@ -1,13 +1,14 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
- */
-defined('_JEXEC') or die;
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
+ */
use Joomla\Component\Users\Administrator\Helper\DebugHelper;
diff --git a/administrator/components/com_users/helpers/users.php b/administrator/components/com_users/helpers/users.php
index f0d556c782f20..c56a1fef95d07 100644
--- a/administrator/components/com_users/helpers/users.php
+++ b/administrator/components/com_users/helpers/users.php
@@ -1,13 +1,16 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* Users component helper.
diff --git a/administrator/components/com_users/postinstall/multifactorauth.php b/administrator/components/com_users/postinstall/multifactorauth.php
index d0989284c8ab8..26bc66f3aaa1b 100644
--- a/administrator/components/com_users/postinstall/multifactorauth.php
+++ b/administrator/components/com_users/postinstall/multifactorauth.php
@@ -1,4 +1,5 @@
get('DatabaseDriver');
- $coreMfaPlugins = ['email', 'totp', 'webauthn', 'yubikey'];
+ /** @var DatabaseDriver $db */
+ $db = Factory::getContainer()->get('DatabaseDriver');
+ $coreMfaPlugins = ['email', 'totp', 'webauthn', 'yubikey'];
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__extensions'))
- ->set($db->quoteName('enabled') . ' = 1')
- ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
- ->where($db->quoteName('folder') . ' = ' . $db->quote('multifactorauth'))
- ->whereIn($db->quoteName('element'), $coreMfaPlugins, ParameterType::STRING);
- $db->setQuery($query);
- $db->execute();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__extensions'))
+ ->set($db->quoteName('enabled') . ' = 1')
+ ->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('multifactorauth'))
+ ->whereIn($db->quoteName('element'), $coreMfaPlugins, ParameterType::STRING);
+ $db->setQuery($query);
+ $db->execute();
- $url = 'index.php?option=com_plugins&filter[folder]=multifactorauth';
- Factory::getApplication()->redirect($url);
+ $url = 'index.php?option=com_plugins&filter[folder]=multifactorauth';
+ Factory::getApplication()->redirect($url);
}
diff --git a/administrator/components/com_users/services/provider.php b/administrator/components/com_users/services/provider.php
index b2efe40383c44..d444883bf62c0 100644
--- a/administrator/components/com_users/services/provider.php
+++ b/administrator/components/com_users/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Users'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Users'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Users'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Users'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Users'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Users'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new UsersComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- $component->setRegistry($container->get(Registry::class));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new UsersComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ $component->setRegistry($container->get(Registry::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_users/src/Controller/CallbackController.php b/administrator/components/com_users/src/Controller/CallbackController.php
index debe38bdd938a..63ee61ad2162a 100644
--- a/administrator/components/com_users/src/Controller/CallbackController.php
+++ b/administrator/components/com_users/src/Controller/CallbackController.php
@@ -1,4 +1,5 @@
registerDefaultTask('callback');
- }
+ $this->registerDefaultTask('callback');
+ }
- /**
- * Implement a callback feature, typically used for OAuth2 authentication
- *
- * @param bool $cachable Can this view be cached
- * @param array|bool $urlparams An array of safe url parameters and their variable types, for valid values see
- * {@link JFilterInput::clean()}.
- *
- * @return void
- * @since 4.2.0
- */
- public function callback($cachable = false, $urlparams = false): void
- {
- $app = $this->app;
+ /**
+ * Implement a callback feature, typically used for OAuth2 authentication
+ *
+ * @param bool $cachable Can this view be cached
+ * @param array|bool $urlparams An array of safe url parameters and their variable types, for valid values see
+ * {@link JFilterInput::clean()}.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function callback($cachable = false, $urlparams = false): void
+ {
+ $app = $this->app;
- // Get the Method and make sure it's non-empty
- $method = $this->input->getCmd('method', '');
+ // Get the Method and make sure it's non-empty
+ $method = $this->input->getCmd('method', '');
- if (empty($method))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ if (empty($method)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- PluginHelper::importPlugin('multifactorauth');
+ PluginHelper::importPlugin('multifactorauth');
- $event = new Callback($method);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
+ $event = new Callback($method);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
- /**
- * The first plugin to handle the request should either redirect or close the application. If we are still here
- * no plugin handled the request successfully. Show an error.
- */
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * The first plugin to handle the request should either redirect or close the application. If we are still here
+ * no plugin handled the request successfully. Show an error.
+ */
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/CaptiveController.php b/administrator/components/com_users/src/Controller/CaptiveController.php
index 384840b98a0f0..a4d9e8ab798e9 100644
--- a/administrator/components/com_users/src/Controller/CaptiveController.php
+++ b/administrator/components/com_users/src/Controller/CaptiveController.php
@@ -1,4 +1,5 @@
registerTask('captive', 'display');
- }
-
- /**
- * Displays the captive login page
- *
- * @param boolean $cachable Ignored. This page is never cached.
- * @param boolean|array $urlparams Ignored. This page is never cached.
- *
- * @return void
- * @throws Exception
- * @since 4.2.0
- */
- public function display($cachable = false, $urlparams = false): void
- {
- $user = $this->app->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- // Only allow logged in Users
- if ($user->guest)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- // Get the view object
- $viewLayout = $this->input->get('layout', 'default', 'string');
- $view = $this->getView('Captive', 'html', '',
- [
- 'base_path' => $this->basePath,
- 'layout' => $viewLayout,
- ]
- );
-
- $view->document = $this->app->getDocument();
-
- // If we're already logged in go to the site's home page
- if ((int) $this->app->getSession()->get('com_users.mfa_checked', 0) === 1)
- {
- $url = Route::_('index.php?option=com_users&task=methods.display', false);
-
- $this->setRedirect($url);
- }
-
- // Pass the model to the view
- /** @var CaptiveModel $model */
- $model = $this->getModel('Captive');
- $view->setModel($model, true);
-
- /** @var BackupcodesModel $codesModel */
- $codesModel = $this->getModel('Backupcodes');
- $view->setModel($codesModel, false);
-
- try
- {
- // Suppress all modules on the page except those explicitly allowed
- $model->suppressAllModules();
- }
- catch (Exception $e)
- {
- // If we can't kill the modules we can still survive.
- }
-
- // Pass the MFA record ID to the model
- $recordId = $this->input->getInt('record_id', null);
- $model->setState('record_id', $recordId);
-
- // Do not go through $this->display() because it overrides the model.
- $view->display();
- }
-
- /**
- * Validate the MFA code entered by the user
- *
- * @param bool $cachable Ignored. This page is never cached.
- * @param array $urlparameters Ignored. This page is never cached.
- *
- * @return void
- * @throws Exception
- * @since 4.2.0
- */
- public function validate($cachable = false, $urlparameters = [])
- {
- // CSRF Check
- $this->checkToken($this->input->getMethod());
-
- // Get the MFA parameters from the request
- $recordId = $this->input->getInt('record_id', null);
- $code = $this->input->get('code', null, 'raw');
- /** @var CaptiveModel $model */
- $model = $this->getModel('Captive');
-
- // Validate the MFA record
- $model->setState('record_id', $recordId);
- $record = $model->getRecord();
-
- if (empty($record))
- {
- $event = new NotifyActionLog('onComUsersCaptiveValidateInvalidMethod');
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- throw new RuntimeException(Text::_('COM_USERS_MFA_INVALID_METHOD'), 500);
- }
-
- // Validate the code
- $user = $this->app->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- $event = new Validate($record, $user, $code);
- $results = $this->app
- ->getDispatcher()
- ->dispatch($event->getName(), $event)
- ->getArgument('result', []);
-
- $isValidCode = false;
-
- if ($record->method === 'backupcodes')
- {
- /** @var BackupcodesModel $codesModel */
- $codesModel = $this->getModel('Backupcodes');
- $results = [$codesModel->isBackupCode($code, $user)];
- /**
- * This is required! Do not remove!
- *
- * There is a store() call below. It saves the in-memory MFA record to the database. That includes the
- * options key which contains the configuration of the Method. For backup codes, these are the actual codes
- * you can use. When we check for a backup code validity we also "burn" it, i.e. we remove it from the
- * options table and save that to the database. However, this DOES NOT update the $record here. Therefore
- * the call to saveRecord() would overwrite the database contents with a record that _includes_ the backup
- * code we had just burned. As a result the single use backup codes end up being multiple use.
- *
- * By doing a getRecord() here, right after we have "burned" any correct backup codes, we resolve this
- * issue. The loaded record will reflect the database contents where the options DO NOT include the code we
- * just used. Therefore the call to store() will result in the correct database state, i.e. the used backup
- * code being removed.
- */
- $record = $model->getRecord();
- }
-
- $isValidCode = array_reduce(
- $results,
- function (bool $carry, $result)
- {
- return $carry || boolval($result);
- },
- false
- );
-
- if (!$isValidCode)
- {
- // The code is wrong. Display an error and go back.
- $captiveURL = Route::_('index.php?option=com_users&view=captive&record_id=' . $recordId, false);
- $message = Text::_('COM_USERS_MFA_INVALID_CODE');
- $this->setRedirect($captiveURL, $message, 'error');
-
- $event = new NotifyActionLog('onComUsersCaptiveValidateFailed', [$record->title]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- return;
- }
-
- // Update the Last Used, UA and IP columns
- $jNow = Date::getInstance();
+ /**
+ * Public constructor
+ *
+ * @param array $config Plugin configuration
+ * @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
+ * @param CMSApplication|null $app CMS application object
+ * @param Input|null $input Joomla CMS input object
+ *
+ * @since 4.2.0
+ */
+ public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('captive', 'display');
+ }
+
+ /**
+ * Displays the captive login page
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function display($cachable = false, $urlparams = false): void
+ {
+ $user = $this->app->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ // Only allow logged in Users
+ if ($user->guest) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ // Get the view object
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+ $view = $this->getView(
+ 'Captive',
+ 'html',
+ '',
+ [
+ 'base_path' => $this->basePath,
+ 'layout' => $viewLayout,
+ ]
+ );
+
+ $view->document = $this->app->getDocument();
+
+ // If we're already logged in go to the site's home page
+ if ((int) $this->app->getSession()->get('com_users.mfa_checked', 0) === 1) {
+ $url = Route::_('index.php?option=com_users&task=methods.display', false);
+
+ $this->setRedirect($url);
+ }
+
+ // Pass the model to the view
+ /** @var CaptiveModel $model */
+ $model = $this->getModel('Captive');
+ $view->setModel($model, true);
+
+ /** @var BackupcodesModel $codesModel */
+ $codesModel = $this->getModel('Backupcodes');
+ $view->setModel($codesModel, false);
+
+ try {
+ // Suppress all modules on the page except those explicitly allowed
+ $model->suppressAllModules();
+ } catch (Exception $e) {
+ // If we can't kill the modules we can still survive.
+ }
+
+ // Pass the MFA record ID to the model
+ $recordId = $this->input->getInt('record_id', null);
+ $model->setState('record_id', $recordId);
+
+ // Do not go through $this->display() because it overrides the model.
+ $view->display();
+ }
+
+ /**
+ * Validate the MFA code entered by the user
+ *
+ * @param bool $cachable Ignored. This page is never cached.
+ * @param array $urlparameters Ignored. This page is never cached.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function validate($cachable = false, $urlparameters = [])
+ {
+ // CSRF Check
+ $this->checkToken($this->input->getMethod());
+
+ // Get the MFA parameters from the request
+ $recordId = $this->input->getInt('record_id', null);
+ $code = $this->input->get('code', null, 'raw');
+ /** @var CaptiveModel $model */
+ $model = $this->getModel('Captive');
+
+ // Validate the MFA record
+ $model->setState('record_id', $recordId);
+ $record = $model->getRecord();
+
+ if (empty($record)) {
+ $event = new NotifyActionLog('onComUsersCaptiveValidateInvalidMethod');
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ throw new RuntimeException(Text::_('COM_USERS_MFA_INVALID_METHOD'), 500);
+ }
+
+ // Validate the code
+ $user = $this->app->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ $event = new Validate($record, $user, $code);
+ $results = $this->app
+ ->getDispatcher()
+ ->dispatch($event->getName(), $event)
+ ->getArgument('result', []);
+
+ $isValidCode = false;
+
+ if ($record->method === 'backupcodes') {
+ /** @var BackupcodesModel $codesModel */
+ $codesModel = $this->getModel('Backupcodes');
+ $results = [$codesModel->isBackupCode($code, $user)];
+ /**
+ * This is required! Do not remove!
+ *
+ * There is a store() call below. It saves the in-memory MFA record to the database. That includes the
+ * options key which contains the configuration of the Method. For backup codes, these are the actual codes
+ * you can use. When we check for a backup code validity we also "burn" it, i.e. we remove it from the
+ * options table and save that to the database. However, this DOES NOT update the $record here. Therefore
+ * the call to saveRecord() would overwrite the database contents with a record that _includes_ the backup
+ * code we had just burned. As a result the single use backup codes end up being multiple use.
+ *
+ * By doing a getRecord() here, right after we have "burned" any correct backup codes, we resolve this
+ * issue. The loaded record will reflect the database contents where the options DO NOT include the code we
+ * just used. Therefore the call to store() will result in the correct database state, i.e. the used backup
+ * code being removed.
+ */
+ $record = $model->getRecord();
+ }
+
+ $isValidCode = array_reduce(
+ $results,
+ function (bool $carry, $result) {
+ return $carry || boolval($result);
+ },
+ false
+ );
+
+ if (!$isValidCode) {
+ // The code is wrong. Display an error and go back.
+ $captiveURL = Route::_('index.php?option=com_users&view=captive&record_id=' . $recordId, false);
+ $message = Text::_('COM_USERS_MFA_INVALID_CODE');
+ $this->setRedirect($captiveURL, $message, 'error');
+
+ $event = new NotifyActionLog('onComUsersCaptiveValidateFailed', [$record->title]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ return;
+ }
+
+ // Update the Last Used, UA and IP columns
+ $jNow = Date::getInstance();
// phpcs:ignore
$record->last_used = $jNow->toSql();
- $record->store();
+ $record->store();
- // Flag the user as fully logged in
- $session = $this->app->getSession();
- $session->set('com_users.mfa_checked', 1);
- $session->set('com_users.mandatory_mfa_setup', 0);
+ // Flag the user as fully logged in
+ $session = $this->app->getSession();
+ $session->set('com_users.mfa_checked', 1);
+ $session->set('com_users.mandatory_mfa_setup', 0);
- // Get the return URL stored by the plugin in the session
- $returnUrl = $session->get('com_users.return_url', '');
+ // Get the return URL stored by the plugin in the session
+ $returnUrl = $session->get('com_users.return_url', '');
- // If the return URL is not set or not internal to this site redirect to the site's front page
- if (empty($returnUrl) || !Uri::isInternal($returnUrl))
- {
- $returnUrl = Uri::base();
- }
+ // If the return URL is not set or not internal to this site redirect to the site's front page
+ if (empty($returnUrl) || !Uri::isInternal($returnUrl)) {
+ $returnUrl = Uri::base();
+ }
- $this->setRedirect($returnUrl);
+ $this->setRedirect($returnUrl);
- $event = new NotifyActionLog('onComUsersCaptiveValidateSuccess', [$record->title]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
- }
+ $event = new NotifyActionLog('onComUsersCaptiveValidateSuccess', [$record->title]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/DisplayController.php b/administrator/components/com_users/src/Controller/DisplayController.php
index 018bbde7b6782..cbb9042dc22b7 100644
--- a/administrator/components/com_users/src/Controller/DisplayController.php
+++ b/administrator/components/com_users/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
get('core.admin');
-
- // Default permissions.
- default:
- return true;
- }
- }
-
- /**
- * Method to display a view.
- *
- * @param boolean $cachable If true, the view output will be cached
- * @param array $urlparams An array of safe URL parameters and their variable types,
- * for valid values see {@link \Joomla\CMS\Filter\InputFilter::clean()}.
- *
- * @return BaseController|boolean This object to support chaining or false on failure.
- *
- * @since 1.5
- */
- public function display($cachable = false, $urlparams = array())
- {
- $view = $this->input->get('view', 'users');
- $layout = $this->input->get('layout', 'default');
- $id = $this->input->getInt('id');
-
- if (!$this->canView($view))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- // Check for edit form.
- if ($view == 'user' && $layout == 'edit' && !$this->checkEditId('com_users.edit.user', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_users&view=users', false));
-
- return false;
- }
- elseif ($view == 'group' && $layout == 'edit' && !$this->checkEditId('com_users.edit.group', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_users&view=groups', false));
-
- return false;
- }
- elseif ($view == 'level' && $layout == 'edit' && !$this->checkEditId('com_users.edit.level', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_users&view=levels', false));
-
- return false;
- }
- elseif ($view == 'note' && $layout == 'edit' && !$this->checkEditId('com_users.edit.note', $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $this->setRedirect(Route::_('index.php?option=com_users&view=notes', false));
-
- return false;
- }
- elseif (in_array($view, ['captive', 'callback', 'methods', 'method']))
- {
- $controller = $this->factory->createController($view, 'Administrator', [], $this->app, $this->input);
- $task = $this->input->get('task', '');
-
- return $controller->execute($task);
- }
-
- return parent::display($cachable, $urlparams);
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $default_view = 'users';
+
+ /**
+ * Checks whether a user can see this view.
+ *
+ * @param string $view The view name.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function canView($view)
+ {
+ $canDo = ContentHelper::getActions('com_users');
+
+ switch ($view) {
+ // Special permissions.
+ case 'groups':
+ case 'group':
+ case 'levels':
+ case 'level':
+ return $canDo->get('core.admin');
+
+ // Default permissions.
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types,
+ * for valid values see {@link \Joomla\CMS\Filter\InputFilter::clean()}.
+ *
+ * @return BaseController|boolean This object to support chaining or false on failure.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view', 'users');
+ $layout = $this->input->get('layout', 'default');
+ $id = $this->input->getInt('id');
+
+ if (!$this->canView($view)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ // Check for edit form.
+ if ($view == 'user' && $layout == 'edit' && !$this->checkEditId('com_users.edit.user', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_users&view=users', false));
+
+ return false;
+ } elseif ($view == 'group' && $layout == 'edit' && !$this->checkEditId('com_users.edit.group', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_users&view=groups', false));
+
+ return false;
+ } elseif ($view == 'level' && $layout == 'edit' && !$this->checkEditId('com_users.edit.level', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_users&view=levels', false));
+
+ return false;
+ } elseif ($view == 'note' && $layout == 'edit' && !$this->checkEditId('com_users.edit.note', $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $this->setRedirect(Route::_('index.php?option=com_users&view=notes', false));
+
+ return false;
+ } elseif (in_array($view, ['captive', 'callback', 'methods', 'method'])) {
+ $controller = $this->factory->createController($view, 'Administrator', [], $this->app, $this->input);
+ $task = $this->input->get('task', '');
+
+ return $controller->execute($task);
+ }
+
+ return parent::display($cachable, $urlparams);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/GroupController.php b/administrator/components/com_users/src/Controller/GroupController.php
index d2d55aafcd778..f1555ca2a34d4 100644
--- a/administrator/components/com_users/src/Controller/GroupController.php
+++ b/administrator/components/com_users/src/Controller/GroupController.php
@@ -1,4 +1,5 @@
app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
- }
+ /**
+ * Method to check if you can save a new or existing record.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\FormController::allowSave to check the core.admin permission.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowSave($data, $key = 'id')
+ {
+ return ($this->app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
+ }
- /**
- * Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
- *
- * Checks that non-Super Admins are not editing Super Admins.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- // Check if this group is a Super Admin
- if (Access::checkGroup($data[$key], 'core.admin'))
- {
- // If I'm not a Super Admin, then disallow the edit.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- return false;
- }
- }
+ /**
+ * Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
+ *
+ * Checks that non-Super Admins are not editing Super Admins.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ // Check if this group is a Super Admin
+ if (Access::checkGroup($data[$key], 'core.admin')) {
+ // If I'm not a Super Admin, then disallow the edit.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ return false;
+ }
+ }
- return parent::allowEdit($data, $key);
- }
+ return parent::allowEdit($data, $key);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/GroupsController.php b/administrator/components/com_users/src/Controller/GroupsController.php
index 9b71d8870add9..3ce083fbd078c 100644
--- a/administrator/components/com_users/src/Controller/GroupsController.php
+++ b/administrator/components/com_users/src/Controller/GroupsController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
namespace Joomla\Component\Users\Administrator\Controller;
use Joomla\CMS\Access\Exception\NotAllowed;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
-\defined('_JEXEC') or die;
-
/**
* User groups list controller class.
*
@@ -21,120 +21,115 @@
*/
class GroupsController extends AdminController
{
- /**
- * @var string The prefix to use with controller messages.
- * @since 1.6
- */
- protected $text_prefix = 'COM_USERS_GROUPS';
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_USERS_GROUPS';
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return object The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Group', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Group', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
- /**
- * Removes an item.
- *
- * Overrides Joomla\CMS\MVC\Controller\AdminController::delete to check the core.admin permission.
- *
- * @return void
- *
- * @since 1.6
- */
- public function delete()
- {
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Removes an item.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\AdminController::delete to check the core.admin permission.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function delete()
+ {
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- parent::delete();
- }
+ parent::delete();
+ }
- /**
- * Method to publish a list of records.
- *
- * Overrides Joomla\CMS\MVC\Controller\AdminController::publish to check the core.admin permission.
- *
- * @return void
- *
- * @since 1.6
- */
- public function publish()
- {
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Method to publish a list of records.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\AdminController::publish to check the core.admin permission.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function publish()
+ {
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- parent::publish();
- }
+ parent::publish();
+ }
- /**
- * Changes the order of one or more records.
- *
- * Overrides Joomla\CMS\MVC\Controller\AdminController::reorder to check the core.admin permission.
- *
- * @return boolean True on success
- *
- * @since 1.6
- */
- public function reorder()
- {
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Changes the order of one or more records.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\AdminController::reorder to check the core.admin permission.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.6
+ */
+ public function reorder()
+ {
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- return parent::reorder();
- }
+ return parent::reorder();
+ }
- /**
- * Method to save the submitted ordering values for records.
- *
- * Overrides Joomla\CMS\MVC\Controller\AdminController::saveorder to check the core.admin permission.
- *
- * @return boolean True on success
- *
- * @since 1.6
- */
- public function saveorder()
- {
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Method to save the submitted ordering values for records.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\AdminController::saveorder to check the core.admin permission.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.6
+ */
+ public function saveorder()
+ {
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- return parent::saveorder();
- }
+ return parent::saveorder();
+ }
- /**
- * Check in of one or more records.
- *
- * Overrides Joomla\CMS\MVC\Controller\AdminController::checkin to check the core.admin permission.
- *
- * @return boolean True on success
- *
- * @since 1.6
- */
- public function checkin()
- {
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
+ /**
+ * Check in of one or more records.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\AdminController::checkin to check the core.admin permission.
+ *
+ * @return boolean True on success
+ *
+ * @since 1.6
+ */
+ public function checkin()
+ {
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
- return parent::checkin();
- }
+ return parent::checkin();
+ }
}
diff --git a/administrator/components/com_users/src/Controller/LevelController.php b/administrator/components/com_users/src/Controller/LevelController.php
index 4028dd10fb9ea..d7ac126707eeb 100644
--- a/administrator/components/com_users/src/Controller/LevelController.php
+++ b/administrator/components/com_users/src/Controller/LevelController.php
@@ -1,4 +1,5 @@
app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
- }
-
- /**
- * Overrides JControllerForm::allowEdit
- *
- * Checks that non-Super Admins are not editing Super Admins.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 3.8.8
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- // Check for if Super Admin can edit
- $viewLevel = $this->getModel('Level', 'Administrator')->getItem((int) $data['id']);
-
- // If this group is super admin and this user is not super admin, canEdit is false
- if (!$this->app->getIdentity()->authorise('core.admin') && $viewLevel->rules && Access::checkGroup($viewLevel->rules[0], 'core.admin'))
- {
- $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . $this->getRedirectToListAppend(), false
- )
- );
-
- return false;
- }
-
- return parent::allowEdit($data, $key);
- }
-
- /**
- * Removes an item.
- *
- * Overrides Joomla\CMS\MVC\Controller\FormController::delete to check the core.admin permission.
- *
- * @return void
- *
- * @since 1.6
- */
- public function delete()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (!$this->app->getIdentity()->authorise('core.admin', $this->option))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- elseif (empty($ids))
- {
- $this->setMessage(Text::_('COM_USERS_NO_LEVELS_SELECTED'), 'warning');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Remove the items.
- if ($model->delete($ids))
- {
- $this->setMessage(Text::plural('COM_USERS_N_LEVELS_DELETED', count($ids)));
- }
- }
-
- $this->setRedirect('index.php?option=com_users&view=levels');
- }
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_USERS_LEVEL';
+
+ /**
+ * Method to check if you can save a new or existing record.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\FormController::allowSave to check the core.admin permission.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 1.6
+ */
+ protected function allowSave($data, $key = 'id')
+ {
+ return ($this->app->getIdentity()->authorise('core.admin', $this->option) && parent::allowSave($data, $key));
+ }
+
+ /**
+ * Overrides JControllerForm::allowEdit
+ *
+ * Checks that non-Super Admins are not editing Super Admins.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 3.8.8
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ // Check for if Super Admin can edit
+ $viewLevel = $this->getModel('Level', 'Administrator')->getItem((int) $data['id']);
+
+ // If this group is super admin and this user is not super admin, canEdit is false
+ if (!$this->app->getIdentity()->authorise('core.admin') && $viewLevel->rules && Access::checkGroup($viewLevel->rules[0], 'core.admin')) {
+ $this->setMessage(Text::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'), 'error');
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . $this->getRedirectToListAppend(),
+ false
+ )
+ );
+
+ return false;
+ }
+
+ return parent::allowEdit($data, $key);
+ }
+
+ /**
+ * Removes an item.
+ *
+ * Overrides Joomla\CMS\MVC\Controller\FormController::delete to check the core.admin permission.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function delete()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (!$this->app->getIdentity()->authorise('core.admin', $this->option)) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ } elseif (empty($ids)) {
+ $this->setMessage(Text::_('COM_USERS_NO_LEVELS_SELECTED'), 'warning');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Remove the items.
+ if ($model->delete($ids)) {
+ $this->setMessage(Text::plural('COM_USERS_N_LEVELS_DELETED', count($ids)));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_users&view=levels');
+ }
}
diff --git a/administrator/components/com_users/src/Controller/LevelsController.php b/administrator/components/com_users/src/Controller/LevelsController.php
index d4ffb80051d27..0496bc63ed011 100644
--- a/administrator/components/com_users/src/Controller/LevelsController.php
+++ b/administrator/components/com_users/src/Controller/LevelsController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Users\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Users\Administrator\Controller;
use Joomla\CMS\MVC\Controller\AdminController;
@@ -19,25 +19,25 @@
*/
class LevelsController extends AdminController
{
- /**
- * @var string The prefix to use with controller messages.
- * @since 1.6
- */
- protected $text_prefix = 'COM_USERS_LEVELS';
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_USERS_LEVELS';
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'Level', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'Level', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/MailController.php b/administrator/components/com_users/src/Controller/MailController.php
index efd0d59520040..2ddc744b427e9 100644
--- a/administrator/components/com_users/src/Controller/MailController.php
+++ b/administrator/components/com_users/src/Controller/MailController.php
@@ -1,4 +1,5 @@
app->get('massmailoff', 0) == 1)
- {
- $this->app->redirect(Route::_('index.php', false));
- }
+ /**
+ * Send the mail
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function send()
+ {
+ // Redirect to admin index if mass mailer disabled in conf
+ if ($this->app->get('massmailoff', 0) == 1) {
+ $this->app->redirect(Route::_('index.php', false));
+ }
- // Check for request forgeries.
- $this->checkToken('request');
+ // Check for request forgeries.
+ $this->checkToken('request');
- $model = $this->getModel('Mail');
+ $model = $this->getModel('Mail');
- if ($model->send())
- {
- $type = 'message';
- }
- else
- {
- $type = 'error';
- }
+ if ($model->send()) {
+ $type = 'message';
+ } else {
+ $type = 'error';
+ }
- $msg = $model->getError();
- $this->setRedirect('index.php?option=com_users&view=mail', $msg, $type);
- }
+ $msg = $model->getError();
+ $this->setRedirect('index.php?option=com_users&view=mail', $msg, $type);
+ }
- /**
- * Cancel the mail
- *
- * @return void
- *
- * @since 1.6
- */
- public function cancel()
- {
- // Check for request forgeries.
- $this->checkToken('request');
+ /**
+ * Cancel the mail
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function cancel()
+ {
+ // Check for request forgeries.
+ $this->checkToken('request');
- // Clear data from session.
- $this->app->setUserState('com_users.display.mail.data', null);
+ // Clear data from session.
+ $this->app->setUserState('com_users.display.mail.data', null);
- $this->setRedirect('index.php?option=com_users&view=users');
- }
+ $this->setRedirect('index.php?option=com_users&view=users');
+ }
}
diff --git a/administrator/components/com_users/src/Controller/MethodController.php b/administrator/components/com_users/src/Controller/MethodController.php
index 18918db7f0afa..6c25750e0e7a2 100644
--- a/administrator/components/com_users/src/Controller/MethodController.php
+++ b/administrator/components/com_users/src/Controller/MethodController.php
@@ -1,4 +1,5 @@
assertLoggedInUser();
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
-
- $this->assertCanEdit($user);
-
- // Also make sure the Method really does exist
- $method = $this->input->getCmd('method');
- $this->assertMethodExists($method);
-
- /** @var MethodModel $model */
- $model = $this->getModel('Method');
- $model->setState('method', $method);
-
- // Pass the return URL to the view
- $returnURL = $this->input->getBase64('returnurl');
- $viewLayout = $this->input->get('layout', 'default', 'string');
- $view = $this->getView('Method', 'html');
- $view->setLayout($viewLayout);
- $view->returnURL = $returnURL;
- $view->user = $user;
- $view->document = $this->app->getDocument();
-
- $view->setModel($model, true);
-
- $event = new NotifyActionLog('onComUsersControllerMethodBeforeAdd', [$user, $method]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- $view->display();
- }
-
- /**
- * Edit an existing MFA Method
- *
- * @param boolean $cachable Ignored. This page is never cached.
- * @param boolean|array $urlparams Ignored. This page is never cached.
- *
- * @return void
- * @throws Exception
- * @since 4.2.0
- */
- public function edit($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
-
- $this->assertCanEdit($user);
-
- // Also make sure the Method really does exist
- $id = $this->input->getInt('id');
- $record = $this->assertValidRecordId($id, $user);
-
- if ($id <= 0)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- /** @var MethodModel $model */
- $model = $this->getModel('Method');
- $model->setState('id', $id);
-
- // Pass the return URL to the view
- $returnURL = $this->input->getBase64('returnurl');
- $viewLayout = $this->input->get('layout', 'default', 'string');
- $view = $this->getView('Method', 'html');
- $view->setLayout($viewLayout);
- $view->returnURL = $returnURL;
- $view->user = $user;
- $view->document = $this->app->getDocument();
-
- $view->setModel($model, true);
-
- $event = new NotifyActionLog('onComUsersControllerMethodBeforeEdit', [$id, $user]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- $view->display();
- }
-
- /**
- * Regenerate backup codes
- *
- * @param boolean $cachable Ignored. This page is never cached.
- * @param boolean|array $urlparams Ignored. This page is never cached.
- *
- * @return void
- * @throws Exception
- * @since 4.2.0
- */
- public function regenerateBackupCodes($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- $this->checkToken($this->input->getMethod());
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $this->assertCanEdit($user);
-
- /** @var BackupcodesModel $model */
- $model = $this->getModel('Backupcodes');
- $model->regenerateBackupCodes($user);
-
- $backupCodesRecord = $model->getBackupCodesRecord($user);
-
- // Redirect
- $redirectUrl = 'index.php?option=com_users&task=method.edit&user_id=' . $userId . '&id=' . $backupCodesRecord->id;
- $returnURL = $this->input->getBase64('returnurl');
-
- if (!empty($returnURL))
- {
- $redirectUrl .= '&returnurl=' . $returnURL;
- }
-
- $this->setRedirect(Route::_($redirectUrl, false));
-
- $event = new NotifyActionLog('onComUsersControllerMethodAfterRegenerateBackupCodes');
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
- }
-
- /**
- * Delete an existing MFA Method
- *
- * @param boolean $cachable Ignored. This page is never cached.
- * @param boolean|array $urlparams Ignored. This page is never cached.
- *
- * @return void
- * @since 4.2.0
- */
- public function delete($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- $this->checkToken($this->input->getMethod());
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $this->assertCanDelete($user);
-
- // Also make sure the Method really does exist
- $id = $this->input->getInt('id');
- $record = $this->assertValidRecordId($id, $user);
-
- if ($id <= 0)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $type = null;
- $message = null;
-
- $event = new NotifyActionLog('onComUsersControllerMethodBeforeDelete', [$id, $user]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- try
- {
- $record->delete();
- }
- catch (Exception $e)
- {
- $message = $e->getMessage();
- $type = 'error';
- }
-
- // Redirect
- $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
- $returnURL = $this->input->getBase64('returnurl');
-
- if (!empty($returnURL))
- {
- $url = base64_decode($returnURL);
- }
-
- $this->setRedirect($url, $message, $type);
- }
-
- /**
- * Save the MFA Method
- *
- * @param boolean $cachable Ignored. This page is never cached.
- * @param boolean|array $urlparams Ignored. This page is never cached.
- *
- * @return void
- * @since 4.2.0
- */
- public function save($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- $this->checkToken($this->input->getMethod());
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $this->assertCanEdit($user);
-
- // Redirect
- $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
- $returnURL = $this->input->getBase64('returnurl');
-
- if (!empty($returnURL))
- {
- $url = base64_decode($returnURL);
- }
-
- // The record must either be new (ID zero) or exist
- $id = $this->input->getInt('id', 0);
- $record = $this->assertValidRecordId($id, $user);
-
- // If it's a new record we need to read the Method from the request and update the (not yet created) record.
- if ($record->id == 0)
- {
- $methodName = $this->input->getCmd('method');
- $this->assertMethodExists($methodName);
- $record->method = $methodName;
- }
-
- /** @var MethodModel $model */
- $model = $this->getModel('Method');
-
- // Ask the plugin to validate the input by calling onUserMultifactorSaveSetup
- $result = [];
- $input = $this->app->input;
-
- $event = new NotifyActionLog('onComUsersControllerMethodBeforeSave', [$id, $user]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- try
- {
- $event = new SaveSetup($record, $input);
- $pluginResults = $this->app
- ->getDispatcher()
- ->dispatch($event->getName(), $event)
- ->getArgument('result', []);
-
- foreach ($pluginResults as $pluginResult)
- {
- $result = array_merge($result, $pluginResult);
- }
- }
- catch (RuntimeException $e)
- {
- // Go back to the edit page
- $nonSefUrl = 'index.php?option=com_users&task=method.';
-
- if ($id)
- {
- $nonSefUrl .= 'edit&id=' . (int) $id;
- }
- else
- {
- $nonSefUrl .= 'add&method=' . $record->method;
- }
-
- $nonSefUrl .= '&user_id=' . $userId;
-
- if (!empty($returnURL))
- {
- $nonSefUrl .= '&returnurl=' . urlencode($returnURL);
- }
-
- $url = Route::_($nonSefUrl, false);
- $this->setRedirect($url, $e->getMessage(), 'error');
-
- return;
- }
-
- // Update the record's options with the plugin response
- $title = $this->input->getString('title', null);
- $title = trim($title);
-
- if (empty($title))
- {
- $method = $model->getMethod($record->method);
- $title = $method['display'];
- }
-
- // Update the record's "default" flag
- $default = $this->input->getBool('default', false);
- $record->title = $title;
- $record->options = $result;
- $record->default = $default ? 1 : 0;
-
- // Ask the model to save the record
- $saved = $record->store();
-
- if (!$saved)
- {
- // Go back to the edit page
- $nonSefUrl = 'index.php?option=com_users&task=method.';
-
- if ($id)
- {
- $nonSefUrl .= 'edit&id=' . (int) $id;
- }
- else
- {
- $nonSefUrl .= 'add';
- }
-
- $nonSefUrl .= '&user_id=' . $userId;
-
- if (!empty($returnURL))
- {
- $nonSefUrl .= '&returnurl=' . urlencode($returnURL);
- }
-
- $url = Route::_($nonSefUrl, false);
- $this->setRedirect($url, $record->getError(), 'error');
-
- return;
- }
-
- $this->setRedirect($url);
- }
-
- /**
- * Assert that the provided ID is a valid record identified for the given user
- *
- * @param int $id Record ID to check
- * @param User|null $user User record. Null to use current user.
- *
- * @return MfaTable The loaded record
- * @since 4.2.0
- */
- private function assertValidRecordId($id, ?User $user = null): MfaTable
- {
- if (is_null($user))
- {
- $user = $this->app->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- /** @var MethodModel $model */
- $model = $this->getModel('Method');
-
- $model->setState('id', $id);
-
- $record = $model->getRecord($user);
+ /**
+ * Public constructor
+ *
+ * @param array $config Plugin configuration
+ * @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
+ * @param CMSApplication|null $app CMS application object
+ * @param Input|null $input Joomla CMS input object
+ *
+ * @since 4.2.0
+ */
+ public function __construct(array $config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
+ {
+ // We have to tell Joomla what is the name of the view, otherwise it defaults to the name of the *component*.
+ $config['default_view'] = 'method';
+ $config['default_task'] = 'add';
+
+ parent::__construct($config, $factory, $app, $input);
+ }
+
+ /**
+ * Execute a task by triggering a Method in the derived class.
+ *
+ * @param string $task The task to perform. If no matching task is found, the '__default' task is executed, if
+ * defined.
+ *
+ * @return mixed The value returned by the called Method.
+ *
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function execute($task)
+ {
+ if (empty($task) || $task === 'display') {
+ $task = 'add';
+ }
+
+ return parent::execute($task);
+ }
+
+ /**
+ * Add a new MFA Method
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function add($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+
+ $this->assertCanEdit($user);
+
+ // Also make sure the Method really does exist
+ $method = $this->input->getCmd('method');
+ $this->assertMethodExists($method);
+
+ /** @var MethodModel $model */
+ $model = $this->getModel('Method');
+ $model->setState('method', $method);
+
+ // Pass the return URL to the view
+ $returnURL = $this->input->getBase64('returnurl');
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+ $view = $this->getView('Method', 'html');
+ $view->setLayout($viewLayout);
+ $view->returnURL = $returnURL;
+ $view->user = $user;
+ $view->document = $this->app->getDocument();
+
+ $view->setModel($model, true);
+
+ $event = new NotifyActionLog('onComUsersControllerMethodBeforeAdd', [$user, $method]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ $view->display();
+ }
+
+ /**
+ * Edit an existing MFA Method
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function edit($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+
+ $this->assertCanEdit($user);
+
+ // Also make sure the Method really does exist
+ $id = $this->input->getInt('id');
+ $record = $this->assertValidRecordId($id, $user);
+
+ if ($id <= 0) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ /** @var MethodModel $model */
+ $model = $this->getModel('Method');
+ $model->setState('id', $id);
+
+ // Pass the return URL to the view
+ $returnURL = $this->input->getBase64('returnurl');
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+ $view = $this->getView('Method', 'html');
+ $view->setLayout($viewLayout);
+ $view->returnURL = $returnURL;
+ $view->user = $user;
+ $view->document = $this->app->getDocument();
+
+ $view->setModel($model, true);
+
+ $event = new NotifyActionLog('onComUsersControllerMethodBeforeEdit', [$id, $user]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ $view->display();
+ }
+
+ /**
+ * Regenerate backup codes
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function regenerateBackupCodes($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ $this->checkToken($this->input->getMethod());
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $this->assertCanEdit($user);
+
+ /** @var BackupcodesModel $model */
+ $model = $this->getModel('Backupcodes');
+ $model->regenerateBackupCodes($user);
+
+ $backupCodesRecord = $model->getBackupCodesRecord($user);
+
+ // Redirect
+ $redirectUrl = 'index.php?option=com_users&task=method.edit&user_id=' . $userId . '&id=' . $backupCodesRecord->id;
+ $returnURL = $this->input->getBase64('returnurl');
+
+ if (!empty($returnURL)) {
+ $redirectUrl .= '&returnurl=' . $returnURL;
+ }
+
+ $this->setRedirect(Route::_($redirectUrl, false));
+
+ $event = new NotifyActionLog('onComUsersControllerMethodAfterRegenerateBackupCodes');
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+ }
+
+ /**
+ * Delete an existing MFA Method
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function delete($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ $this->checkToken($this->input->getMethod());
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $this->assertCanDelete($user);
+
+ // Also make sure the Method really does exist
+ $id = $this->input->getInt('id');
+ $record = $this->assertValidRecordId($id, $user);
+
+ if ($id <= 0) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $type = null;
+ $message = null;
+
+ $event = new NotifyActionLog('onComUsersControllerMethodBeforeDelete', [$id, $user]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ try {
+ $record->delete();
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ $type = 'error';
+ }
+
+ // Redirect
+ $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
+ $returnURL = $this->input->getBase64('returnurl');
+
+ if (!empty($returnURL)) {
+ $url = base64_decode($returnURL);
+ }
+
+ $this->setRedirect($url, $message, $type);
+ }
+
+ /**
+ * Save the MFA Method
+ *
+ * @param boolean $cachable Ignored. This page is never cached.
+ * @param boolean|array $urlparams Ignored. This page is never cached.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function save($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ $this->checkToken($this->input->getMethod());
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $this->assertCanEdit($user);
+
+ // Redirect
+ $url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
+ $returnURL = $this->input->getBase64('returnurl');
+
+ if (!empty($returnURL)) {
+ $url = base64_decode($returnURL);
+ }
+
+ // The record must either be new (ID zero) or exist
+ $id = $this->input->getInt('id', 0);
+ $record = $this->assertValidRecordId($id, $user);
+
+ // If it's a new record we need to read the Method from the request and update the (not yet created) record.
+ if ($record->id == 0) {
+ $methodName = $this->input->getCmd('method');
+ $this->assertMethodExists($methodName);
+ $record->method = $methodName;
+ }
+
+ /** @var MethodModel $model */
+ $model = $this->getModel('Method');
+
+ // Ask the plugin to validate the input by calling onUserMultifactorSaveSetup
+ $result = [];
+ $input = $this->app->input;
+
+ $event = new NotifyActionLog('onComUsersControllerMethodBeforeSave', [$id, $user]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ try {
+ $event = new SaveSetup($record, $input);
+ $pluginResults = $this->app
+ ->getDispatcher()
+ ->dispatch($event->getName(), $event)
+ ->getArgument('result', []);
+
+ foreach ($pluginResults as $pluginResult) {
+ $result = array_merge($result, $pluginResult);
+ }
+ } catch (RuntimeException $e) {
+ // Go back to the edit page
+ $nonSefUrl = 'index.php?option=com_users&task=method.';
+
+ if ($id) {
+ $nonSefUrl .= 'edit&id=' . (int) $id;
+ } else {
+ $nonSefUrl .= 'add&method=' . $record->method;
+ }
+
+ $nonSefUrl .= '&user_id=' . $userId;
+
+ if (!empty($returnURL)) {
+ $nonSefUrl .= '&returnurl=' . urlencode($returnURL);
+ }
+
+ $url = Route::_($nonSefUrl, false);
+ $this->setRedirect($url, $e->getMessage(), 'error');
+
+ return;
+ }
+
+ // Update the record's options with the plugin response
+ $title = $this->input->getString('title', null);
+ $title = trim($title);
+
+ if (empty($title)) {
+ $method = $model->getMethod($record->method);
+ $title = $method['display'];
+ }
+
+ // Update the record's "default" flag
+ $default = $this->input->getBool('default', false);
+ $record->title = $title;
+ $record->options = $result;
+ $record->default = $default ? 1 : 0;
+
+ // Ask the model to save the record
+ $saved = $record->store();
+
+ if (!$saved) {
+ // Go back to the edit page
+ $nonSefUrl = 'index.php?option=com_users&task=method.';
+
+ if ($id) {
+ $nonSefUrl .= 'edit&id=' . (int) $id;
+ } else {
+ $nonSefUrl .= 'add';
+ }
+
+ $nonSefUrl .= '&user_id=' . $userId;
+
+ if (!empty($returnURL)) {
+ $nonSefUrl .= '&returnurl=' . urlencode($returnURL);
+ }
+
+ $url = Route::_($nonSefUrl, false);
+ $this->setRedirect($url, $record->getError(), 'error');
+
+ return;
+ }
+
+ $this->setRedirect($url);
+ }
+
+ /**
+ * Assert that the provided ID is a valid record identified for the given user
+ *
+ * @param int $id Record ID to check
+ * @param User|null $user User record. Null to use current user.
+ *
+ * @return MfaTable The loaded record
+ * @since 4.2.0
+ */
+ private function assertValidRecordId($id, ?User $user = null): MfaTable
+ {
+ if (is_null($user)) {
+ $user = $this->app->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ /** @var MethodModel $model */
+ $model = $this->getModel('Method');
+
+ $model->setState('id', $id);
+
+ $record = $model->getRecord($user);
// phpcs:ignore
if (is_null($record) || ($record->id != $id) || ($record->user_id != $user->id))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- return $record;
- }
-
- /**
- * Assert that the user can add / edit MFA methods.
- *
- * @param User|null $user User record. Null to use current user.
- *
- * @return void
- * @throws RuntimeException|Exception
- * @since 4.2.0
- */
- private function assertCanEdit(?User $user = null): void
- {
- if (!MfaHelper::canAddEditMethod($user))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
-
- /**
- * Assert that the user can delete MFA records / disable MFA.
- *
- * @param User|null $user User record. Null to use current user.
- *
- * @return void
- * @throws RuntimeException|Exception
- * @since 4.2.0
- */
- private function assertCanDelete(?User $user = null): void
- {
- if (!MfaHelper::canDeleteMethod($user))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
-
- /**
- * Assert that the specified MFA Method exists, is activated and enabled for the current user
- *
- * @param string|null $method The Method to check
- *
- * @return void
- * @since 4.2.0
- */
- private function assertMethodExists(?string $method): void
- {
- /** @var MethodModel $model */
- $model = $this->getModel('Method');
-
- if (empty($method) || !$model->methodExists($method))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
-
- /**
- * Assert that there is a logged in user.
- *
- * @return void
- * @since 4.2.0
- */
- private function assertLoggedInUser(): void
- {
- $user = $this->app->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- if ($user->guest)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ return $record;
+ }
+
+ /**
+ * Assert that the user can add / edit MFA methods.
+ *
+ * @param User|null $user User record. Null to use current user.
+ *
+ * @return void
+ * @throws RuntimeException|Exception
+ * @since 4.2.0
+ */
+ private function assertCanEdit(?User $user = null): void
+ {
+ if (!MfaHelper::canAddEditMethod($user)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+
+ /**
+ * Assert that the user can delete MFA records / disable MFA.
+ *
+ * @param User|null $user User record. Null to use current user.
+ *
+ * @return void
+ * @throws RuntimeException|Exception
+ * @since 4.2.0
+ */
+ private function assertCanDelete(?User $user = null): void
+ {
+ if (!MfaHelper::canDeleteMethod($user)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+
+ /**
+ * Assert that the specified MFA Method exists, is activated and enabled for the current user
+ *
+ * @param string|null $method The Method to check
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function assertMethodExists(?string $method): void
+ {
+ /** @var MethodModel $model */
+ $model = $this->getModel('Method');
+
+ if (empty($method) || !$model->methodExists($method)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+
+ /**
+ * Assert that there is a logged in user.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function assertLoggedInUser(): void
+ {
+ $user = $this->app->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ if ($user->guest) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Controller/MethodsController.php b/administrator/components/com_users/src/Controller/MethodsController.php
index 92a6fd2b20cc9..4eb1909eed1e0 100644
--- a/administrator/components/com_users/src/Controller/MethodsController.php
+++ b/administrator/components/com_users/src/Controller/MethodsController.php
@@ -1,4 +1,5 @@
assertLoggedInUser();
-
- $this->checkToken($this->input->getMethod());
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = ($userId === null)
- ? $this->app->getIdentity()
- : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- if (!MfaHelper::canDeleteMethod($user))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- // Delete all MFA Methods for the user
- /** @var MethodsModel $model */
- $model = $this->getModel('Methods');
- $type = null;
- $message = null;
-
- $event = new NotifyActionLog('onComUsersControllerMethodsBeforeDisable', [$user]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- try
- {
- $model->deleteAll($user);
- }
- catch (Exception $e)
- {
- $message = $e->getMessage();
- $type = 'error';
- }
-
- // Redirect
+ /**
+ * Public constructor
+ *
+ * @param array $config Plugin configuration
+ * @param MVCFactoryInterface|null $factory MVC Factory for the com_users component
+ * @param CMSApplication|null $app CMS application object
+ * @param Input|null $input Joomla CMS input object
+ *
+ * @since 4.2.0
+ */
+ public function __construct($config = [], MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null)
+ {
+ // We have to tell Joomla what is the name of the view, otherwise it defaults to the name of the *component*.
+ $config['default_view'] = 'Methods';
+
+ parent::__construct($config, $factory, $app, $input);
+ }
+
+ /**
+ * Disable Multi-factor Authentication for the current user
+ *
+ * @param bool $cachable Can this view be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
+ * {@link JFilterInput::clean()}.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function disable($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ $this->checkToken($this->input->getMethod());
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = ($userId === null)
+ ? $this->app->getIdentity()
+ : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ if (!MfaHelper::canDeleteMethod($user)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ // Delete all MFA Methods for the user
+ /** @var MethodsModel $model */
+ $model = $this->getModel('Methods');
+ $type = null;
+ $message = null;
+
+ $event = new NotifyActionLog('onComUsersControllerMethodsBeforeDisable', [$user]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ try {
+ $model->deleteAll($user);
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ $type = 'error';
+ }
+
+ // Redirect
// phpcs:ignore
$url = Route::_('index.php?option=com_users&task=methods.display&user_id=' . $userId, false);
- $returnURL = $this->input->getBase64('returnurl');
-
- if (!empty($returnURL))
- {
- $url = base64_decode($returnURL);
- }
-
- $this->setRedirect($url, $message, $type);
- }
-
- /**
- * List all available Multi-factor Authentication Methods available and guide the user to setting them up
- *
- * @param bool $cachable Can this view be cached
- * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
- * {@link JFilterInput::clean()}.
- *
- * @return void
- * @since 4.2.0
- */
- public function display($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = ($userId === null)
- ? $this->app->getIdentity()
- : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- if (!MfaHelper::canShowConfigurationInterface($user))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $returnURL = $this->input->getBase64('returnurl');
- $viewLayout = $this->input->get('layout', 'default', 'string');
- $view = $this->getView('Methods', 'html');
- $view->setLayout($viewLayout);
- $view->returnURL = $returnURL;
- $view->user = $user;
- $view->document = $this->app->getDocument();
-
- $methodsModel = $this->getModel('Methods');
- $view->setModel($methodsModel, true);
-
- $backupCodesModel = $this->getModel('Backupcodes');
- $view->setModel($backupCodesModel, false);
-
- $view->display();
- }
-
- /**
- * Disable Multi-factor Authentication for the current user
- *
- * @param bool $cachable Can this view be cached
- * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
- * {@link JFilterInput::clean()}.
- *
- * @return void
- * @since 4.2.0
- */
- public function doNotShowThisAgain($cachable = false, $urlparams = []): void
- {
- $this->assertLoggedInUser();
-
- $this->checkToken($this->input->getMethod());
-
- // Make sure I am allowed to edit the specified user
- $userId = $this->input->getInt('user_id', null);
- $user = ($userId === null)
- ? $this->app->getIdentity()
- : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
- $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- if (!MfaHelper::canAddEditMethod($user))
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $event = new NotifyActionLog('onComUsersControllerMethodsBeforeDoNotShowThisAgain', [$user]);
- $this->app->getDispatcher()->dispatch($event->getName(), $event);
-
- /** @var MethodsModel $model */
- $model = $this->getModel('Methods');
- $model->setFlag($user, true);
-
- // Redirect
- $url = Uri::base();
- $returnURL = $this->input->getBase64('returnurl');
-
- if (!empty($returnURL))
- {
- $url = base64_decode($returnURL);
- }
-
- $this->setRedirect($url);
- }
-
- /**
- * Assert that there is a user currently logged in
- *
- * @return void
- * @since 4.2.0
- */
- private function assertLoggedInUser(): void
- {
- $user = $this->app->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- if ($user->guest)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ $returnURL = $this->input->getBase64('returnurl');
+
+ if (!empty($returnURL)) {
+ $url = base64_decode($returnURL);
+ }
+
+ $this->setRedirect($url, $message, $type);
+ }
+
+ /**
+ * List all available Multi-factor Authentication Methods available and guide the user to setting them up
+ *
+ * @param bool $cachable Can this view be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
+ * {@link JFilterInput::clean()}.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function display($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = ($userId === null)
+ ? $this->app->getIdentity()
+ : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ if (!MfaHelper::canShowConfigurationInterface($user)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $returnURL = $this->input->getBase64('returnurl');
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+ $view = $this->getView('Methods', 'html');
+ $view->setLayout($viewLayout);
+ $view->returnURL = $returnURL;
+ $view->user = $user;
+ $view->document = $this->app->getDocument();
+
+ $methodsModel = $this->getModel('Methods');
+ $view->setModel($methodsModel, true);
+
+ $backupCodesModel = $this->getModel('Backupcodes');
+ $view->setModel($backupCodesModel, false);
+
+ $view->display();
+ }
+
+ /**
+ * Disable Multi-factor Authentication for the current user
+ *
+ * @param bool $cachable Can this view be cached
+ * @param array $urlparams An array of safe url parameters and their variable types, for valid values see
+ * {@link JFilterInput::clean()}.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function doNotShowThisAgain($cachable = false, $urlparams = []): void
+ {
+ $this->assertLoggedInUser();
+
+ $this->checkToken($this->input->getMethod());
+
+ // Make sure I am allowed to edit the specified user
+ $userId = $this->input->getInt('user_id', null);
+ $user = ($userId === null)
+ ? $this->app->getIdentity()
+ : Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
+ $user = $user ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ if (!MfaHelper::canAddEditMethod($user)) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $event = new NotifyActionLog('onComUsersControllerMethodsBeforeDoNotShowThisAgain', [$user]);
+ $this->app->getDispatcher()->dispatch($event->getName(), $event);
+
+ /** @var MethodsModel $model */
+ $model = $this->getModel('Methods');
+ $model->setFlag($user, true);
+
+ // Redirect
+ $url = Uri::base();
+ $returnURL = $this->input->getBase64('returnurl');
+
+ if (!empty($returnURL)) {
+ $url = base64_decode($returnURL);
+ }
+
+ $this->setRedirect($url);
+ }
+
+ /**
+ * Assert that there is a user currently logged in
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function assertLoggedInUser(): void
+ {
+ $user = $this->app->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ if ($user->guest) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Controller/NoteController.php b/administrator/components/com_users/src/Controller/NoteController.php
index 34b5ccc374fe6..30e3e3f9113a6 100644
--- a/administrator/components/com_users/src/Controller/NoteController.php
+++ b/administrator/components/com_users/src/Controller/NoteController.php
@@ -1,4 +1,5 @@
input->get('u_id', 0, 'int');
-
- if ($userId)
- {
- $append .= '&u_id=' . $userId;
- }
-
- return $append;
- }
+ use VersionableControllerTrait;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $text_prefix = 'COM_USERS_NOTE';
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $key The name of the primary key variable.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 2.5
+ */
+ protected function getRedirectToItemAppend($recordId = null, $key = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId, $key);
+
+ $userId = $this->input->get('u_id', 0, 'int');
+
+ if ($userId) {
+ $append .= '&u_id=' . $userId;
+ }
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_users/src/Controller/NotesController.php b/administrator/components/com_users/src/Controller/NotesController.php
index cb49b92318e7d..71030d6eeb7af 100644
--- a/administrator/components/com_users/src/Controller/NotesController.php
+++ b/administrator/components/com_users/src/Controller/NotesController.php
@@ -1,4 +1,5 @@
true))
- {
- return parent::getModel($name, $prefix, $config);
- }
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 2.5
+ */
+ public function getModel($name = 'Note', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
}
diff --git a/administrator/components/com_users/src/Controller/UserController.php b/administrator/components/com_users/src/Controller/UserController.php
index 5694b46d2f1e4..d72c8c844dc61 100644
--- a/administrator/components/com_users/src/Controller/UserController.php
+++ b/administrator/components/com_users/src/Controller/UserController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Users\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Users\Administrator\Controller;
use Joomla\CMS\Access\Access;
use Joomla\CMS\MVC\Controller\FormController;
@@ -23,139 +23,132 @@
*/
class UserController extends FormController
{
- /**
- * @var string The prefix to use with controller messages.
- * @since 1.6
- */
- protected $text_prefix = 'COM_USERS_USER';
-
- /**
- * Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
- *
- * Checks that non-Super Admins are not editing Super Admins.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean True if allowed, false otherwise.
- *
- * @since 1.6
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- // Check if this person is a Super Admin
- if (Access::check($data[$key], 'core.admin'))
- {
- // If I'm not a Super Admin, then disallow the edit.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- return false;
- }
- }
-
- // Allow users to edit their own account
- if (isset($data[$key]) && (int) $this->app->getIdentity()->id === (int) $data[$key])
- {
- return true;
- }
-
- return parent::allowEdit($data, $key);
- }
-
- /**
- * Override parent cancel to redirect when using status edit account.
- *
- * @param string $key The name of the primary key of the URL variable.
- *
- * @return boolean True if access level checks pass, false otherwise.
- *
- * @since 4.0.0
- */
- public function cancel($key = null)
- {
- $result = parent::cancel();
-
- if ($return = $this->input->get('return', '', 'BASE64'))
- {
- $return = base64_decode($return);
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($return))
- {
- $return = Uri::base();
- }
-
- $this->app->redirect($return);
- }
-
- return $result;
- }
-
- /**
- * Override parent save to redirect when using status edit account.
- *
- * @param string $key The name of the primary key of the URL variable.
- * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
- *
- * @return boolean True if successful, false otherwise.
- *
- * @since 4.0.0
- */
- public function save($key = null, $urlVar = null)
- {
- $result = parent::save($key, $urlVar);
-
- $task = $this->getTask();
-
- if ($task === 'save' && $return = $this->input->get('return', '', 'BASE64'))
- {
- $return = base64_decode($return);
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($return))
- {
- $return = Uri::base();
- }
-
- $this->setRedirect($return);
- }
-
- return $result;
- }
-
- /**
- * Method to run batch operations.
- *
- * @param object $model The model.
- *
- * @return boolean True on success, false on failure
- *
- * @since 2.5
- */
- public function batch($model = null)
- {
- $this->checkToken();
-
- // Set the model
- $model = $this->getModel('User', 'Administrator', array());
-
- // Preset the redirect
- $this->setRedirect(Route::_('index.php?option=com_users&view=users' . $this->getRedirectToListAppend(), false));
-
- return parent::batch($model);
- }
-
- /**
- * Function that allows child controller access to model data after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 3.1
- */
- protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- }
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_USERS_USER';
+
+ /**
+ * Overrides Joomla\CMS\MVC\Controller\FormController::allowEdit
+ *
+ * Checks that non-Super Admins are not editing Super Admins.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean True if allowed, false otherwise.
+ *
+ * @since 1.6
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ // Check if this person is a Super Admin
+ if (Access::check($data[$key], 'core.admin')) {
+ // If I'm not a Super Admin, then disallow the edit.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ return false;
+ }
+ }
+
+ // Allow users to edit their own account
+ if (isset($data[$key]) && (int) $this->app->getIdentity()->id === (int) $data[$key]) {
+ return true;
+ }
+
+ return parent::allowEdit($data, $key);
+ }
+
+ /**
+ * Override parent cancel to redirect when using status edit account.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ *
+ * @return boolean True if access level checks pass, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public function cancel($key = null)
+ {
+ $result = parent::cancel();
+
+ if ($return = $this->input->get('return', '', 'BASE64')) {
+ $return = base64_decode($return);
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($return)) {
+ $return = Uri::base();
+ }
+
+ $this->app->redirect($return);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Override parent save to redirect when using status edit account.
+ *
+ * @param string $key The name of the primary key of the URL variable.
+ * @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
+ *
+ * @return boolean True if successful, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public function save($key = null, $urlVar = null)
+ {
+ $result = parent::save($key, $urlVar);
+
+ $task = $this->getTask();
+
+ if ($task === 'save' && $return = $this->input->get('return', '', 'BASE64')) {
+ $return = base64_decode($return);
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($return)) {
+ $return = Uri::base();
+ }
+
+ $this->setRedirect($return);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to run batch operations.
+ *
+ * @param object $model The model.
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @since 2.5
+ */
+ public function batch($model = null)
+ {
+ $this->checkToken();
+
+ // Set the model
+ $model = $this->getModel('User', 'Administrator', array());
+
+ // Preset the redirect
+ $this->setRedirect(Route::_('index.php?option=com_users&view=users' . $this->getRedirectToListAppend(), false));
+
+ return parent::batch($model);
+ }
+
+ /**
+ * Function that allows child controller access to model data after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 3.1
+ */
+ protected function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ }
}
diff --git a/administrator/components/com_users/src/Controller/UsersController.php b/administrator/components/com_users/src/Controller/UsersController.php
index f5d9f0b07e7eb..feb029cb001b5 100644
--- a/administrator/components/com_users/src/Controller/UsersController.php
+++ b/administrator/components/com_users/src/Controller/UsersController.php
@@ -1,4 +1,5 @@
registerTask('block', 'changeBlock');
- $this->registerTask('unblock', 'changeBlock');
- }
-
- /**
- * Proxy for getModel.
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return object The model.
- *
- * @since 1.6
- */
- public function getModel($name = 'User', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to change the block status on a record.
- *
- * @return void
- *
- * @since 1.6
- */
- public function changeBlock()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
- $values = array('block' => 1, 'unblock' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($values, $task, 0, 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (empty($ids))
- {
- $this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'warning');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Change the state of the records.
- if (!$model->block($ids, $value))
- {
- $this->setMessage($model->getError(), 'error');
- }
- else
- {
- if ($value == 1)
- {
- $this->setMessage(Text::plural('COM_USERS_N_USERS_BLOCKED', count($ids)));
- }
- elseif ($value == 0)
- {
- $this->setMessage(Text::plural('COM_USERS_N_USERS_UNBLOCKED', count($ids)));
- }
- }
- }
-
- $this->setRedirect('index.php?option=com_users&view=users');
- }
-
- /**
- * Method to activate a record.
- *
- * @return void
- *
- * @since 1.6
- */
- public function activate()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- $ids = (array) $this->input->get('cid', array(), 'int');
-
- // Remove zero values resulting from input filter
- $ids = array_filter($ids);
-
- if (empty($ids))
- {
- $this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'error');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Change the state of the records.
- if (!$model->activate($ids))
- {
- $this->setMessage($model->getError(), 'error');
- }
- else
- {
- $this->setMessage(Text::plural('COM_USERS_N_USERS_ACTIVATED', count($ids)));
- }
- }
-
- $this->setRedirect('index.php?option=com_users&view=users');
- }
-
- /**
- * Method to get the number of active users
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function getQuickiconContent()
- {
- $model = $this->getModel('Users');
-
- $model->setState('filter.state', 0);
-
- $amount = (int) $model->getTotal();
-
- $result = [];
-
- $result['amount'] = $amount;
- $result['sronly'] = Text::plural('COM_USERS_N_QUICKICON_SRONLY', $amount);
- $result['name'] = Text::plural('COM_USERS_N_QUICKICON', $amount);
-
- echo new JsonResponse($result);
- }
+ /**
+ * @var string The prefix to use with controller messages.
+ * @since 1.6
+ */
+ protected $text_prefix = 'COM_USERS_USERS';
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The CMSApplication for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 1.6
+ * @see BaseController
+ * @throws \Exception
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('block', 'changeBlock');
+ $this->registerTask('unblock', 'changeBlock');
+ }
+
+ /**
+ * Proxy for getModel.
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return object The model.
+ *
+ * @since 1.6
+ */
+ public function getModel($name = 'User', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to change the block status on a record.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function changeBlock()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+ $values = array('block' => 1, 'unblock' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($values, $task, 0, 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (empty($ids)) {
+ $this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'warning');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Change the state of the records.
+ if (!$model->block($ids, $value)) {
+ $this->setMessage($model->getError(), 'error');
+ } else {
+ if ($value == 1) {
+ $this->setMessage(Text::plural('COM_USERS_N_USERS_BLOCKED', count($ids)));
+ } elseif ($value == 0) {
+ $this->setMessage(Text::plural('COM_USERS_N_USERS_UNBLOCKED', count($ids)));
+ }
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_users&view=users');
+ }
+
+ /**
+ * Method to activate a record.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ public function activate()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ $ids = (array) $this->input->get('cid', array(), 'int');
+
+ // Remove zero values resulting from input filter
+ $ids = array_filter($ids);
+
+ if (empty($ids)) {
+ $this->setMessage(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'), 'error');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Change the state of the records.
+ if (!$model->activate($ids)) {
+ $this->setMessage($model->getError(), 'error');
+ } else {
+ $this->setMessage(Text::plural('COM_USERS_N_USERS_ACTIVATED', count($ids)));
+ }
+ }
+
+ $this->setRedirect('index.php?option=com_users&view=users');
+ }
+
+ /**
+ * Method to get the number of active users
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function getQuickiconContent()
+ {
+ $model = $this->getModel('Users');
+
+ $model->setState('filter.state', 0);
+
+ $amount = (int) $model->getTotal();
+
+ $result = [];
+
+ $result['amount'] = $amount;
+ $result['sronly'] = Text::plural('COM_USERS_N_QUICKICON_SRONLY', $amount);
+ $result['name'] = Text::plural('COM_USERS_N_QUICKICON', $amount);
+
+ echo new JsonResponse($result);
+ }
}
diff --git a/administrator/components/com_users/src/DataShape/CaptiveRenderOptions.php b/administrator/components/com_users/src/DataShape/CaptiveRenderOptions.php
index 273bb73de9ed7..4542388548552 100644
--- a/administrator/components/com_users/src/DataShape/CaptiveRenderOptions.php
+++ b/administrator/components/com_users/src/DataShape/CaptiveRenderOptions.php
@@ -1,4 +1,5 @@
field_type = $value;
- }
-
- /**
- * Setter for the input_attributes property.
- *
- * @param array $value The value to set
- *
- * @return void
- * @@since 4.2.0
- */
+ }
+
+ /**
+ * Setter for the input_attributes property.
+ *
+ * @param array $value The value to set
+ *
+ * @return void
+ * @@since 4.2.0
+ */
// phpcs:ignore
protected function setInput_attributes(array $value)
- {
- $forbiddenAttributes = ['id', 'type', 'name', 'value'];
+ {
+ $forbiddenAttributes = ['id', 'type', 'name', 'value'];
- foreach ($forbiddenAttributes as $key)
- {
- if (isset($value[$key]))
- {
- unset($value[$key]);
- }
- }
+ foreach ($forbiddenAttributes as $key) {
+ if (isset($value[$key])) {
+ unset($value[$key]);
+ }
+ }
// phpcs:ignore
$this->input_attributes = $value;
- }
+ }
}
diff --git a/administrator/components/com_users/src/DataShape/MethodDescriptor.php b/administrator/components/com_users/src/DataShape/MethodDescriptor.php
index 4988c573b6d21..2b9ee51080615 100644
--- a/administrator/components/com_users/src/DataShape/MethodDescriptor.php
+++ b/administrator/components/com_users/src/DataShape/MethodDescriptor.php
@@ -1,4 +1,5 @@
active[$record->id] = $record;
- }
+ /**
+ * Adds an active MFA method
+ *
+ * @param MfaTable $record The MFA method record to add
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ public function addActiveMethod(MfaTable $record)
+ {
+ $this->active[$record->id] = $record;
+ }
}
diff --git a/administrator/components/com_users/src/DataShape/SetupRenderOptions.php b/administrator/components/com_users/src/DataShape/SetupRenderOptions.php
index fcf612b05c805..3034fd67333bb 100644
--- a/administrator/components/com_users/src/DataShape/SetupRenderOptions.php
+++ b/administrator/components/com_users/src/DataShape/SetupRenderOptions.php
@@ -1,4 +1,5 @@
custom HTML). See above
- *
- * @var array
- * @since 4.2.0
- */
+ /**
+ * Any tabular data to display (label => custom HTML). See above
+ *
+ * @var array
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $tabular_data = [];
- /**
- * Hidden fields to include in the form (name => value)
- *
- * @var array
- * @since 4.2.0
- */
+ /**
+ * Hidden fields to include in the form (name => value)
+ *
+ * @var array
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $hidden_data = [];
- /**
- * How to render the MFA setup code field. "input" (HTML input element) or "custom" (custom HTML)
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * How to render the MFA setup code field. "input" (HTML input element) or "custom" (custom HTML)
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $field_type = 'input';
- /**
- * The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type.
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * The type attribute for the HTML input box. Typically "text" or "password". Use any HTML5 input type.
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $input_type = 'text';
- /**
- * Attributes other than type and id which will be added to the HTML input box.
- *
- * @var array
- * @@since 4.2.0
- */
+ /**
+ * Attributes other than type and id which will be added to the HTML input box.
+ *
+ * @var array
+ * @@since 4.2.0
+ */
// phpcs:ignore
protected $input_attributes = [];
- /**
- * Pre-filled value for the HTML input box. Typically used for fixed codes, the fixed YubiKey ID etc.
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * Pre-filled value for the HTML input box. Typically used for fixed codes, the fixed YubiKey ID etc.
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $input_value = '';
- /**
- * Placeholder text for the HTML input box. Leave empty if you don't need it.
- *
- * @var string
- * @since 4.2.0
- */
- protected $placeholder = '';
+ /**
+ * Placeholder text for the HTML input box. Leave empty if you don't need it.
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ protected $placeholder = '';
- /**
- * Label to show above the HTML input box. Leave empty if you don't need it.
- *
- * @var string
- * @since 4.2.0
- */
- protected $label = '';
+ /**
+ * Label to show above the HTML input box. Leave empty if you don't need it.
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ protected $label = '';
- /**
- * Custom HTML. Only used when field_type = custom.
- *
- * @var string
- * @since 4.2.0
- */
- protected $html = '';
+ /**
+ * Custom HTML. Only used when field_type = custom.
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ protected $html = '';
- /**
- * Should I show the submit button (apply the MFA setup)?
- *
- * @var boolean
- * @since 4.2.0
- */
+ /**
+ * Should I show the submit button (apply the MFA setup)?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $show_submit = true;
- /**
- * Additional CSS classes for the submit button (apply the MFA setup)
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * Additional CSS classes for the submit button (apply the MFA setup)
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $submit_class = '';
- /**
- * Icon class to use for the submit button
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * Icon class to use for the submit button
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $submit_icon = 'icon icon-ok';
- /**
- * Language key to use for the text on the submit button
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * Language key to use for the text on the submit button
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $submit_text = 'JSAVE';
- /**
- * Custom HTML to display below the MFA setup form
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * Custom HTML to display below the MFA setup form
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $post_message = '';
- /**
- * A URL with help content for this Method to display to the user
- *
- * @var string
- * @since 4.2.0
- */
+ /**
+ * A URL with help content for this Method to display to the user
+ *
+ * @var string
+ * @since 4.2.0
+ */
// phpcs:ignore
protected $help_url = '';
- /**
- * Setter for the field_type property
- *
- * @param string $value One of self::FIELD_INPUT, self::FIELD_CUSTOM
- *
- * @since 4.2.0
- * @throws InvalidArgumentException
- */
+ /**
+ * Setter for the field_type property
+ *
+ * @param string $value One of self::FIELD_INPUT, self::FIELD_CUSTOM
+ *
+ * @since 4.2.0
+ * @throws InvalidArgumentException
+ */
// phpcs:ignore
protected function setField_type($value)
- {
- if (!in_array($value, [self::FIELD_INPUT, self::FIELD_CUSTOM]))
- {
- throw new InvalidArgumentException('Invalid value for property field_type.');
- }
+ {
+ if (!in_array($value, [self::FIELD_INPUT, self::FIELD_CUSTOM])) {
+ throw new InvalidArgumentException('Invalid value for property field_type.');
+ }
// phpcs:ignore
$this->field_type = $value;
- }
+ }
- /**
- * Setter for the input_attributes property.
- *
- * @param array $value The value to set
- *
- * @return void
- * @@since 4.2.0
- */
+ /**
+ * Setter for the input_attributes property.
+ *
+ * @param array $value The value to set
+ *
+ * @return void
+ * @@since 4.2.0
+ */
// phpcs:ignore
protected function setInput_attributes(array $value)
- {
- $forbiddenAttributes = ['id', 'type', 'name', 'value'];
+ {
+ $forbiddenAttributes = ['id', 'type', 'name', 'value'];
- foreach ($forbiddenAttributes as $key)
- {
- if (isset($value[$key]))
- {
- unset($value[$key]);
- }
- }
+ foreach ($forbiddenAttributes as $key) {
+ if (isset($value[$key])) {
+ unset($value[$key]);
+ }
+ }
// phpcs:ignore
$this->input_attributes = $value;
- }
+ }
}
diff --git a/administrator/components/com_users/src/Dispatcher/Dispatcher.php b/administrator/components/com_users/src/Dispatcher/Dispatcher.php
index 2a3b6e79e2773..dc05ceb7654ac 100644
--- a/administrator/components/com_users/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_users/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
input->getCmd('task');
- $view = $this->input->getCmd('view');
- $layout = $this->input->getCmd('layout');
- $allowedTasks = ['user.edit', 'user.apply', 'user.save', 'user.cancel'];
+ /**
+ * Override checkAccess to allow users edit profile without having to have core.manager permission
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function checkAccess()
+ {
+ $task = $this->input->getCmd('task');
+ $view = $this->input->getCmd('view');
+ $layout = $this->input->getCmd('layout');
+ $allowedTasks = ['user.edit', 'user.apply', 'user.save', 'user.cancel'];
- // Allow users to edit their own account
- if (in_array($task, $allowedTasks, true) || ($view === 'user' && $layout === 'edit'))
- {
- $user = $this->app->getIdentity();
- $id = $this->input->getInt('id');
+ // Allow users to edit their own account
+ if (in_array($task, $allowedTasks, true) || ($view === 'user' && $layout === 'edit')) {
+ $user = $this->app->getIdentity();
+ $id = $this->input->getInt('id');
- if ((int) $user->id === $id)
- {
- return;
- }
- }
+ if ((int) $user->id === $id) {
+ return;
+ }
+ }
- /**
- * Special case: Multi-factor Authentication
- *
- * We allow access to all MFA views and tasks. Access control for MFA tasks is performed in
- * the Controllers since what is allowed depends on who is logged in and whose account you
- * are trying to modify. Implementing these checks in the Dispatcher would violate the
- * separation of concerns.
- */
- $allowedViews = ['callback', 'captive', 'method', 'methods'];
- $isAllowedTask = array_reduce(
- $allowedViews,
- function ($carry, $taskPrefix) use ($task)
- {
- return $carry || strpos($task ?? '', $taskPrefix . '.') === 0;
- },
- false
- );
+ /**
+ * Special case: Multi-factor Authentication
+ *
+ * We allow access to all MFA views and tasks. Access control for MFA tasks is performed in
+ * the Controllers since what is allowed depends on who is logged in and whose account you
+ * are trying to modify. Implementing these checks in the Dispatcher would violate the
+ * separation of concerns.
+ */
+ $allowedViews = ['callback', 'captive', 'method', 'methods'];
+ $isAllowedTask = array_reduce(
+ $allowedViews,
+ function ($carry, $taskPrefix) use ($task) {
+ return $carry || strpos($task ?? '', $taskPrefix . '.') === 0;
+ },
+ false
+ );
- if (in_array(strtolower($view ?? ''), $allowedViews) || $isAllowedTask)
- {
- return;
- }
+ if (in_array(strtolower($view ?? ''), $allowedViews) || $isAllowedTask) {
+ return;
+ }
- parent::checkAccess();
- }
+ parent::checkAccess();
+ }
}
diff --git a/administrator/components/com_users/src/Extension/UsersComponent.php b/administrator/components/com_users/src/Extension/UsersComponent.php
index 0b9dcd89fcf01..3b519c2d7b6cf 100644
--- a/administrator/components/com_users/src/Extension/UsersComponent.php
+++ b/administrator/components/com_users/src/Extension/UsersComponent.php
@@ -1,4 +1,5 @@
getRegistry()->register('users', new Users);
- }
+ /**
+ * Booting the extension. This is the function to set up the environment of the extension like
+ * registering new class loaders, etc.
+ *
+ * If required, some initial set up can be done from services of the container, eg.
+ * registering HTML services.
+ *
+ * @param ContainerInterface $container The container
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function boot(ContainerInterface $container)
+ {
+ $this->getRegistry()->register('users', new Users());
+ }
- /**
- * Returns a valid section for the given section. If it is not valid then null is returned.
- *
- * @param string $section The section to get the mapping for
- * @param object|null $item The content item or null
- *
- * @return string|null The new section or null
- *
- * @since 4.0.0
- */
- public function validateSection($section, $item = null)
- {
- if (Factory::getApplication()->isClient('site'))
- {
- switch ($section)
- {
- case 'registration':
- case 'profile':
- return 'user';
- }
- }
+ /**
+ * Returns a valid section for the given section. If it is not valid then null is returned.
+ *
+ * @param string $section The section to get the mapping for
+ * @param object|null $item The content item or null
+ *
+ * @return string|null The new section or null
+ *
+ * @since 4.0.0
+ */
+ public function validateSection($section, $item = null)
+ {
+ if (Factory::getApplication()->isClient('site')) {
+ switch ($section) {
+ case 'registration':
+ case 'profile':
+ return 'user';
+ }
+ }
- if ($section === 'user')
- {
- return $section;
- }
+ if ($section === 'user') {
+ return $section;
+ }
- // We don't know other sections.
- return null;
- }
+ // We don't know other sections.
+ return null;
+ }
- /**
- * Returns valid contexts.
- *
- * @return array Associative array with contexts as keys and translated strings as values
- *
- * @since 4.0.0
- */
- public function getContexts(): array
- {
- $language = Factory::getApplication()->getLanguage();
- $language->load('com_users', JPATH_ADMINISTRATOR);
+ /**
+ * Returns valid contexts.
+ *
+ * @return array Associative array with contexts as keys and translated strings as values
+ *
+ * @since 4.0.0
+ */
+ public function getContexts(): array
+ {
+ $language = Factory::getApplication()->getLanguage();
+ $language->load('com_users', JPATH_ADMINISTRATOR);
- return [
- 'com_users.user' => $language->_('COM_USERS'),
- ];
- }
+ return [
+ 'com_users.user' => $language->_('COM_USERS'),
+ ];
+ }
}
diff --git a/administrator/components/com_users/src/Field/GroupparentField.php b/administrator/components/com_users/src/Field/GroupparentField.php
index 8bf8532860612..bfce429260ac5 100644
--- a/administrator/components/com_users/src/Field/GroupparentField.php
+++ b/administrator/components/com_users/src/Field/GroupparentField.php
@@ -1,4 +1,5 @@
$userGroupsOptionsData)
- {
- if ((int) $userGroupsOptionsData->parent_id === (int) $fatherId)
- {
- unset($userGroupsOptions[$userGroupsOptionsId]);
+ /**
+ * Method to clean the Usergroup Options from all children starting by a given father
+ *
+ * @param array $userGroupsOptions The usergroup options to clean
+ * @param integer $fatherId The father ID to start with
+ *
+ * @return array The cleaned field options
+ *
+ * @since 3.9.4
+ */
+ private function cleanOptionsChildrenByFather($userGroupsOptions, $fatherId)
+ {
+ foreach ($userGroupsOptions as $userGroupsOptionsId => $userGroupsOptionsData) {
+ if ((int) $userGroupsOptionsData->parent_id === (int) $fatherId) {
+ unset($userGroupsOptions[$userGroupsOptionsId]);
- $userGroupsOptions = $this->cleanOptionsChildrenByFather($userGroupsOptions, $userGroupsOptionsId);
- }
- }
+ $userGroupsOptions = $this->cleanOptionsChildrenByFather($userGroupsOptions, $userGroupsOptionsId);
+ }
+ }
- return $userGroupsOptions;
- }
+ return $userGroupsOptions;
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects
- *
- * @since 1.6
- */
- protected function getOptions()
- {
- $options = UserGroupsHelper::getInstance()->getAll();
- $currentGroupId = (int) Factory::getApplication()->input->get('id', 0, 'int');
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects
+ *
+ * @since 1.6
+ */
+ protected function getOptions()
+ {
+ $options = UserGroupsHelper::getInstance()->getAll();
+ $currentGroupId = (int) Factory::getApplication()->input->get('id', 0, 'int');
- // Prevent to set yourself as parent
- if ($currentGroupId)
- {
- unset($options[$currentGroupId]);
- }
+ // Prevent to set yourself as parent
+ if ($currentGroupId) {
+ unset($options[$currentGroupId]);
+ }
- // We should not remove any groups when we are creating a new group
- if ($currentGroupId !== 0)
- {
- // Prevent parenting direct children and children of children of this item.
- $options = $this->cleanOptionsChildrenByFather($options, $currentGroupId);
- }
+ // We should not remove any groups when we are creating a new group
+ if ($currentGroupId !== 0) {
+ // Prevent parenting direct children and children of children of this item.
+ $options = $this->cleanOptionsChildrenByFather($options, $currentGroupId);
+ }
- $options = array_values($options);
- $isSuperAdmin = Factory::getUser()->authorise('core.admin');
+ $options = array_values($options);
+ $isSuperAdmin = Factory::getUser()->authorise('core.admin');
- // Pad the option text with spaces using depth level as a multiplier.
- for ($i = 0, $n = count($options); $i < $n; $i++)
- {
- // Show groups only if user is super admin or group is not super admin
- if ($isSuperAdmin || !Access::checkGroup($options[$i]->id, 'core.admin'))
- {
- $options[$i]->value = $options[$i]->id;
- $options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
- }
- else
- {
- unset($options[$i]);
- }
- }
+ // Pad the option text with spaces using depth level as a multiplier.
+ for ($i = 0, $n = count($options); $i < $n; $i++) {
+ // Show groups only if user is super admin or group is not super admin
+ if ($isSuperAdmin || !Access::checkGroup($options[$i]->id, 'core.admin')) {
+ $options[$i]->value = $options[$i]->id;
+ $options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
+ } else {
+ unset($options[$i]);
+ }
+ }
- // Merge any additional options in the XML definition.
- return array_merge(parent::getOptions(), $options);
- }
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/administrator/components/com_users/src/Field/LevelsField.php b/administrator/components/com_users/src/Field/LevelsField.php
index ddf70480a85c5..ee78f7a752b8f 100644
--- a/administrator/components/com_users/src/Field/LevelsField.php
+++ b/administrator/components/com_users/src/Field/LevelsField.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select('name AS text, element AS value')
- ->from('#__extensions')
- ->where('enabled >= 1')
- ->where('type =' . $db->quote('component'));
-
- $items = $db->setQuery($query)->loadObjectList();
-
- if (count($items))
- {
- $lang = Factory::getLanguage();
-
- foreach ($items as &$item)
- {
- // Load language
- $extension = $item->value;
- $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
- $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
- || $lang->load("$extension.sys", $source);
-
- // Translate component name
- $item->text = Text::_($item->text);
- }
-
- // Sort by component name
- $items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
- }
-
- return $items;
- }
-
- /**
- * Get a list of the actions for the component or code actions.
- *
- * @param string $component The name of the component.
- *
- * @return array
- *
- * @since 1.6
- */
- public static function getDebugActions($component = null)
- {
- $actions = array();
-
- // Try to get actions for the component
- if (!empty($component))
- {
- $component_actions = Access::getActionsFromFile(JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml');
-
- if (!empty($component_actions))
- {
- foreach ($component_actions as &$action)
- {
- $descr = (string) $action->title;
-
- if (!empty($action->description))
- {
- $descr = (string) $action->description;
- }
-
- $actions[$action->title] = array($action->name, $descr);
- }
- }
- }
-
- // Use default actions from configuration if no component selected or component doesn't have actions
- if (empty($actions))
- {
- $filename = JPATH_ADMINISTRATOR . '/components/com_config/forms/application.xml';
-
- if (is_file($filename))
- {
- $xml = simplexml_load_file($filename);
-
- foreach ($xml->children()->fieldset as $fieldset)
- {
- if ('permissions' == (string) $fieldset['name'])
- {
- foreach ($fieldset->children() as $field)
- {
- if ('rules' == (string) $field['name'])
- {
- foreach ($field->children() as $action)
- {
- $descr = (string) $action['title'];
-
- if (isset($action['description']) && !empty($action['description']))
- {
- $descr = (string) $action['description'];
- }
-
- $actions[(string) $action['title']] = array(
- (string) $action['name'],
- $descr
- );
- }
-
- break;
- }
- }
- }
- }
-
- // Load language
- $lang = Factory::getLanguage();
- $extension = 'com_config';
- $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
-
- $lang->load($extension, JPATH_ADMINISTRATOR, null, false, false)
- || $lang->load($extension, $source, null, false, false)
- || $lang->load($extension, JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
- || $lang->load($extension, $source, $lang->getDefault(), false, false);
- }
- }
-
- return $actions;
- }
-
- /**
- * Get a list of filter options for the levels.
- *
- * @return array An array of \JHtmlOption elements.
- */
- public static function getLevelsOptions()
- {
- // Build the filter options.
- $options = array();
- $options[] = HTMLHelper::_('select.option', '1', Text::sprintf('COM_USERS_OPTION_LEVEL_COMPONENT', 1));
- $options[] = HTMLHelper::_('select.option', '2', Text::sprintf('COM_USERS_OPTION_LEVEL_CATEGORY', 2));
- $options[] = HTMLHelper::_('select.option', '3', Text::sprintf('COM_USERS_OPTION_LEVEL_DEEPER', 3));
- $options[] = HTMLHelper::_('select.option', '4', '4');
- $options[] = HTMLHelper::_('select.option', '5', '5');
- $options[] = HTMLHelper::_('select.option', '6', '6');
-
- return $options;
- }
+ /**
+ * Get a list of the components.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public static function getComponents()
+ {
+ // Initialise variable.
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('name AS text, element AS value')
+ ->from('#__extensions')
+ ->where('enabled >= 1')
+ ->where('type =' . $db->quote('component'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ if (count($items)) {
+ $lang = Factory::getLanguage();
+
+ foreach ($items as &$item) {
+ // Load language
+ $extension = $item->value;
+ $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
+ $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
+ || $lang->load("$extension.sys", $source);
+
+ // Translate component name
+ $item->text = Text::_($item->text);
+ }
+
+ // Sort by component name
+ $items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Get a list of the actions for the component or code actions.
+ *
+ * @param string $component The name of the component.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public static function getDebugActions($component = null)
+ {
+ $actions = array();
+
+ // Try to get actions for the component
+ if (!empty($component)) {
+ $component_actions = Access::getActionsFromFile(JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml');
+
+ if (!empty($component_actions)) {
+ foreach ($component_actions as &$action) {
+ $descr = (string) $action->title;
+
+ if (!empty($action->description)) {
+ $descr = (string) $action->description;
+ }
+
+ $actions[$action->title] = array($action->name, $descr);
+ }
+ }
+ }
+
+ // Use default actions from configuration if no component selected or component doesn't have actions
+ if (empty($actions)) {
+ $filename = JPATH_ADMINISTRATOR . '/components/com_config/forms/application.xml';
+
+ if (is_file($filename)) {
+ $xml = simplexml_load_file($filename);
+
+ foreach ($xml->children()->fieldset as $fieldset) {
+ if ('permissions' == (string) $fieldset['name']) {
+ foreach ($fieldset->children() as $field) {
+ if ('rules' == (string) $field['name']) {
+ foreach ($field->children() as $action) {
+ $descr = (string) $action['title'];
+
+ if (isset($action['description']) && !empty($action['description'])) {
+ $descr = (string) $action['description'];
+ }
+
+ $actions[(string) $action['title']] = array(
+ (string) $action['name'],
+ $descr
+ );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Load language
+ $lang = Factory::getLanguage();
+ $extension = 'com_config';
+ $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
+
+ $lang->load($extension, JPATH_ADMINISTRATOR, null, false, false)
+ || $lang->load($extension, $source, null, false, false)
+ || $lang->load($extension, JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
+ || $lang->load($extension, $source, $lang->getDefault(), false, false);
+ }
+ }
+
+ return $actions;
+ }
+
+ /**
+ * Get a list of filter options for the levels.
+ *
+ * @return array An array of \JHtmlOption elements.
+ */
+ public static function getLevelsOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '1', Text::sprintf('COM_USERS_OPTION_LEVEL_COMPONENT', 1));
+ $options[] = HTMLHelper::_('select.option', '2', Text::sprintf('COM_USERS_OPTION_LEVEL_CATEGORY', 2));
+ $options[] = HTMLHelper::_('select.option', '3', Text::sprintf('COM_USERS_OPTION_LEVEL_DEEPER', 3));
+ $options[] = HTMLHelper::_('select.option', '4', '4');
+ $options[] = HTMLHelper::_('select.option', '5', '5');
+ $options[] = HTMLHelper::_('select.option', '6', '6');
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_users/src/Helper/Mfa.php b/administrator/components/com_users/src/Helper/Mfa.php
index 34b7ab5c84ae5..1964296ff4785 100644
--- a/administrator/components/com_users/src/Helper/Mfa.php
+++ b/administrator/components/com_users/src/Helper/Mfa.php
@@ -1,4 +1,5 @@
input->getCmd('option', '') === 'com_users')
- {
- $app->getLanguage()->load('com_users');
- $app->getDocument()
- ->getWebAssetManager()
- ->getRegistry()
- ->addExtensionRegistryFile('com_users');
- }
-
- // Get a model
- /** @var MVCFactoryInterface $factory */
- $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
-
- /** @var MethodsModel $methodsModel */
- $methodsModel = $factory->createModel('Methods', 'Administrator');
- /** @var BackupcodesModel $methodsModel */
- $backupCodesModel = $factory->createModel('Backupcodes', 'Administrator');
-
- // Get a view object
- $appRoot = $app->isClient('site') ? \JPATH_SITE : \JPATH_ADMINISTRATOR;
- $prefix = $app->isClient('site') ? 'Site' : 'Administrator';
- /** @var HtmlView $view */
- $view = $factory->createView('Methods', $prefix, 'Html',
- [
- 'base_path' => $appRoot . '/components/com_users',
- ]
- );
- $view->setModel($methodsModel, true);
- /** @noinspection PhpParamsInspection */
- $view->setModel($backupCodesModel);
- $view->document = $app->getDocument();
- $view->returnURL = base64_encode(Uri::getInstance()->toString());
- $view->user = $user;
- $view->set('forHMVC', true);
-
- @ob_start();
-
- try
- {
- $view->display();
- }
- catch (\Throwable $e)
- {
- @ob_end_clean();
-
- /**
- * This is intentional! When you are developing a Multi-factor Authentication plugin you
- * will inevitably mess something up and end up with an error. This would cause the
- * entire MFA configuration page to disappear. No problem! Set Debug System to Yes in
- * Global Configuration and you can see the error exception which will help you solve
- * your problem.
- */
- if (defined('JDEBUG') && JDEBUG)
- {
- throw $e;
- }
-
- return null;
- }
-
- return @ob_get_clean();
- }
-
- /**
- * Get a list of all of the MFA Methods
- *
- * @return MethodDescriptor[]
- * @since 4.2.0
- */
- public static function getMfaMethods(): array
- {
- PluginHelper::importPlugin('multifactorauth');
-
- if (is_null(self::$allMFAs))
- {
- // Get all the plugin results
- $event = new GetMethod;
- $temp = Factory::getApplication()
- ->getDispatcher()
- ->dispatch($event->getName(), $event)
- ->getArgument('result', []);
-
- // Normalize the results
- self::$allMFAs = [];
-
- foreach ($temp as $method)
- {
- if (!is_array($method) && !($method instanceof MethodDescriptor))
- {
- continue;
- }
-
- $method = $method instanceof MethodDescriptor
- ? $method : new MethodDescriptor($method);
-
- if (empty($method['name']))
- {
- continue;
- }
-
- self::$allMFAs[$method['name']] = $method;
- }
- }
-
- return self::$allMFAs;
- }
-
- /**
- * Is the current user allowed to add/edit MFA methods for $user?
- *
- * This is only allowed if I am adding / editing methods for myself.
- *
- * If the target user is a member of any group disallowed to use MFA this will return false.
- *
- * @param User|null $user The user you want to know if we're allowed to edit
- *
- * @return boolean
- * @throws Exception
- * @since 4.2.0
- */
- public static function canAddEditMethod(?User $user = null): bool
- {
- // Cannot do MFA operations on no user or a guest user.
- if (is_null($user) || $user->guest)
- {
- return false;
- }
-
- // If the user is in a user group which disallows MFA we cannot allow adding / editing methods.
- $neverMFAGroups = ComponentHelper::getParams('com_users')->get('neverMFAUserGroups', []);
- $neverMFAGroups = is_array($neverMFAGroups) ? $neverMFAGroups : [];
-
- if (count(array_intersect($user->getAuthorisedGroups(), $neverMFAGroups)))
- {
- return false;
- }
-
- // Check if this is the same as the logged-in user.
- $myUser = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- return $myUser->id === $user->id;
- }
-
- /**
- * Is the current user allowed to delete MFA methods / disable MFA for $user?
- *
- * This is allowed if:
- * - The user being queried is the same as the logged-in user
- * - The logged-in user is a Super User AND the queried user is NOT a Super User.
- *
- * Note that Super Users can be edited by their own user only for security reasons. If a Super
- * User gets locked out they must use the Backup Codes to regain access. If that's not possible,
- * they will need to delete their records from the `#__user_mfa` table.
- *
- * @param User|null $user The user being queried.
- *
- * @return boolean
- * @throws Exception
- * @since 4.2.0
- */
- public static function canDeleteMethod(?User $user = null): bool
- {
- // Cannot do MFA operations on no user or a guest user.
- if (is_null($user) || $user->guest)
- {
- return false;
- }
-
- $myUser = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- return $myUser->id === $user->id
- || ($myUser->authorise('core.admin') && !$user->authorise('core.admin'));
- }
-
- /**
- * Return all MFA records for a specific user
- *
- * @param int|null $userId User ID. NULL for currently logged in user.
- *
- * @return MfaTable[]
- * @throws Exception
- *
- * @since 4.2.0
- */
- public static function getUserMfaRecords(?int $userId): array
- {
- if (empty($userId))
- {
- $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
- $userId = $user->id ?: 0;
- }
-
- /** @var DatabaseDriver $db */
- $db = Factory::getContainer()->get('DatabaseDriver');
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__user_mfa'))
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->bind(':user_id', $userId, ParameterType::INTEGER);
-
- try
- {
- $ids = $db->setQuery($query)->loadColumn() ?: [];
- }
- catch (Exception $e)
- {
- $ids = [];
- }
-
- if (empty($ids))
- {
- return [];
- }
-
- /** @var MVCFactoryInterface $factory */
- $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
-
- // Map all results to MFA table objects
- $records = array_map(
- function ($id) use ($factory)
- {
- /** @var MfaTable $record */
- $record = $factory->createTable('Mfa', 'Administrator');
- $loaded = $record->load($id);
-
- return $loaded ? $record : null;
- },
- $ids
- );
-
- // Let's remove Methods we couldn't decrypt when reading from the database.
- $hasBackupCodes = false;
-
- $records = array_filter(
- $records,
- function ($record) use (&$hasBackupCodes)
- {
- $isValid = !is_null($record) && (!empty($record->options));
-
- if ($isValid && ($record->method === 'backupcodes'))
- {
- $hasBackupCodes = true;
- }
-
- return $isValid;
- }
- );
-
- // If the only Method is backup codes it's as good as having no records
- if ((count($records) === 1) && $hasBackupCodes)
- {
- return [];
- }
-
- return $records;
- }
-
- /**
- * Are the conditions for showing the MFA configuration interface met?
- *
- * @param User|null $user The user to be configured
- *
- * @return boolean
- * @throws Exception
- * @since 4.2.0
- */
- public static function canShowConfigurationInterface(?User $user = null): bool
- {
- // If I have no user to check against that's all the checking I can do.
- if (empty($user))
- {
- return false;
- }
-
- // I need at least one MFA method plugin for the setup interface to make any sense.
- $plugins = PluginHelper::getPlugin('multifactorauth');
-
- if (count($plugins) < 1)
- {
- return false;
- }
-
- /** @var CMSApplication $app */
- $app = Factory::getApplication();
-
- // We can only show a configuration page in the front- or backend application.
- if (!$app->isClient('site') && !$app->isClient('administrator'))
- {
- return false;
- }
-
- // Only show the configuration page if we have an HTML document
- if (!($app->getDocument() instanceof HtmlDocument))
- {
- return false;
- }
-
- // I must be able to add, edit or delete the user's MFA settings
- return self::canAddEditMethod($user) || self::canDeleteMethod($user);
- }
+ /**
+ * Cache of all currently active MFAs
+ *
+ * @var array|null
+ * @since 4.2.0
+ */
+ protected static $allMFAs = null;
+
+ /**
+ * Are we inside the administrator application
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ protected static $isAdmin = null;
+
+ /**
+ * Get the HTML for the Multi-factor Authentication configuration interface for a user.
+ *
+ * This helper method uses a sort of primitive HMVC to display the com_users' Methods page which
+ * renders the MFA configuration interface.
+ *
+ * @param User $user The user we are going to show the configuration UI for.
+ *
+ * @return string|null The HTML of the UI; null if we cannot / must not show it.
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public static function getConfigurationInterface(User $user): ?string
+ {
+ // Check the conditions
+ if (!self::canShowConfigurationInterface($user)) {
+ return null;
+ }
+
+ /** @var CMSApplication $app */
+ $app = Factory::getApplication();
+
+ if (!$app->input->getCmd('option', '') === 'com_users') {
+ $app->getLanguage()->load('com_users');
+ $app->getDocument()
+ ->getWebAssetManager()
+ ->getRegistry()
+ ->addExtensionRegistryFile('com_users');
+ }
+
+ // Get a model
+ /** @var MVCFactoryInterface $factory */
+ $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
+
+ /** @var MethodsModel $methodsModel */
+ $methodsModel = $factory->createModel('Methods', 'Administrator');
+ /** @var BackupcodesModel $methodsModel */
+ $backupCodesModel = $factory->createModel('Backupcodes', 'Administrator');
+
+ // Get a view object
+ $appRoot = $app->isClient('site') ? \JPATH_SITE : \JPATH_ADMINISTRATOR;
+ $prefix = $app->isClient('site') ? 'Site' : 'Administrator';
+ /** @var HtmlView $view */
+ $view = $factory->createView(
+ 'Methods',
+ $prefix,
+ 'Html',
+ [
+ 'base_path' => $appRoot . '/components/com_users',
+ ]
+ );
+ $view->setModel($methodsModel, true);
+ /** @noinspection PhpParamsInspection */
+ $view->setModel($backupCodesModel);
+ $view->document = $app->getDocument();
+ $view->returnURL = base64_encode(Uri::getInstance()->toString());
+ $view->user = $user;
+ $view->set('forHMVC', true);
+
+ @ob_start();
+
+ try {
+ $view->display();
+ } catch (\Throwable $e) {
+ @ob_end_clean();
+
+ /**
+ * This is intentional! When you are developing a Multi-factor Authentication plugin you
+ * will inevitably mess something up and end up with an error. This would cause the
+ * entire MFA configuration page to disappear. No problem! Set Debug System to Yes in
+ * Global Configuration and you can see the error exception which will help you solve
+ * your problem.
+ */
+ if (defined('JDEBUG') && JDEBUG) {
+ throw $e;
+ }
+
+ return null;
+ }
+
+ return @ob_get_clean();
+ }
+
+ /**
+ * Get a list of all of the MFA Methods
+ *
+ * @return MethodDescriptor[]
+ * @since 4.2.0
+ */
+ public static function getMfaMethods(): array
+ {
+ PluginHelper::importPlugin('multifactorauth');
+
+ if (is_null(self::$allMFAs)) {
+ // Get all the plugin results
+ $event = new GetMethod();
+ $temp = Factory::getApplication()
+ ->getDispatcher()
+ ->dispatch($event->getName(), $event)
+ ->getArgument('result', []);
+
+ // Normalize the results
+ self::$allMFAs = [];
+
+ foreach ($temp as $method) {
+ if (!is_array($method) && !($method instanceof MethodDescriptor)) {
+ continue;
+ }
+
+ $method = $method instanceof MethodDescriptor
+ ? $method : new MethodDescriptor($method);
+
+ if (empty($method['name'])) {
+ continue;
+ }
+
+ self::$allMFAs[$method['name']] = $method;
+ }
+ }
+
+ return self::$allMFAs;
+ }
+
+ /**
+ * Is the current user allowed to add/edit MFA methods for $user?
+ *
+ * This is only allowed if I am adding / editing methods for myself.
+ *
+ * If the target user is a member of any group disallowed to use MFA this will return false.
+ *
+ * @param User|null $user The user you want to know if we're allowed to edit
+ *
+ * @return boolean
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public static function canAddEditMethod(?User $user = null): bool
+ {
+ // Cannot do MFA operations on no user or a guest user.
+ if (is_null($user) || $user->guest) {
+ return false;
+ }
+
+ // If the user is in a user group which disallows MFA we cannot allow adding / editing methods.
+ $neverMFAGroups = ComponentHelper::getParams('com_users')->get('neverMFAUserGroups', []);
+ $neverMFAGroups = is_array($neverMFAGroups) ? $neverMFAGroups : [];
+
+ if (count(array_intersect($user->getAuthorisedGroups(), $neverMFAGroups))) {
+ return false;
+ }
+
+ // Check if this is the same as the logged-in user.
+ $myUser = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ return $myUser->id === $user->id;
+ }
+
+ /**
+ * Is the current user allowed to delete MFA methods / disable MFA for $user?
+ *
+ * This is allowed if:
+ * - The user being queried is the same as the logged-in user
+ * - The logged-in user is a Super User AND the queried user is NOT a Super User.
+ *
+ * Note that Super Users can be edited by their own user only for security reasons. If a Super
+ * User gets locked out they must use the Backup Codes to regain access. If that's not possible,
+ * they will need to delete their records from the `#__user_mfa` table.
+ *
+ * @param User|null $user The user being queried.
+ *
+ * @return boolean
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public static function canDeleteMethod(?User $user = null): bool
+ {
+ // Cannot do MFA operations on no user or a guest user.
+ if (is_null($user) || $user->guest) {
+ return false;
+ }
+
+ $myUser = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ return $myUser->id === $user->id
+ || ($myUser->authorise('core.admin') && !$user->authorise('core.admin'));
+ }
+
+ /**
+ * Return all MFA records for a specific user
+ *
+ * @param int|null $userId User ID. NULL for currently logged in user.
+ *
+ * @return MfaTable[]
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public static function getUserMfaRecords(?int $userId): array
+ {
+ if (empty($userId)) {
+ $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
+ $userId = $user->id ?: 0;
+ }
+
+ /** @var DatabaseDriver $db */
+ $db = Factory::getContainer()->get('DatabaseDriver');
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__user_mfa'))
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->bind(':user_id', $userId, ParameterType::INTEGER);
+
+ try {
+ $ids = $db->setQuery($query)->loadColumn() ?: [];
+ } catch (Exception $e) {
+ $ids = [];
+ }
+
+ if (empty($ids)) {
+ return [];
+ }
+
+ /** @var MVCFactoryInterface $factory */
+ $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
+
+ // Map all results to MFA table objects
+ $records = array_map(
+ function ($id) use ($factory) {
+ /** @var MfaTable $record */
+ $record = $factory->createTable('Mfa', 'Administrator');
+ $loaded = $record->load($id);
+
+ return $loaded ? $record : null;
+ },
+ $ids
+ );
+
+ // Let's remove Methods we couldn't decrypt when reading from the database.
+ $hasBackupCodes = false;
+
+ $records = array_filter(
+ $records,
+ function ($record) use (&$hasBackupCodes) {
+ $isValid = !is_null($record) && (!empty($record->options));
+
+ if ($isValid && ($record->method === 'backupcodes')) {
+ $hasBackupCodes = true;
+ }
+
+ return $isValid;
+ }
+ );
+
+ // If the only Method is backup codes it's as good as having no records
+ if ((count($records) === 1) && $hasBackupCodes) {
+ return [];
+ }
+
+ return $records;
+ }
+
+ /**
+ * Are the conditions for showing the MFA configuration interface met?
+ *
+ * @param User|null $user The user to be configured
+ *
+ * @return boolean
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public static function canShowConfigurationInterface(?User $user = null): bool
+ {
+ // If I have no user to check against that's all the checking I can do.
+ if (empty($user)) {
+ return false;
+ }
+
+ // I need at least one MFA method plugin for the setup interface to make any sense.
+ $plugins = PluginHelper::getPlugin('multifactorauth');
+
+ if (count($plugins) < 1) {
+ return false;
+ }
+
+ /** @var CMSApplication $app */
+ $app = Factory::getApplication();
+
+ // We can only show a configuration page in the front- or backend application.
+ if (!$app->isClient('site') && !$app->isClient('administrator')) {
+ return false;
+ }
+
+ // Only show the configuration page if we have an HTML document
+ if (!($app->getDocument() instanceof HtmlDocument)) {
+ return false;
+ }
+
+ // I must be able to add, edit or delete the user's MFA settings
+ return self::canAddEditMethod($user) || self::canDeleteMethod($user);
+ }
}
diff --git a/administrator/components/com_users/src/Helper/UsersHelper.php b/administrator/components/com_users/src/Helper/UsersHelper.php
index e8ef7828aead1..41e13cb51662d 100644
--- a/administrator/components/com_users/src/Helper/UsersHelper.php
+++ b/administrator/components/com_users/src/Helper/UsersHelper.php
@@ -1,4 +1,5 @@
getAll();
-
- foreach ($options as &$option)
- {
- $option->value = $option->id;
- $option->text = str_repeat('- ', $option->level) . $option->title;
- }
-
- return $options;
- }
-
- /**
- * Creates a list of range options used in filter select list
- * used in com_users on users view
- *
- * @return array
- *
- * @since 2.5
- */
- public static function getRangeOptions()
- {
- $options = array(
- HTMLHelper::_('select.option', 'today', Text::_('COM_USERS_OPTION_RANGE_TODAY')),
- HTMLHelper::_('select.option', 'past_week', Text::_('COM_USERS_OPTION_RANGE_PAST_WEEK')),
- HTMLHelper::_('select.option', 'past_1month', Text::_('COM_USERS_OPTION_RANGE_PAST_1MONTH')),
- HTMLHelper::_('select.option', 'past_3month', Text::_('COM_USERS_OPTION_RANGE_PAST_3MONTH')),
- HTMLHelper::_('select.option', 'past_6month', Text::_('COM_USERS_OPTION_RANGE_PAST_6MONTH')),
- HTMLHelper::_('select.option', 'past_year', Text::_('COM_USERS_OPTION_RANGE_PAST_YEAR')),
- HTMLHelper::_('select.option', 'post_year', Text::_('COM_USERS_OPTION_RANGE_POST_YEAR')),
- );
-
- return $options;
- }
-
- /**
- * No longer used.
- *
- * @return array
- *
- * @since 3.2.0
- * @throws \Exception
- *
- * @deprecated 4.2.0 Will be removed in 5.0
- */
- public static function getTwoFactorMethods()
- {
- return [];
- }
-
- /**
- * Get a list of the User Groups for Viewing Access Levels
- *
- * @param string $rules User Groups in JSON format
- *
- * @return string $groups Comma separated list of User Groups
- *
- * @since 3.6
- */
- public static function getVisibleByGroups($rules)
- {
- $rules = json_decode($rules);
-
- if (!$rules)
- {
- return false;
- }
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title', 'text'))
- ->from($db->quoteName('#__usergroups'))
- ->whereIn($db->quoteName('id'), $rules);
- $db->setQuery($query);
-
- $groups = $db->loadColumn();
- $groups = implode(', ', $groups);
-
- return $groups;
- }
-
- /**
- * Returns a valid section for users. If it is not valid then null
- * is returned.
- *
- * @param string $section The section to get the mapping for
- *
- * @return string|null The new section
- *
- * @since 3.7.0
- * @throws \Exception
- * @deprecated 5.0 Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::validateSection() instead.
- */
- public static function validateSection($section)
- {
- return Factory::getApplication()->bootComponent('com_users')->validateSection($section, null);
- }
-
- /**
- * Returns valid contexts
- *
- * @return array
- *
- * @since 3.7.0
- * @deprecated 5.0 Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::getContexts() instead.
- */
- public static function getContexts()
- {
- return Factory::getApplication()->bootComponent('com_users')->getContexts();
- }
+ /**
+ * @var CMSObject A cache for the available actions.
+ * @since 1.6
+ */
+ protected static $actions;
+
+ /**
+ * Get a list of filter options for the blocked state of a user.
+ *
+ * @return array An array of \JHtmlOption elements.
+ *
+ * @since 1.6
+ */
+ public static function getStateOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '0', Text::_('JENABLED'));
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('JDISABLED'));
+
+ return $options;
+ }
+
+ /**
+ * Get a list of filter options for the activated state of a user.
+ *
+ * @return array An array of \JHtmlOption elements.
+ *
+ * @since 1.6
+ */
+ public static function getActiveOptions()
+ {
+ // Build the filter options.
+ $options = array();
+ $options[] = HTMLHelper::_('select.option', '0', Text::_('COM_USERS_ACTIVATED'));
+ $options[] = HTMLHelper::_('select.option', '1', Text::_('COM_USERS_UNACTIVATED'));
+
+ return $options;
+ }
+
+ /**
+ * Get a list of the user groups for filtering.
+ *
+ * @return array An array of \JHtmlOption elements.
+ *
+ * @since 1.6
+ */
+ public static function getGroups()
+ {
+ $options = UserGroupsHelper::getInstance()->getAll();
+
+ foreach ($options as &$option) {
+ $option->value = $option->id;
+ $option->text = str_repeat('- ', $option->level) . $option->title;
+ }
+
+ return $options;
+ }
+
+ /**
+ * Creates a list of range options used in filter select list
+ * used in com_users on users view
+ *
+ * @return array
+ *
+ * @since 2.5
+ */
+ public static function getRangeOptions()
+ {
+ $options = array(
+ HTMLHelper::_('select.option', 'today', Text::_('COM_USERS_OPTION_RANGE_TODAY')),
+ HTMLHelper::_('select.option', 'past_week', Text::_('COM_USERS_OPTION_RANGE_PAST_WEEK')),
+ HTMLHelper::_('select.option', 'past_1month', Text::_('COM_USERS_OPTION_RANGE_PAST_1MONTH')),
+ HTMLHelper::_('select.option', 'past_3month', Text::_('COM_USERS_OPTION_RANGE_PAST_3MONTH')),
+ HTMLHelper::_('select.option', 'past_6month', Text::_('COM_USERS_OPTION_RANGE_PAST_6MONTH')),
+ HTMLHelper::_('select.option', 'past_year', Text::_('COM_USERS_OPTION_RANGE_PAST_YEAR')),
+ HTMLHelper::_('select.option', 'post_year', Text::_('COM_USERS_OPTION_RANGE_POST_YEAR')),
+ );
+
+ return $options;
+ }
+
+ /**
+ * No longer used.
+ *
+ * @return array
+ *
+ * @since 3.2.0
+ * @throws \Exception
+ *
+ * @deprecated 4.2.0 Will be removed in 5.0
+ */
+ public static function getTwoFactorMethods()
+ {
+ return [];
+ }
+
+ /**
+ * Get a list of the User Groups for Viewing Access Levels
+ *
+ * @param string $rules User Groups in JSON format
+ *
+ * @return string $groups Comma separated list of User Groups
+ *
+ * @since 3.6
+ */
+ public static function getVisibleByGroups($rules)
+ {
+ $rules = json_decode($rules);
+
+ if (!$rules) {
+ return false;
+ }
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title', 'text'))
+ ->from($db->quoteName('#__usergroups'))
+ ->whereIn($db->quoteName('id'), $rules);
+ $db->setQuery($query);
+
+ $groups = $db->loadColumn();
+ $groups = implode(', ', $groups);
+
+ return $groups;
+ }
+
+ /**
+ * Returns a valid section for users. If it is not valid then null
+ * is returned.
+ *
+ * @param string $section The section to get the mapping for
+ *
+ * @return string|null The new section
+ *
+ * @since 3.7.0
+ * @throws \Exception
+ * @deprecated 5.0 Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::validateSection() instead.
+ */
+ public static function validateSection($section)
+ {
+ return Factory::getApplication()->bootComponent('com_users')->validateSection($section, null);
+ }
+
+ /**
+ * Returns valid contexts
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ * @deprecated 5.0 Use \Joomla\Component\Users\Administrator\Extension\UsersComponent::getContexts() instead.
+ */
+ public static function getContexts()
+ {
+ return Factory::getApplication()->bootComponent('com_users')->getContexts();
+ }
}
diff --git a/administrator/components/com_users/src/Model/BackupcodesModel.php b/administrator/components/com_users/src/Model/BackupcodesModel.php
index 26d5d2abd5f00..442a4ed224098 100644
--- a/administrator/components/com_users/src/Model/BackupcodesModel.php
+++ b/administrator/components/com_users/src/Model/BackupcodesModel.php
@@ -1,4 +1,5 @@
getIdentity() ?:
- Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
- $loaded = $record->load(
- [
- 'user_id' => $user->id,
- 'method' => 'backupcodes',
- ]
- );
-
- if (!$loaded)
- {
- $record = null;
- }
-
- return $record;
- }
-
- /**
- * Generate a new set of backup codes for the specified user. The generated codes are immediately saved to the
- * database and the internal cache is updated.
- *
- * @param User|null $user Which user to generate codes for?
- *
- * @return void
- * @throws \Exception
- * @since 4.2.0
- */
- public function regenerateBackupCodes(User $user = null): void
- {
- // Make sure I have a user
- if (empty($user))
- {
- $user = Factory::getApplication()->getIdentity() ?:
- Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- // Generate backup codes
- $backupCodes = [];
-
- for ($i = 0; $i < 10; $i++)
- {
- // Each backup code is 2 groups of 4 digits
- $backupCodes[$i] = sprintf('%04u%04u', random_int(0, 9999), random_int(0, 9999));
- }
-
- // Save the backup codes to the database and update the cache
- $this->saveBackupCodes($backupCodes, $user);
- }
-
- /**
- * Saves the backup codes to the database
- *
- * @param array $codes An array of exactly 10 elements
- * @param User|null $user The user for which to save the backup codes
- *
- * @return boolean
- * @throws \Exception
- * @since 4.2.0
- */
- public function saveBackupCodes(array $codes, ?User $user = null): bool
- {
- // Make sure I have a user
- if (empty($user))
- {
- $user = Factory::getApplication()->getIdentity() ?:
- Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- // Try to load existing backup codes
- $existingCodes = $this->getBackupCodes($user);
- $jNow = Date::getInstance();
-
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
-
- if (is_null($existingCodes))
- {
- $record->reset();
-
- $newData = [
- 'user_id' => $user->id,
- 'title' => Text::_('COM_USERS_PROFILE_OTEPS'),
- 'method' => 'backupcodes',
- 'default' => 0,
- 'created_on' => $jNow->toSql(),
- 'options' => $codes,
- ];
- }
- else
- {
- $record->load(
- [
- 'user_id' => $user->id,
- 'method' => 'backupcodes',
- ]
- );
-
- $newData = [
- 'options' => $codes,
- ];
- }
-
- $saved = $record->save($newData);
-
- if (!$saved)
- {
- return false;
- }
-
- // Finally, update the cache
- $this->cache[$user->id] = $codes;
-
- return true;
- }
-
- /**
- * Returns the backup codes for the specified user. Cached values will be preferentially returned, therefore you
- * MUST go through this model's Methods ONLY when dealing with backup codes.
- *
- * @param User|null $user The user for which you want the backup codes
- *
- * @return array|null The backup codes, or null if they do not exist
- * @throws \Exception
- * @since 4.2.0
- */
- public function getBackupCodes(User $user = null): ?array
- {
- // Make sure I have a user
- if (empty($user))
- {
- $user = Factory::getApplication()->getIdentity() ?:
- Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- if (isset($this->cache[$user->id]))
- {
- return $this->cache[$user->id];
- }
-
- // If there is no cached record try to load it from the database
- $this->cache[$user->id] = null;
-
- // Try to load the record
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
- $loaded = $record->load(
- [
- 'user_id' => $user->id,
- 'method' => 'backupcodes',
- ]
- );
-
- if ($loaded)
- {
- $this->cache[$user->id] = $record->options;
- }
-
- return $this->cache[$user->id];
- }
-
- /**
- * Check if the provided string is a backup code. If it is, it will be removed from the list (replaced with an empty
- * string) and the codes will be saved to the database. All comparisons are performed in a timing safe manner.
- *
- * @param string $code The code to check
- * @param User|null $user The user to check against
- *
- * @return boolean
- * @throws \Exception
- * @since 4.2.0
- */
- public function isBackupCode($code, ?User $user = null): bool
- {
- // Load the backup codes
- $codes = $this->getBackupCodes($user) ?: array_fill(0, 10, '');
-
- // Keep only the numbers in the provided $code
- $code = filter_var($code, FILTER_SANITIZE_NUMBER_INT);
- $code = trim($code);
-
- // Check if the code is in the array. We always check against ten codes to prevent timing attacks which
- // determine the amount of codes.
- $result = false;
-
- // The two arrays let us always add an element to an array, therefore having PHP expend the same amount of time
- // for the correct code, the incorrect codes and the fake codes.
- $newArray = [];
- $dummyArray = [];
-
- $realLength = count($codes);
- $restLength = 10 - $realLength;
-
- for ($i = 0; $i < $realLength; $i++)
- {
- if (hash_equals($codes[$i], $code))
- {
- // This may seem redundant but makes sure both branches of the if-block are isochronous
- $result = $result || true;
- $newArray[] = '';
- $dummyArray[] = $codes[$i];
- }
- else
- {
- // This may seem redundant but makes sure both branches of the if-block are isochronous
- $result = $result || false;
- $dummyArray[] = '';
- $newArray[] = $codes[$i];
- }
- }
-
- /**
- * This is an intentional waste of time, symmetrical to the code above, making sure
- * evaluating each of the total of ten elements takes the same time. This code should never
- * run UNLESS someone messed up with our backup codes array and it no longer contains 10
- * elements.
- */
- $otherResult = false;
-
- $temp1 = '';
-
- for ($i = 0; $i < 10; $i++)
- {
- $temp1[$i] = random_int(0, 99999999);
- }
-
- for ($i = 0; $i < $restLength; $i++)
- {
- if (Crypt::timingSafeCompare($temp1[$i], $code))
- {
- $otherResult = $otherResult || true;
- $newArray[] = '';
- $dummyArray[] = $temp1[$i];
- }
- else
- {
- $otherResult = $otherResult || false;
- $newArray[] = '';
- $dummyArray[] = $temp1[$i];
- }
- }
-
- // This last check makes sure than an empty code does not validate
- $result = $result && !hash_equals('', $code);
-
- // Save the backup codes
- $this->saveBackupCodes($newArray, $user);
-
- // Finally return the result
- return $result;
- }
+ /**
+ * Caches the backup codes per user ID
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ protected $cache = [];
+
+ /**
+ * Get the backup codes record for the specified user
+ *
+ * @param User|null $user The user in question. Use null for the currently logged in user.
+ *
+ * @return MfaTable|null Record object or null if none is found
+ * @throws \Exception
+ * @since 4.2.0
+ */
+ public function getBackupCodesRecord(User $user = null): ?MfaTable
+ {
+ // Make sure I have a user
+ if (empty($user)) {
+ $user = Factory::getApplication()->getIdentity() ?:
+ Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+ $loaded = $record->load(
+ [
+ 'user_id' => $user->id,
+ 'method' => 'backupcodes',
+ ]
+ );
+
+ if (!$loaded) {
+ $record = null;
+ }
+
+ return $record;
+ }
+
+ /**
+ * Generate a new set of backup codes for the specified user. The generated codes are immediately saved to the
+ * database and the internal cache is updated.
+ *
+ * @param User|null $user Which user to generate codes for?
+ *
+ * @return void
+ * @throws \Exception
+ * @since 4.2.0
+ */
+ public function regenerateBackupCodes(User $user = null): void
+ {
+ // Make sure I have a user
+ if (empty($user)) {
+ $user = Factory::getApplication()->getIdentity() ?:
+ Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ // Generate backup codes
+ $backupCodes = [];
+
+ for ($i = 0; $i < 10; $i++) {
+ // Each backup code is 2 groups of 4 digits
+ $backupCodes[$i] = sprintf('%04u%04u', random_int(0, 9999), random_int(0, 9999));
+ }
+
+ // Save the backup codes to the database and update the cache
+ $this->saveBackupCodes($backupCodes, $user);
+ }
+
+ /**
+ * Saves the backup codes to the database
+ *
+ * @param array $codes An array of exactly 10 elements
+ * @param User|null $user The user for which to save the backup codes
+ *
+ * @return boolean
+ * @throws \Exception
+ * @since 4.2.0
+ */
+ public function saveBackupCodes(array $codes, ?User $user = null): bool
+ {
+ // Make sure I have a user
+ if (empty($user)) {
+ $user = Factory::getApplication()->getIdentity() ?:
+ Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ // Try to load existing backup codes
+ $existingCodes = $this->getBackupCodes($user);
+ $jNow = Date::getInstance();
+
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+
+ if (is_null($existingCodes)) {
+ $record->reset();
+
+ $newData = [
+ 'user_id' => $user->id,
+ 'title' => Text::_('COM_USERS_PROFILE_OTEPS'),
+ 'method' => 'backupcodes',
+ 'default' => 0,
+ 'created_on' => $jNow->toSql(),
+ 'options' => $codes,
+ ];
+ } else {
+ $record->load(
+ [
+ 'user_id' => $user->id,
+ 'method' => 'backupcodes',
+ ]
+ );
+
+ $newData = [
+ 'options' => $codes,
+ ];
+ }
+
+ $saved = $record->save($newData);
+
+ if (!$saved) {
+ return false;
+ }
+
+ // Finally, update the cache
+ $this->cache[$user->id] = $codes;
+
+ return true;
+ }
+
+ /**
+ * Returns the backup codes for the specified user. Cached values will be preferentially returned, therefore you
+ * MUST go through this model's Methods ONLY when dealing with backup codes.
+ *
+ * @param User|null $user The user for which you want the backup codes
+ *
+ * @return array|null The backup codes, or null if they do not exist
+ * @throws \Exception
+ * @since 4.2.0
+ */
+ public function getBackupCodes(User $user = null): ?array
+ {
+ // Make sure I have a user
+ if (empty($user)) {
+ $user = Factory::getApplication()->getIdentity() ?:
+ Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ if (isset($this->cache[$user->id])) {
+ return $this->cache[$user->id];
+ }
+
+ // If there is no cached record try to load it from the database
+ $this->cache[$user->id] = null;
+
+ // Try to load the record
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+ $loaded = $record->load(
+ [
+ 'user_id' => $user->id,
+ 'method' => 'backupcodes',
+ ]
+ );
+
+ if ($loaded) {
+ $this->cache[$user->id] = $record->options;
+ }
+
+ return $this->cache[$user->id];
+ }
+
+ /**
+ * Check if the provided string is a backup code. If it is, it will be removed from the list (replaced with an empty
+ * string) and the codes will be saved to the database. All comparisons are performed in a timing safe manner.
+ *
+ * @param string $code The code to check
+ * @param User|null $user The user to check against
+ *
+ * @return boolean
+ * @throws \Exception
+ * @since 4.2.0
+ */
+ public function isBackupCode($code, ?User $user = null): bool
+ {
+ // Load the backup codes
+ $codes = $this->getBackupCodes($user) ?: array_fill(0, 10, '');
+
+ // Keep only the numbers in the provided $code
+ $code = filter_var($code, FILTER_SANITIZE_NUMBER_INT);
+ $code = trim($code);
+
+ // Check if the code is in the array. We always check against ten codes to prevent timing attacks which
+ // determine the amount of codes.
+ $result = false;
+
+ // The two arrays let us always add an element to an array, therefore having PHP expend the same amount of time
+ // for the correct code, the incorrect codes and the fake codes.
+ $newArray = [];
+ $dummyArray = [];
+
+ $realLength = count($codes);
+ $restLength = 10 - $realLength;
+
+ for ($i = 0; $i < $realLength; $i++) {
+ if (hash_equals($codes[$i], $code)) {
+ // This may seem redundant but makes sure both branches of the if-block are isochronous
+ $result = $result || true;
+ $newArray[] = '';
+ $dummyArray[] = $codes[$i];
+ } else {
+ // This may seem redundant but makes sure both branches of the if-block are isochronous
+ $result = $result || false;
+ $dummyArray[] = '';
+ $newArray[] = $codes[$i];
+ }
+ }
+
+ /**
+ * This is an intentional waste of time, symmetrical to the code above, making sure
+ * evaluating each of the total of ten elements takes the same time. This code should never
+ * run UNLESS someone messed up with our backup codes array and it no longer contains 10
+ * elements.
+ */
+ $otherResult = false;
+
+ $temp1 = '';
+
+ for ($i = 0; $i < 10; $i++) {
+ $temp1[$i] = random_int(0, 99999999);
+ }
+
+ for ($i = 0; $i < $restLength; $i++) {
+ if (Crypt::timingSafeCompare($temp1[$i], $code)) {
+ $otherResult = $otherResult || true;
+ $newArray[] = '';
+ $dummyArray[] = $temp1[$i];
+ } else {
+ $otherResult = $otherResult || false;
+ $newArray[] = '';
+ $dummyArray[] = $temp1[$i];
+ }
+ }
+
+ // This last check makes sure than an empty code does not validate
+ $result = $result && !hash_equals('', $code);
+
+ // Save the backup codes
+ $this->saveBackupCodes($newArray, $user);
+
+ // Finally return the result
+ return $result;
+ }
}
diff --git a/administrator/components/com_users/src/Model/CaptiveModel.php b/administrator/components/com_users/src/Model/CaptiveModel.php
index f61f47f3aa51b..1b873691e31c5 100644
--- a/administrator/components/com_users/src/Model/CaptiveModel.php
+++ b/administrator/components/com_users/src/Model/CaptiveModel.php
@@ -1,4 +1,5 @@
registerEvent('onAfterModuleList', [$this, 'onAfterModuleList']);
- }
-
- /**
- * Get the MFA records for the user which correspond to active plugins
- *
- * @param User|null $user The user for which to fetch records. Skip to use the current user.
- * @param bool $includeBackupCodes Should I include the backup codes record?
- *
- * @return array
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function getRecords(User $user = null, bool $includeBackupCodes = false): array
- {
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- // Get the user's MFA records
- $records = MfaHelper::getUserMfaRecords($user->id);
-
- // No MFA Methods? Then we obviously don't need to display a Captive login page.
- if (empty($records))
- {
- return [];
- }
-
- // Get the enabled MFA Methods' names
- $methodNames = $this->getActiveMethodNames();
-
- // Filter the records based on currently active MFA Methods
- $ret = [];
-
- $methodNames[] = 'backupcodes';
- $methodNames = array_unique($methodNames);
-
- if (!$includeBackupCodes)
- {
- $methodNames = array_filter(
- $methodNames,
- function ($method)
- {
- return $method != 'backupcodes';
- }
- );
- }
-
- foreach ($records as $record)
- {
- // Backup codes must not be included in the list. We add them in the View, at the end of the list.
- if (in_array($record->method, $methodNames))
- {
- $ret[$record->id] = $record;
- }
- }
-
- return $ret;
- }
-
- /**
- * Return all the active MFA Methods' names
- *
- * @return array
- * @since 4.2.0
- */
- private function getActiveMethodNames(): ?array
- {
- if (!is_null($this->activeMFAMethodNames))
- {
- return $this->activeMFAMethodNames;
- }
-
- // Let's get a list of all currently active MFA Methods
- $mfaMethods = MfaHelper::getMfaMethods();
-
- // If no MFA Method is active we can't really display a Captive login page.
- if (empty($mfaMethods))
- {
- $this->activeMFAMethodNames = [];
-
- return $this->activeMFAMethodNames;
- }
-
- // Get a list of just the Method names
- $this->activeMFAMethodNames = [];
-
- foreach ($mfaMethods as $mfaMethod)
- {
- $this->activeMFAMethodNames[] = $mfaMethod['name'];
- }
-
- return $this->activeMFAMethodNames;
- }
-
- /**
- * Get the currently selected MFA record for the current user. If the record ID is empty, it does not correspond to
- * the currently logged in user or does not correspond to an active plugin null is returned instead.
- *
- * @param User|null $user The user for which to fetch records. Skip to use the current user.
- *
- * @return MfaTable|null
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function getRecord(?User $user = null): ?MfaTable
- {
- $id = (int) $this->getState('record_id', null);
-
- if ($id <= 0)
- {
- return null;
- }
-
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
- $loaded = $record->load(
- [
- 'user_id' => $user->id,
- 'id' => $id,
- ]
- );
-
- if (!$loaded)
- {
- return null;
- }
-
- $methodNames = $this->getActiveMethodNames();
-
- if (!in_array($record->method, $methodNames) && ($record->method != 'backupcodes'))
- {
- return null;
- }
-
- return $record;
- }
-
- /**
- * Load the Captive login page render options for a specific MFA record
- *
- * @param MfaTable $record The MFA record to process
- *
- * @return CaptiveRenderOptions The rendering options
- * @since 4.2.0
- */
- public function loadCaptiveRenderOptions(?MfaTable $record): CaptiveRenderOptions
- {
- $renderOptions = new CaptiveRenderOptions;
-
- if (empty($record))
- {
- return $renderOptions;
- }
-
- $event = new Captive($record);
- $results = Factory::getApplication()
- ->getDispatcher()
- ->dispatch($event->getName(), $event)
- ->getArgument('result', []);
-
- if (empty($results))
- {
- if ($record->method === 'backupcodes')
- {
- return $renderOptions->merge(
- [
- 'pre_message' => Text::_('COM_USERS_USER_BACKUPCODES_CAPTIVE_PROMPT'),
- 'input_type' => 'number',
- 'label' => Text::_('COM_USERS_USER_BACKUPCODE'),
- ]
- );
- }
-
- return $renderOptions;
- }
-
- foreach ($results as $result)
- {
- if (empty($result))
- {
- continue;
- }
-
- return $renderOptions->merge($result);
- }
-
- return $renderOptions;
- }
-
- /**
- * Returns the title to display in the Captive login page, or an empty string if no title is to be displayed.
- *
- * @return string
- * @since 4.2.0
- */
- public function getPageTitle(): string
- {
- // In the frontend we can choose if we will display a title
- $showTitle = (bool) ComponentHelper::getParams('com_users')
- ->get('frontend_show_title', 1);
-
- if (!$showTitle)
- {
- return '';
- }
-
- return Text::_('COM_USERS_USER_MULTIFACTOR_AUTH');
- }
-
- /**
- * Translate a MFA Method's name into its human-readable, display name
- *
- * @param string $name The internal MFA Method name
- *
- * @return string
- * @since 4.2.0
- */
- public function translateMethodName(string $name): string
- {
- static $map = null;
-
- if (!is_array($map))
- {
- $map = [];
- $mfaMethods = MfaHelper::getMfaMethods();
-
- if (!empty($mfaMethods))
- {
- foreach ($mfaMethods as $mfaMethod)
- {
- $map[$mfaMethod['name']] = $mfaMethod['display'];
- }
- }
- }
-
- if ($name == 'backupcodes')
- {
- return Text::_('COM_USERS_USER_BACKUPCODES');
- }
-
- return $map[$name] ?? $name;
- }
-
- /**
- * Translate a MFA Method's name into the relative URL if its logo image
- *
- * @param string $name The internal MFA Method name
- *
- * @return string
- * @since 4.2.0
- */
- public function getMethodImage(string $name): string
- {
- static $map = null;
-
- if (!is_array($map))
- {
- $map = [];
- $mfaMethods = MfaHelper::getMfaMethods();
-
- if (!empty($mfaMethods))
- {
- foreach ($mfaMethods as $mfaMethod)
- {
- $map[$mfaMethod['name']] = $mfaMethod['image'];
- }
- }
- }
-
- if ($name == 'backupcodes')
- {
- return 'media/com_users/images/emergency.svg';
- }
-
- return $map[$name] ?? $name;
- }
-
- /**
- * Process the modules list on Joomla! 4.
- *
- * Joomla! 4.x is passing an Event object. The first argument of the event object is the array of modules. After
- * filtering it we have to overwrite the event argument (NOT just return the new list of modules). If a future
- * version of Joomla! uses immutable events we'll have to use Reflection to do that or Joomla! would have to fix
- * the way this event is handled, taking its return into account. For now, we just abuse the mutable event
- * properties - a feature of the event objects we discussed in the Joomla! 4 Working Group back in August 2015.
- *
- * @param Event $event The Joomla! event object
- *
- * @return void
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function onAfterModuleList(Event $event): void
- {
- $modules = $event->getArgument(0);
-
- if (empty($modules))
- {
- return;
- }
-
- $this->filterModules($modules);
-
- $event->setArgument(0, $modules);
- }
-
- /**
- * This is the Method which actually filters the sites modules based on the allowed module positions specified by
- * the user.
- *
- * @param array $modules The list of the site's modules. Passed by reference.
- *
- * @return void The by-reference value is modified instead.
- * @since 4.2.0
- * @throws Exception
- */
- private function filterModules(array &$modules): void
- {
- $allowedPositions = $this->getAllowedModulePositions();
-
- if (empty($allowedPositions))
- {
- $modules = [];
-
- return;
- }
-
- $filtered = [];
-
- foreach ($modules as $module)
- {
- if (in_array($module->position, $allowedPositions))
- {
- $filtered[] = $module;
- }
- }
-
- $modules = $filtered;
- }
-
- /**
- * Get a list of module positions we are allowed to display
- *
- * @return array
- * @throws Exception
- *
- * @since 4.2.0
- */
- private function getAllowedModulePositions(): array
- {
- $isAdmin = Factory::getApplication()->isClient('administrator');
-
- // Load the list of allowed module positions from the component's settings. May be different for front- and back-end
- $configKey = 'allowed_positions_' . ($isAdmin ? 'backend' : 'frontend');
- $res = ComponentHelper::getParams('com_users')->get($configKey, []);
-
- // In the backend we must always add the 'title' module position
- if ($isAdmin)
- {
- $res[] = 'title';
- $res[] = 'toolbar';
- }
-
- return $res;
- }
-
+ /**
+ * Cache of the names of the currently active MFA Methods
+ *
+ * @var array|null
+ * @since 4.2.0
+ */
+ protected $activeMFAMethodNames = null;
+
+ /**
+ * Prevents Joomla from displaying any modules.
+ *
+ * This is implemented with a trick. If you use jdoc tags to load modules the JDocumentRendererHtmlModules
+ * uses JModuleHelper::getModules() to load the list of modules to render. This goes through JModuleHelper::load()
+ * which triggers the onAfterModuleList event after cleaning up the module list from duplicates. By resetting
+ * the list to an empty array we force Joomla to not display any modules.
+ *
+ * Similar code paths are followed by any canonical code which tries to load modules. So even if your template does
+ * not use jdoc tags this code will still work as expected.
+ *
+ * @param CMSApplication|null $app The CMS application to manipulate
+ *
+ * @return void
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function suppressAllModules(CMSApplication $app = null): void
+ {
+ if (is_null($app)) {
+ $app = Factory::getApplication();
+ }
+
+ $app->registerEvent('onAfterModuleList', [$this, 'onAfterModuleList']);
+ }
+
+ /**
+ * Get the MFA records for the user which correspond to active plugins
+ *
+ * @param User|null $user The user for which to fetch records. Skip to use the current user.
+ * @param bool $includeBackupCodes Should I include the backup codes record?
+ *
+ * @return array
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function getRecords(User $user = null, bool $includeBackupCodes = false): array
+ {
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ // Get the user's MFA records
+ $records = MfaHelper::getUserMfaRecords($user->id);
+
+ // No MFA Methods? Then we obviously don't need to display a Captive login page.
+ if (empty($records)) {
+ return [];
+ }
+
+ // Get the enabled MFA Methods' names
+ $methodNames = $this->getActiveMethodNames();
+
+ // Filter the records based on currently active MFA Methods
+ $ret = [];
+
+ $methodNames[] = 'backupcodes';
+ $methodNames = array_unique($methodNames);
+
+ if (!$includeBackupCodes) {
+ $methodNames = array_filter(
+ $methodNames,
+ function ($method) {
+ return $method != 'backupcodes';
+ }
+ );
+ }
+
+ foreach ($records as $record) {
+ // Backup codes must not be included in the list. We add them in the View, at the end of the list.
+ if (in_array($record->method, $methodNames)) {
+ $ret[$record->id] = $record;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Return all the active MFA Methods' names
+ *
+ * @return array
+ * @since 4.2.0
+ */
+ private function getActiveMethodNames(): ?array
+ {
+ if (!is_null($this->activeMFAMethodNames)) {
+ return $this->activeMFAMethodNames;
+ }
+
+ // Let's get a list of all currently active MFA Methods
+ $mfaMethods = MfaHelper::getMfaMethods();
+
+ // If no MFA Method is active we can't really display a Captive login page.
+ if (empty($mfaMethods)) {
+ $this->activeMFAMethodNames = [];
+
+ return $this->activeMFAMethodNames;
+ }
+
+ // Get a list of just the Method names
+ $this->activeMFAMethodNames = [];
+
+ foreach ($mfaMethods as $mfaMethod) {
+ $this->activeMFAMethodNames[] = $mfaMethod['name'];
+ }
+
+ return $this->activeMFAMethodNames;
+ }
+
+ /**
+ * Get the currently selected MFA record for the current user. If the record ID is empty, it does not correspond to
+ * the currently logged in user or does not correspond to an active plugin null is returned instead.
+ *
+ * @param User|null $user The user for which to fetch records. Skip to use the current user.
+ *
+ * @return MfaTable|null
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function getRecord(?User $user = null): ?MfaTable
+ {
+ $id = (int) $this->getState('record_id', null);
+
+ if ($id <= 0) {
+ return null;
+ }
+
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+ $loaded = $record->load(
+ [
+ 'user_id' => $user->id,
+ 'id' => $id,
+ ]
+ );
+
+ if (!$loaded) {
+ return null;
+ }
+
+ $methodNames = $this->getActiveMethodNames();
+
+ if (!in_array($record->method, $methodNames) && ($record->method != 'backupcodes')) {
+ return null;
+ }
+
+ return $record;
+ }
+
+ /**
+ * Load the Captive login page render options for a specific MFA record
+ *
+ * @param MfaTable $record The MFA record to process
+ *
+ * @return CaptiveRenderOptions The rendering options
+ * @since 4.2.0
+ */
+ public function loadCaptiveRenderOptions(?MfaTable $record): CaptiveRenderOptions
+ {
+ $renderOptions = new CaptiveRenderOptions();
+
+ if (empty($record)) {
+ return $renderOptions;
+ }
+
+ $event = new Captive($record);
+ $results = Factory::getApplication()
+ ->getDispatcher()
+ ->dispatch($event->getName(), $event)
+ ->getArgument('result', []);
+
+ if (empty($results)) {
+ if ($record->method === 'backupcodes') {
+ return $renderOptions->merge(
+ [
+ 'pre_message' => Text::_('COM_USERS_USER_BACKUPCODES_CAPTIVE_PROMPT'),
+ 'input_type' => 'number',
+ 'label' => Text::_('COM_USERS_USER_BACKUPCODE'),
+ ]
+ );
+ }
+
+ return $renderOptions;
+ }
+
+ foreach ($results as $result) {
+ if (empty($result)) {
+ continue;
+ }
+
+ return $renderOptions->merge($result);
+ }
+
+ return $renderOptions;
+ }
+
+ /**
+ * Returns the title to display in the Captive login page, or an empty string if no title is to be displayed.
+ *
+ * @return string
+ * @since 4.2.0
+ */
+ public function getPageTitle(): string
+ {
+ // In the frontend we can choose if we will display a title
+ $showTitle = (bool) ComponentHelper::getParams('com_users')
+ ->get('frontend_show_title', 1);
+
+ if (!$showTitle) {
+ return '';
+ }
+
+ return Text::_('COM_USERS_USER_MULTIFACTOR_AUTH');
+ }
+
+ /**
+ * Translate a MFA Method's name into its human-readable, display name
+ *
+ * @param string $name The internal MFA Method name
+ *
+ * @return string
+ * @since 4.2.0
+ */
+ public function translateMethodName(string $name): string
+ {
+ static $map = null;
+
+ if (!is_array($map)) {
+ $map = [];
+ $mfaMethods = MfaHelper::getMfaMethods();
+
+ if (!empty($mfaMethods)) {
+ foreach ($mfaMethods as $mfaMethod) {
+ $map[$mfaMethod['name']] = $mfaMethod['display'];
+ }
+ }
+ }
+
+ if ($name == 'backupcodes') {
+ return Text::_('COM_USERS_USER_BACKUPCODES');
+ }
+
+ return $map[$name] ?? $name;
+ }
+
+ /**
+ * Translate a MFA Method's name into the relative URL if its logo image
+ *
+ * @param string $name The internal MFA Method name
+ *
+ * @return string
+ * @since 4.2.0
+ */
+ public function getMethodImage(string $name): string
+ {
+ static $map = null;
+
+ if (!is_array($map)) {
+ $map = [];
+ $mfaMethods = MfaHelper::getMfaMethods();
+
+ if (!empty($mfaMethods)) {
+ foreach ($mfaMethods as $mfaMethod) {
+ $map[$mfaMethod['name']] = $mfaMethod['image'];
+ }
+ }
+ }
+
+ if ($name == 'backupcodes') {
+ return 'media/com_users/images/emergency.svg';
+ }
+
+ return $map[$name] ?? $name;
+ }
+
+ /**
+ * Process the modules list on Joomla! 4.
+ *
+ * Joomla! 4.x is passing an Event object. The first argument of the event object is the array of modules. After
+ * filtering it we have to overwrite the event argument (NOT just return the new list of modules). If a future
+ * version of Joomla! uses immutable events we'll have to use Reflection to do that or Joomla! would have to fix
+ * the way this event is handled, taking its return into account. For now, we just abuse the mutable event
+ * properties - a feature of the event objects we discussed in the Joomla! 4 Working Group back in August 2015.
+ *
+ * @param Event $event The Joomla! event object
+ *
+ * @return void
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function onAfterModuleList(Event $event): void
+ {
+ $modules = $event->getArgument(0);
+
+ if (empty($modules)) {
+ return;
+ }
+
+ $this->filterModules($modules);
+
+ $event->setArgument(0, $modules);
+ }
+
+ /**
+ * This is the Method which actually filters the sites modules based on the allowed module positions specified by
+ * the user.
+ *
+ * @param array $modules The list of the site's modules. Passed by reference.
+ *
+ * @return void The by-reference value is modified instead.
+ * @since 4.2.0
+ * @throws Exception
+ */
+ private function filterModules(array &$modules): void
+ {
+ $allowedPositions = $this->getAllowedModulePositions();
+
+ if (empty($allowedPositions)) {
+ $modules = [];
+
+ return;
+ }
+
+ $filtered = [];
+
+ foreach ($modules as $module) {
+ if (in_array($module->position, $allowedPositions)) {
+ $filtered[] = $module;
+ }
+ }
+
+ $modules = $filtered;
+ }
+
+ /**
+ * Get a list of module positions we are allowed to display
+ *
+ * @return array
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ private function getAllowedModulePositions(): array
+ {
+ $isAdmin = Factory::getApplication()->isClient('administrator');
+
+ // Load the list of allowed module positions from the component's settings. May be different for front- and back-end
+ $configKey = 'allowed_positions_' . ($isAdmin ? 'backend' : 'frontend');
+ $res = ComponentHelper::getParams('com_users')->get($configKey, []);
+
+ // In the backend we must always add the 'title' module position
+ if ($isAdmin) {
+ $res[] = 'title';
+ $res[] = 'toolbar';
+ }
+
+ return $res;
+ }
}
diff --git a/administrator/components/com_users/src/Model/DebuggroupModel.php b/administrator/components/com_users/src/Model/DebuggroupModel.php
index bebde5ee18ce7..f3f7df2300b1f 100644
--- a/administrator/components/com_users/src/Model/DebuggroupModel.php
+++ b/administrator/components/com_users/src/Model/DebuggroupModel.php
@@ -1,4 +1,5 @@
getState('filter.component');
-
- return DebugHelper::getDebugActions($component);
- }
-
- /**
- * Override getItems method.
- *
- * @return array
- *
- * @since 1.6
- */
- public function getItems()
- {
- $groupId = $this->getState('group_id');
-
- if (($assets = parent::getItems()) && $groupId)
- {
- $actions = $this->getDebugActions();
-
- foreach ($assets as &$asset)
- {
- $asset->checks = array();
-
- foreach ($actions as $action)
- {
- $name = $action[0];
- $asset->checks[$name] = Access::checkGroup($groupId, $name, $asset->name);
- }
- }
- }
-
- return $assets;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.lft', $direction = 'asc')
- {
- $app = Factory::getApplication();
-
- // Adjust the context to support modal layouts.
- $layout = $app->input->get('layout', 'default');
-
- if ($layout)
- {
- $this->context .= '.' . $layout;
- }
-
- // Load the filter state.
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
- $this->setState('group_id', $this->getUserStateFromRequest($this->context . '.group_id', 'group_id', 0, 'int', false));
-
- $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
- $this->setState('filter.level_start', $levelStart);
-
- $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
-
- if ($value > 0 && $value < $levelStart)
- {
- $value = $levelStart;
- }
-
- $this->setState('filter.level_end', $value);
-
- $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_users');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('group_id');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.level_start');
- $id .= ':' . $this->getState('filter.level_end');
- $id .= ':' . $this->getState('filter.component');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Get the group being debugged.
- *
- * @return CMSObject
- *
- * @since 1.6
- */
- public function getGroup()
- {
- $groupId = (int) $this->getState('group_id');
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['id', 'title']))
- ->from($db->quoteName('#__usergroups'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $groupId, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $group = $db->loadObject();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return $group;
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.id, a.name, a.title, a.level, a.lft, a.rgt'
- )
- );
- $query->from($db->quoteName('#__assets', 'a'));
-
- // Filter the items over the search string if set.
- if ($this->getState('filter.search'))
- {
- $search = '%' . trim($this->getState('filter.search')) . '%';
-
- // Add the clauses to the query.
- $query->where(
- '(' . $db->quoteName('a.name') . ' LIKE :name'
- . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
- )
- ->bind(':name', $search)
- ->bind(':title', $search);
- }
-
- // Filter on the start and end levels.
- $levelStart = (int) $this->getState('filter.level_start');
- $levelEnd = (int) $this->getState('filter.level_end');
-
- if ($levelEnd > 0 && $levelEnd < $levelStart)
- {
- $levelEnd = $levelStart;
- }
-
- if ($levelStart > 0)
- {
- $query->where($db->quoteName('a.level') . ' >= :levelStart')
- ->bind(':levelStart', $levelStart, ParameterType::INTEGER);
- }
-
- if ($levelEnd > 0)
- {
- $query->where($db->quoteName('a.level') . ' <= :levelEnd')
- ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
- }
-
- // Filter the items over the component if set.
- if ($this->getState('filter.component'))
- {
- $component = $this->getState('filter.component');
- $lcomponent = $component . '.%';
- $query->where(
- '(' . $db->quoteName('a.name') . ' = :component'
- . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
- )
- ->bind(':component', $component)
- ->bind(':lcomponent', $lcomponent);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'a.title',
+ 'component', 'a.name',
+ 'a.lft',
+ 'a.id',
+ 'level_start', 'level_end', 'a.level',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Get a list of the actions.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getDebugActions()
+ {
+ $component = $this->getState('filter.component');
+
+ return DebugHelper::getDebugActions($component);
+ }
+
+ /**
+ * Override getItems method.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ $groupId = $this->getState('group_id');
+
+ if (($assets = parent::getItems()) && $groupId) {
+ $actions = $this->getDebugActions();
+
+ foreach ($assets as &$asset) {
+ $asset->checks = array();
+
+ foreach ($actions as $action) {
+ $name = $action[0];
+ $asset->checks[$name] = Access::checkGroup($groupId, $name, $asset->name);
+ }
+ }
+ }
+
+ return $assets;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ // Adjust the context to support modal layouts.
+ $layout = $app->input->get('layout', 'default');
+
+ if ($layout) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+ $this->setState('group_id', $this->getUserStateFromRequest($this->context . '.group_id', 'group_id', 0, 'int', false));
+
+ $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
+ $this->setState('filter.level_start', $levelStart);
+
+ $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
+
+ if ($value > 0 && $value < $levelStart) {
+ $value = $levelStart;
+ }
+
+ $this->setState('filter.level_end', $value);
+
+ $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_users');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('group_id');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.level_start');
+ $id .= ':' . $this->getState('filter.level_end');
+ $id .= ':' . $this->getState('filter.component');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Get the group being debugged.
+ *
+ * @return CMSObject
+ *
+ * @since 1.6
+ */
+ public function getGroup()
+ {
+ $groupId = (int) $this->getState('group_id');
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['id', 'title']))
+ ->from($db->quoteName('#__usergroups'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $groupId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $group = $db->loadObject();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return $group;
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.name, a.title, a.level, a.lft, a.rgt'
+ )
+ );
+ $query->from($db->quoteName('#__assets', 'a'));
+
+ // Filter the items over the search string if set.
+ if ($this->getState('filter.search')) {
+ $search = '%' . trim($this->getState('filter.search')) . '%';
+
+ // Add the clauses to the query.
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' LIKE :name'
+ . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
+ )
+ ->bind(':name', $search)
+ ->bind(':title', $search);
+ }
+
+ // Filter on the start and end levels.
+ $levelStart = (int) $this->getState('filter.level_start');
+ $levelEnd = (int) $this->getState('filter.level_end');
+
+ if ($levelEnd > 0 && $levelEnd < $levelStart) {
+ $levelEnd = $levelStart;
+ }
+
+ if ($levelStart > 0) {
+ $query->where($db->quoteName('a.level') . ' >= :levelStart')
+ ->bind(':levelStart', $levelStart, ParameterType::INTEGER);
+ }
+
+ if ($levelEnd > 0) {
+ $query->where($db->quoteName('a.level') . ' <= :levelEnd')
+ ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
+ }
+
+ // Filter the items over the component if set.
+ if ($this->getState('filter.component')) {
+ $component = $this->getState('filter.component');
+ $lcomponent = $component . '.%';
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' = :component'
+ . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
+ )
+ ->bind(':component', $component)
+ ->bind(':lcomponent', $lcomponent);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_users/src/Model/DebuguserModel.php b/administrator/components/com_users/src/Model/DebuguserModel.php
index 9c9a23c04d399..97a0f2229c1d6 100644
--- a/administrator/components/com_users/src/Model/DebuguserModel.php
+++ b/administrator/components/com_users/src/Model/DebuguserModel.php
@@ -1,4 +1,5 @@
getState('filter.component');
-
- return DebugHelper::getDebugActions($component);
- }
-
- /**
- * Override getItems method.
- *
- * @return array
- *
- * @since 1.6
- */
- public function getItems()
- {
- $userId = $this->getState('user_id');
- $user = Factory::getUser($userId);
-
- if (($assets = parent::getItems()) && $userId)
- {
- $actions = $this->getDebugActions();
-
- foreach ($assets as &$asset)
- {
- $asset->checks = array();
-
- foreach ($actions as $action)
- {
- $name = $action[0];
- $asset->checks[$name] = $user->authorise($name, $asset->name);
- }
- }
- }
-
- return $assets;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function populateState($ordering = 'a.lft', $direction = 'asc')
- {
- $app = Factory::getApplication();
-
- // Adjust the context to support modal layouts.
- $layout = $app->input->get('layout', 'default');
-
- if ($layout)
- {
- $this->context .= '.' . $layout;
- }
-
- // Load the filter state.
- $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
- $this->setState('user_id', $this->getUserStateFromRequest($this->context . '.user_id', 'user_id', 0, 'int', false));
-
- $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
- $this->setState('filter.level_start', $levelStart);
-
- $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
-
- if ($value > 0 && $value < $levelStart)
- {
- $value = $levelStart;
- }
-
- $this->setState('filter.level_end', $value);
-
- $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_users');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('user_id');
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.level_start');
- $id .= ':' . $this->getState('filter.level_end');
- $id .= ':' . $this->getState('filter.component');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Get the user being debugged.
- *
- * @return User
- *
- * @since 1.6
- */
- public function getUser()
- {
- $userId = $this->getState('user_id');
-
- return Factory::getUser($userId);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.id, a.name, a.title, a.level, a.lft, a.rgt'
- )
- );
- $query->from($db->quoteName('#__assets', 'a'));
-
- // Filter the items over the search string if set.
- if ($this->getState('filter.search'))
- {
- $search = '%' . trim($this->getState('filter.search')) . '%';
-
- // Add the clauses to the query.
- $query->where(
- '(' . $db->quoteName('a.name') . ' LIKE :name'
- . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
- )
- ->bind(':name', $search)
- ->bind(':title', $search);
- }
-
- // Filter on the start and end levels.
- $levelStart = (int) $this->getState('filter.level_start');
- $levelEnd = (int) $this->getState('filter.level_end');
-
- if ($levelEnd > 0 && $levelEnd < $levelStart)
- {
- $levelEnd = $levelStart;
- }
-
- if ($levelStart > 0)
- {
- $query->where($db->quoteName('a.level') . ' >= :levelStart')
- ->bind(':levelStart', $levelStart, ParameterType::INTEGER);
- }
-
- if ($levelEnd > 0)
- {
- $query->where($db->quoteName('a.level') . ' <= :levelEnd')
- ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
- }
-
- // Filter the items over the component if set.
- if ($this->getState('filter.component'))
- {
- $component = $this->getState('filter.component');
- $lcomponent = $component . '.%';
- $query->where(
- '(' . $db->quoteName('a.name') . ' = :component'
- . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
- )
- ->bind(':component', $component)
- ->bind(':lcomponent', $lcomponent);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'a.title',
+ 'component', 'a.name',
+ 'a.lft',
+ 'a.id',
+ 'level_start', 'level_end', 'a.level',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Get a list of the actions.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getDebugActions()
+ {
+ $component = $this->getState('filter.component');
+
+ return DebugHelper::getDebugActions($component);
+ }
+
+ /**
+ * Override getItems method.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ $userId = $this->getState('user_id');
+ $user = Factory::getUser($userId);
+
+ if (($assets = parent::getItems()) && $userId) {
+ $actions = $this->getDebugActions();
+
+ foreach ($assets as &$asset) {
+ $asset->checks = array();
+
+ foreach ($actions as $action) {
+ $name = $action[0];
+ $asset->checks[$name] = $user->authorise($name, $asset->name);
+ }
+ }
+ }
+
+ return $assets;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ // Adjust the context to support modal layouts.
+ $layout = $app->input->get('layout', 'default');
+
+ if ($layout) {
+ $this->context .= '.' . $layout;
+ }
+
+ // Load the filter state.
+ $this->setState('filter.search', $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '', 'string'));
+ $this->setState('user_id', $this->getUserStateFromRequest($this->context . '.user_id', 'user_id', 0, 'int', false));
+
+ $levelStart = $this->getUserStateFromRequest($this->context . '.filter.level_start', 'filter_level_start', '', 'cmd');
+ $this->setState('filter.level_start', $levelStart);
+
+ $value = $this->getUserStateFromRequest($this->context . '.filter.level_end', 'filter_level_end', '', 'cmd');
+
+ if ($value > 0 && $value < $levelStart) {
+ $value = $levelStart;
+ }
+
+ $this->setState('filter.level_end', $value);
+
+ $this->setState('filter.component', $this->getUserStateFromRequest($this->context . '.filter.component', 'filter_component', '', 'string'));
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_users');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('user_id');
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.level_start');
+ $id .= ':' . $this->getState('filter.level_end');
+ $id .= ':' . $this->getState('filter.component');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Get the user being debugged.
+ *
+ * @return User
+ *
+ * @since 1.6
+ */
+ public function getUser()
+ {
+ $userId = $this->getState('user_id');
+
+ return Factory::getUser($userId);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.name, a.title, a.level, a.lft, a.rgt'
+ )
+ );
+ $query->from($db->quoteName('#__assets', 'a'));
+
+ // Filter the items over the search string if set.
+ if ($this->getState('filter.search')) {
+ $search = '%' . trim($this->getState('filter.search')) . '%';
+
+ // Add the clauses to the query.
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' LIKE :name'
+ . ' OR ' . $db->quoteName('a.title') . ' LIKE :title)'
+ )
+ ->bind(':name', $search)
+ ->bind(':title', $search);
+ }
+
+ // Filter on the start and end levels.
+ $levelStart = (int) $this->getState('filter.level_start');
+ $levelEnd = (int) $this->getState('filter.level_end');
+
+ if ($levelEnd > 0 && $levelEnd < $levelStart) {
+ $levelEnd = $levelStart;
+ }
+
+ if ($levelStart > 0) {
+ $query->where($db->quoteName('a.level') . ' >= :levelStart')
+ ->bind(':levelStart', $levelStart, ParameterType::INTEGER);
+ }
+
+ if ($levelEnd > 0) {
+ $query->where($db->quoteName('a.level') . ' <= :levelEnd')
+ ->bind(':levelEnd', $levelEnd, ParameterType::INTEGER);
+ }
+
+ // Filter the items over the component if set.
+ if ($this->getState('filter.component')) {
+ $component = $this->getState('filter.component');
+ $lcomponent = $component . '.%';
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' = :component'
+ . ' OR ' . $db->quoteName('a.name') . ' LIKE :lcomponent)'
+ )
+ ->bind(':component', $component)
+ ->bind(':lcomponent', $lcomponent);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_users/src/Model/GroupModel.php b/administrator/components/com_users/src/Model/GroupModel.php
index 5b770fd105a57..d8ec477a401c0 100644
--- a/administrator/components/com_users/src/Model/GroupModel.php
+++ b/administrator/components/com_users/src/Model/GroupModel.php
@@ -1,4 +1,5 @@
'onUserAfterDeleteGroup',
- 'event_after_save' => 'onUserAfterSaveGroup',
- 'event_before_delete' => 'onUserBeforeDeleteGroup',
- 'event_before_save' => 'onUserBeforeSaveGroup',
- 'events_map' => array('delete' => 'user', 'save' => 'user')
- ), $config
- );
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Returns a reference to the a Table object, always creating it.
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- *
- * @since 1.6
- */
- public function getTable($type = 'Usergroup', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- $return = Table::getInstance($type, $prefix, $config);
-
- return $return;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data An optional array of data for the form to interrogate.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_users.group', 'group', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_users.edit.group.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_users.group', $data);
-
- return $data;
- }
-
- /**
- * Override preprocessForm to load the user plugin group instead of content.
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error loading the form.
- */
- protected function preprocessForm(Form $form, $data, $group = '')
- {
- $obj = is_array($data) ? ArrayHelper::toObject($data, CMSObject::class) : $data;
-
- if (isset($obj->parent_id) && $obj->parent_id == 0 && $obj->id > 0)
- {
- $form->setFieldAttribute('parent_id', 'type', 'hidden');
- $form->setFieldAttribute('parent_id', 'hidden', 'true');
- }
-
- parent::preprocessForm($form, $data, 'user');
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- // Include the user plugins for events.
- PluginHelper::importPlugin($this->events_map['save']);
-
- /**
- * Check the super admin permissions for group
- * We get the parent group permissions and then check the group permissions manually
- * We have to calculate the group permissions manually because we haven't saved the group yet
- */
- $parentSuperAdmin = Access::checkGroup($data['parent_id'], 'core.admin');
-
- // Get core.admin rules from the root asset
- $rules = Access::getAssetRules('root.1')->getData('core.admin');
-
- // Get the value for the current group (will be true (allowed), false (denied), or null (inherit)
- $groupSuperAdmin = $rules['core.admin']->allow($data['id']);
-
- // We only need to change the $groupSuperAdmin if the parent is true or false. Otherwise, the value set in the rule takes effect.
- if ($parentSuperAdmin === false)
- {
- // If parent is false (Denied), effective value will always be false
- $groupSuperAdmin = false;
- }
- elseif ($parentSuperAdmin === true)
- {
- // If parent is true (allowed), group is true unless explicitly set to false
- $groupSuperAdmin = ($groupSuperAdmin === false) ? false : true;
- }
-
- // Check for non-super admin trying to save with super admin group
- $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
-
- if (!$iAmSuperAdmin && $groupSuperAdmin)
- {
- $this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
-
- return false;
- }
-
- /**
- * Check for super-admin changing self to be non-super-admin
- * First, are we a super admin
- */
- if ($iAmSuperAdmin)
- {
- // Next, are we a member of the current group?
- $myGroups = Access::getGroupsByUser(Factory::getUser()->get('id'), false);
-
- if (in_array($data['id'], $myGroups))
- {
- // Now, would we have super admin permissions without the current group?
- $otherGroups = array_diff($myGroups, array($data['id']));
- $otherSuperAdmin = false;
-
- foreach ($otherGroups as $otherGroup)
- {
- $otherSuperAdmin = $otherSuperAdmin ?: Access::checkGroup($otherGroup, 'core.admin');
- }
-
- /**
- * If we would not otherwise have super admin permissions
- * and the current group does not have super admin permissions, throw an exception
- */
- if ((!$otherSuperAdmin) && (!$groupSuperAdmin))
- {
- $this->setError(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'));
-
- return false;
- }
- }
- }
-
- if (Factory::getApplication()->input->get('task') == 'save2copy')
- {
- $data['title'] = $this->generateGroupTitle($data['parent_id'], $data['title']);
- }
-
- // Proceed with the save
- return parent::save($data);
- }
-
- /**
- * Method to delete rows.
- *
- * @param array &$pks An array of item ids.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function delete(&$pks)
- {
- // Typecast variable.
- $pks = (array) $pks;
- $user = Factory::getUser();
- $groups = Access::getGroupsByUser($user->get('id'));
-
- // Get a row instance.
- $table = $this->getTable();
-
- // Load plugins.
- PluginHelper::importPlugin($this->events_map['delete']);
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = $user->authorise('core.admin');
-
- foreach ($pks as $pk)
- {
- // Do not allow to delete groups to which the current user belongs
- if (in_array($pk, $groups))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_DELETE_ERROR_INVALID_GROUP'), 'error');
-
- return false;
- }
- elseif (!$table->load($pk))
- {
- // Item is not in the table.
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- // Iterate the items to delete each one.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- // Access checks.
- $allow = $user->authorise('core.edit.state', 'com_users');
-
- // Don't allow non-super-admin to delete a super admin
- $allow = (!$iAmSuperAdmin && Access::checkGroup($pk, 'core.admin')) ? false : $allow;
-
- if ($allow)
- {
- // Fire the before delete event.
- Factory::getApplication()->triggerEvent($this->event_before_delete, array($table->getProperties()));
-
- if (!$table->delete($pk))
- {
- $this->setError($table->getError());
-
- return false;
- }
- else
- {
- // Trigger the after delete event.
- Factory::getApplication()->triggerEvent($this->event_after_delete, array($table->getProperties(), true, $this->getError()));
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
- }
- }
- }
-
- return true;
- }
-
- /**
- * Method to generate the title of group on Save as Copy action
- *
- * @param integer $parentId The id of the parent.
- * @param string $title The title of group
- *
- * @return string Contains the modified title.
- *
- * @since 3.3.7
- */
- protected function generateGroupTitle($parentId, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('title' => $title, 'parent_id' => $parentId)))
- {
- if ($title == $table->title)
- {
- $title = StringHelper::increment($title);
- }
- }
-
- return $title;
- }
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ $config = array_merge(
+ array(
+ 'event_after_delete' => 'onUserAfterDeleteGroup',
+ 'event_after_save' => 'onUserAfterSaveGroup',
+ 'event_before_delete' => 'onUserBeforeDeleteGroup',
+ 'event_before_save' => 'onUserBeforeSaveGroup',
+ 'events_map' => array('delete' => 'user', 'save' => 'user')
+ ),
+ $config
+ );
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Returns a reference to the a Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'Usergroup', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ $return = Table::getInstance($type, $prefix, $config);
+
+ return $return;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_users.group', 'group', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_users.edit.group.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_users.group', $data);
+
+ return $data;
+ }
+
+ /**
+ * Override preprocessForm to load the user plugin group instead of content.
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error loading the form.
+ */
+ protected function preprocessForm(Form $form, $data, $group = '')
+ {
+ $obj = is_array($data) ? ArrayHelper::toObject($data, CMSObject::class) : $data;
+
+ if (isset($obj->parent_id) && $obj->parent_id == 0 && $obj->id > 0) {
+ $form->setFieldAttribute('parent_id', 'type', 'hidden');
+ $form->setFieldAttribute('parent_id', 'hidden', 'true');
+ }
+
+ parent::preprocessForm($form, $data, 'user');
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ // Include the user plugins for events.
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ /**
+ * Check the super admin permissions for group
+ * We get the parent group permissions and then check the group permissions manually
+ * We have to calculate the group permissions manually because we haven't saved the group yet
+ */
+ $parentSuperAdmin = Access::checkGroup($data['parent_id'], 'core.admin');
+
+ // Get core.admin rules from the root asset
+ $rules = Access::getAssetRules('root.1')->getData('core.admin');
+
+ // Get the value for the current group (will be true (allowed), false (denied), or null (inherit)
+ $groupSuperAdmin = $rules['core.admin']->allow($data['id']);
+
+ // We only need to change the $groupSuperAdmin if the parent is true or false. Otherwise, the value set in the rule takes effect.
+ if ($parentSuperAdmin === false) {
+ // If parent is false (Denied), effective value will always be false
+ $groupSuperAdmin = false;
+ } elseif ($parentSuperAdmin === true) {
+ // If parent is true (allowed), group is true unless explicitly set to false
+ $groupSuperAdmin = ($groupSuperAdmin === false) ? false : true;
+ }
+
+ // Check for non-super admin trying to save with super admin group
+ $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
+
+ if (!$iAmSuperAdmin && $groupSuperAdmin) {
+ $this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
+
+ return false;
+ }
+
+ /**
+ * Check for super-admin changing self to be non-super-admin
+ * First, are we a super admin
+ */
+ if ($iAmSuperAdmin) {
+ // Next, are we a member of the current group?
+ $myGroups = Access::getGroupsByUser(Factory::getUser()->get('id'), false);
+
+ if (in_array($data['id'], $myGroups)) {
+ // Now, would we have super admin permissions without the current group?
+ $otherGroups = array_diff($myGroups, array($data['id']));
+ $otherSuperAdmin = false;
+
+ foreach ($otherGroups as $otherGroup) {
+ $otherSuperAdmin = $otherSuperAdmin ?: Access::checkGroup($otherGroup, 'core.admin');
+ }
+
+ /**
+ * If we would not otherwise have super admin permissions
+ * and the current group does not have super admin permissions, throw an exception
+ */
+ if ((!$otherSuperAdmin) && (!$groupSuperAdmin)) {
+ $this->setError(Text::_('JLIB_USER_ERROR_CANNOT_DEMOTE_SELF'));
+
+ return false;
+ }
+ }
+ }
+
+ if (Factory::getApplication()->input->get('task') == 'save2copy') {
+ $data['title'] = $this->generateGroupTitle($data['parent_id'], $data['title']);
+ }
+
+ // Proceed with the save
+ return parent::save($data);
+ }
+
+ /**
+ * Method to delete rows.
+ *
+ * @param array &$pks An array of item ids.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function delete(&$pks)
+ {
+ // Typecast variable.
+ $pks = (array) $pks;
+ $user = Factory::getUser();
+ $groups = Access::getGroupsByUser($user->get('id'));
+
+ // Get a row instance.
+ $table = $this->getTable();
+
+ // Load plugins.
+ PluginHelper::importPlugin($this->events_map['delete']);
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = $user->authorise('core.admin');
+
+ foreach ($pks as $pk) {
+ // Do not allow to delete groups to which the current user belongs
+ if (in_array($pk, $groups)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_DELETE_ERROR_INVALID_GROUP'), 'error');
+
+ return false;
+ } elseif (!$table->load($pk)) {
+ // Item is not in the table.
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ // Access checks.
+ $allow = $user->authorise('core.edit.state', 'com_users');
+
+ // Don't allow non-super-admin to delete a super admin
+ $allow = (!$iAmSuperAdmin && Access::checkGroup($pk, 'core.admin')) ? false : $allow;
+
+ if ($allow) {
+ // Fire the before delete event.
+ Factory::getApplication()->triggerEvent($this->event_before_delete, array($table->getProperties()));
+
+ if (!$table->delete($pk)) {
+ $this->setError($table->getError());
+
+ return false;
+ } else {
+ // Trigger the after delete event.
+ Factory::getApplication()->triggerEvent($this->event_after_delete, array($table->getProperties(), true, $this->getError()));
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to generate the title of group on Save as Copy action
+ *
+ * @param integer $parentId The id of the parent.
+ * @param string $title The title of group
+ *
+ * @return string Contains the modified title.
+ *
+ * @since 3.3.7
+ */
+ protected function generateGroupTitle($parentId, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('title' => $title, 'parent_id' => $parentId))) {
+ if ($title == $table->title) {
+ $title = StringHelper::increment($title);
+ }
+ }
+
+ return $title;
+ }
}
diff --git a/administrator/components/com_users/src/Model/GroupsModel.php b/administrator/components/com_users/src/Model/GroupsModel.php
index c8f9083a80487..26326d14a3d3e 100644
--- a/administrator/components/com_users/src/Model/GroupsModel.php
+++ b/administrator/components/com_users/src/Model/GroupsModel.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Users\Administrator\Model;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Users\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Helper\UserGroupsHelper;
@@ -24,237 +24,219 @@
*/
class GroupsModel extends ListModel
{
- /**
- * Override parent constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- *
- * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
- * @since 3.2
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null)
- {
- if (empty($config['filter_fields']))
- {
- $config['filter_fields'] = array(
- 'id', 'a.id',
- 'parent_id', 'a.parent_id',
- 'title', 'a.title',
- 'lft', 'a.lft',
- 'rgt', 'a.rgt',
- );
- }
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- */
- protected function populateState($ordering = 'a.lft', $direction = 'asc')
- {
- // Load the parameters.
- $params = ComponentHelper::getParams('com_users');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Gets the list of groups and adds expensive joins to the result set.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 1.6
- */
- public function getItems()
- {
- // Get a storage key.
- $store = $this->getStoreId();
-
- // Try to load the data from internal storage.
- if (empty($this->cache[$store]))
- {
- $items = parent::getItems();
-
- // Bail out on an error or empty list.
- if (empty($items))
- {
- $this->cache[$store] = $items;
-
- return $items;
- }
-
- try
- {
- $items = $this->populateExtraData($items);
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Add the items to the internal cache.
- $this->cache[$store] = $items;
- }
-
- return $this->cache[$store];
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.*'
- )
- );
- $query->from($db->quoteName('#__usergroups') . ' AS a');
-
- // Filter the comments over the search string if set.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . trim($search) . '%';
- $query->where($db->quoteName('a.title') . ' LIKE :title');
- $query->bind(':title', $search);
- }
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Populate level & path for items.
- *
- * @param array $items Array of \stdClass objects
- *
- * @return array
- *
- * @since 3.6.3
- */
- private function populateExtraData(array $items)
- {
- // First pass: get list of the group ids and reset the counts.
- $groupsByKey = array();
-
- foreach ($items as $item)
- {
- $groupsByKey[(int) $item->id] = $item;
- }
-
- $groupIds = array_keys($groupsByKey);
-
- $db = $this->getDatabase();
-
- // Get total enabled users in group.
- $query = $db->getQuery(true);
-
- // Count the objects in the user group.
- $query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
- ->from($db->quoteName('#__user_usergroup_map', 'map'))
- ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
- ->whereIn($db->quoteName('map.group_id'), $groupIds)
- ->where($db->quoteName('u.block') . ' = 0')
- ->group($db->quoteName('map.group_id'));
- $db->setQuery($query);
-
- try
- {
- $countEnabled = $db->loadAssocList('group_id', 'count_enabled');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Get total disabled users in group.
- $query->clear();
- $query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
- ->from($db->quoteName('#__user_usergroup_map', 'map'))
- ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
- ->whereIn($db->quoteName('map.group_id'), $groupIds)
- ->where($db->quoteName('u.block') . ' = 1')
- ->group($db->quoteName('map.group_id'));
- $db->setQuery($query);
-
- try
- {
- $countDisabled = $db->loadAssocList('group_id', 'count_disabled');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Inject the values back into the array.
- foreach ($groupsByKey as &$item)
- {
- $item->count_enabled = isset($countEnabled[$item->id]) ? (int) $countEnabled[$item->id]['user_count'] : 0;
- $item->count_disabled = isset($countDisabled[$item->id]) ? (int) $countDisabled[$item->id]['user_count'] : 0;
- $item->user_count = $item->count_enabled + $item->count_disabled;
- }
-
- $groups = new UserGroupsHelper($groupsByKey);
-
- return array_values($groups->getAll());
- }
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'parent_id', 'a.parent_id',
+ 'title', 'a.title',
+ 'lft', 'a.lft',
+ 'rgt', 'a.rgt',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.lft', $direction = 'asc')
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_users');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Gets the list of groups and adds expensive joins to the result set.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId();
+
+ // Try to load the data from internal storage.
+ if (empty($this->cache[$store])) {
+ $items = parent::getItems();
+
+ // Bail out on an error or empty list.
+ if (empty($items)) {
+ $this->cache[$store] = $items;
+
+ return $items;
+ }
+
+ try {
+ $items = $this->populateExtraData($items);
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Add the items to the internal cache.
+ $this->cache[$store] = $items;
+ }
+
+ return $this->cache[$store];
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.*'
+ )
+ );
+ $query->from($db->quoteName('#__usergroups') . ' AS a');
+
+ // Filter the comments over the search string if set.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . trim($search) . '%';
+ $query->where($db->quoteName('a.title') . ' LIKE :title');
+ $query->bind(':title', $search);
+ }
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Populate level & path for items.
+ *
+ * @param array $items Array of \stdClass objects
+ *
+ * @return array
+ *
+ * @since 3.6.3
+ */
+ private function populateExtraData(array $items)
+ {
+ // First pass: get list of the group ids and reset the counts.
+ $groupsByKey = array();
+
+ foreach ($items as $item) {
+ $groupsByKey[(int) $item->id] = $item;
+ }
+
+ $groupIds = array_keys($groupsByKey);
+
+ $db = $this->getDatabase();
+
+ // Get total enabled users in group.
+ $query = $db->getQuery(true);
+
+ // Count the objects in the user group.
+ $query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
+ ->from($db->quoteName('#__user_usergroup_map', 'map'))
+ ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
+ ->whereIn($db->quoteName('map.group_id'), $groupIds)
+ ->where($db->quoteName('u.block') . ' = 0')
+ ->group($db->quoteName('map.group_id'));
+ $db->setQuery($query);
+
+ try {
+ $countEnabled = $db->loadAssocList('group_id', 'count_enabled');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Get total disabled users in group.
+ $query->clear();
+ $query->select('map.group_id, COUNT(DISTINCT map.user_id) AS user_count')
+ ->from($db->quoteName('#__user_usergroup_map', 'map'))
+ ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('map.user_id'))
+ ->whereIn($db->quoteName('map.group_id'), $groupIds)
+ ->where($db->quoteName('u.block') . ' = 1')
+ ->group($db->quoteName('map.group_id'));
+ $db->setQuery($query);
+
+ try {
+ $countDisabled = $db->loadAssocList('group_id', 'count_disabled');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Inject the values back into the array.
+ foreach ($groupsByKey as &$item) {
+ $item->count_enabled = isset($countEnabled[$item->id]) ? (int) $countEnabled[$item->id]['user_count'] : 0;
+ $item->count_disabled = isset($countDisabled[$item->id]) ? (int) $countDisabled[$item->id]['user_count'] : 0;
+ $item->user_count = $item->count_enabled + $item->count_disabled;
+ }
+
+ $groups = new UserGroupsHelper($groupsByKey);
+
+ return array_values($groups->getAll());
+ }
}
diff --git a/administrator/components/com_users/src/Model/LevelModel.php b/administrator/components/com_users/src/Model/LevelModel.php
index 3cd56420196db..8574a135312ad 100644
--- a/administrator/components/com_users/src/Model/LevelModel.php
+++ b/administrator/components/com_users/src/Model/LevelModel.php
@@ -1,4 +1,5 @@
rules);
-
- if ($groups === null)
- {
- throw new \RuntimeException('Invalid rules schema');
- }
-
- $isAdmin = Factory::getUser()->authorise('core.admin');
-
- // Check permissions
- foreach ($groups as $group)
- {
- if (!$isAdmin && Access::checkGroup($group, 'core.admin'))
- {
- $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
-
- return false;
- }
- }
-
- // Check if the access level is being used by any content.
- if ($this->levelsInUse === null)
- {
- // Populate the list once.
- $this->levelsInUse = array();
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select('DISTINCT access');
-
- // Get all the tables and the prefix
- $tables = $db->getTableList();
- $prefix = $db->getPrefix();
-
- foreach ($tables as $table)
- {
- // Get all of the columns in the table
- $fields = $db->getTableColumns($table);
-
- /**
- * We are looking for the access field. If custom tables are using something other
- * than the 'access' field they are on their own unfortunately.
- * Also make sure the table prefix matches the live db prefix (eg, it is not a "bak_" table)
- */
- if (strpos($table, $prefix) === 0 && isset($fields['access']))
- {
- // Lookup the distinct values of the field.
- $query->clear('from')
- ->from($db->quoteName($table));
- $db->setQuery($query);
-
- try
- {
- $values = $db->loadColumn();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $this->levelsInUse = array_merge($this->levelsInUse, $values);
-
- // @todo Could assemble an array of the tables used by each view level list those,
- // giving the user a clue in the error where to look.
- }
- }
-
- // Get uniques.
- $this->levelsInUse = array_unique($this->levelsInUse);
-
- // Ok, after all that we are ready to check the record :)
- }
-
- if (in_array($record->id, $this->levelsInUse))
- {
- $this->setError(Text::sprintf('COM_USERS_ERROR_VIEW_LEVEL_IN_USE', $record->id, $record->title));
-
- return false;
- }
-
- return parent::canDelete($record);
- }
-
- /**
- * Returns a reference to the a Table object, always creating it.
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- *
- * @since 1.6
- */
- public function getTable($type = 'ViewLevel', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- $return = Table::getInstance($type, $prefix, $config);
-
- return $return;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- $result = parent::getItem($pk);
-
- // Convert the params field to an array.
- $result->rules = json_decode($result->rules);
-
- return $result;
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data An optional array of data for the form to interrogate.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_users.level', 'level', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_users.edit.level.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_users.level', $data);
-
- return $data;
- }
-
- /**
- * Method to preprocess the form
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error loading the form.
- */
- protected function preprocessForm(Form $form, $data, $group = '')
- {
- // TO DO warning!
- parent::preprocessForm($form, $data, 'user');
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- */
- public function save($data)
- {
- if (!isset($data['rules']))
- {
- $data['rules'] = array();
- }
-
- $data['title'] = InputFilter::getInstance()->clean($data['title'], 'TRIM');
-
- return parent::save($data);
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return array|boolean Array of filtered data if valid, false otherwise.
- *
- * @see \Joomla\CMS\Form\FormRule
- * @see \JFilterInput
- * @since 3.8.8
- */
- public function validate($form, $data, $group = null)
- {
- $isSuperAdmin = Factory::getUser()->authorise('core.admin');
-
- // Non Super user should not be able to change the access levels of super user groups
- if (!$isSuperAdmin)
- {
- if (!isset($data['rules']) || !is_array($data['rules']))
- {
- $data['rules'] = array();
- }
-
- $groups = array_values(UserGroupsHelper::getInstance()->getAll());
-
- $rules = array();
-
- if (!empty($data['id']))
- {
- $table = $this->getTable();
-
- $table->load($data['id']);
-
- $rules = json_decode($table->rules);
- }
-
- $rules = ArrayHelper::toInteger($rules);
-
- for ($i = 0, $n = count($groups); $i < $n; ++$i)
- {
- if (Access::checkGroup((int) $groups[$i]->id, 'core.admin'))
- {
- if (in_array((int) $groups[$i]->id, $rules) && !in_array((int) $groups[$i]->id, $data['rules']))
- {
- $data['rules'][] = (int) $groups[$i]->id;
- }
- elseif (!in_array((int) $groups[$i]->id, $rules) && in_array((int) $groups[$i]->id, $data['rules']))
- {
- $this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
-
- return false;
- }
- }
- }
- }
-
- return parent::validate($form, $data, $group);
- }
+ /**
+ * @var array A list of the access levels in use.
+ * @since 1.6
+ */
+ protected $levelsInUse = null;
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission set in the component.
+ *
+ * @since 1.6
+ */
+ protected function canDelete($record)
+ {
+ $groups = json_decode($record->rules);
+
+ if ($groups === null) {
+ throw new \RuntimeException('Invalid rules schema');
+ }
+
+ $isAdmin = Factory::getUser()->authorise('core.admin');
+
+ // Check permissions
+ foreach ($groups as $group) {
+ if (!$isAdmin && Access::checkGroup($group, 'core.admin')) {
+ $this->setError(Text::_('JERROR_ALERTNOAUTHOR'));
+
+ return false;
+ }
+ }
+
+ // Check if the access level is being used by any content.
+ if ($this->levelsInUse === null) {
+ // Populate the list once.
+ $this->levelsInUse = array();
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT access');
+
+ // Get all the tables and the prefix
+ $tables = $db->getTableList();
+ $prefix = $db->getPrefix();
+
+ foreach ($tables as $table) {
+ // Get all of the columns in the table
+ $fields = $db->getTableColumns($table);
+
+ /**
+ * We are looking for the access field. If custom tables are using something other
+ * than the 'access' field they are on their own unfortunately.
+ * Also make sure the table prefix matches the live db prefix (eg, it is not a "bak_" table)
+ */
+ if (strpos($table, $prefix) === 0 && isset($fields['access'])) {
+ // Lookup the distinct values of the field.
+ $query->clear('from')
+ ->from($db->quoteName($table));
+ $db->setQuery($query);
+
+ try {
+ $values = $db->loadColumn();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $this->levelsInUse = array_merge($this->levelsInUse, $values);
+
+ // @todo Could assemble an array of the tables used by each view level list those,
+ // giving the user a clue in the error where to look.
+ }
+ }
+
+ // Get uniques.
+ $this->levelsInUse = array_unique($this->levelsInUse);
+
+ // Ok, after all that we are ready to check the record :)
+ }
+
+ if (in_array($record->id, $this->levelsInUse)) {
+ $this->setError(Text::sprintf('COM_USERS_ERROR_VIEW_LEVEL_IN_USE', $record->id, $record->title));
+
+ return false;
+ }
+
+ return parent::canDelete($record);
+ }
+
+ /**
+ * Returns a reference to the a Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'ViewLevel', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ $return = Table::getInstance($type, $prefix, $config);
+
+ return $return;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ $result = parent::getItem($pk);
+
+ // Convert the params field to an array.
+ $result->rules = json_decode($result->rules);
+
+ return $result;
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_users.level', 'level', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_users.edit.level.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_users.level', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to preprocess the form
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error loading the form.
+ */
+ protected function preprocessForm(Form $form, $data, $group = '')
+ {
+ // TO DO warning!
+ parent::preprocessForm($form, $data, 'user');
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ */
+ public function save($data)
+ {
+ if (!isset($data['rules'])) {
+ $data['rules'] = array();
+ }
+
+ $data['title'] = InputFilter::getInstance()->clean($data['title'], 'TRIM');
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return array|boolean Array of filtered data if valid, false otherwise.
+ *
+ * @see \Joomla\CMS\Form\FormRule
+ * @see \JFilterInput
+ * @since 3.8.8
+ */
+ public function validate($form, $data, $group = null)
+ {
+ $isSuperAdmin = Factory::getUser()->authorise('core.admin');
+
+ // Non Super user should not be able to change the access levels of super user groups
+ if (!$isSuperAdmin) {
+ if (!isset($data['rules']) || !is_array($data['rules'])) {
+ $data['rules'] = array();
+ }
+
+ $groups = array_values(UserGroupsHelper::getInstance()->getAll());
+
+ $rules = array();
+
+ if (!empty($data['id'])) {
+ $table = $this->getTable();
+
+ $table->load($data['id']);
+
+ $rules = json_decode($table->rules);
+ }
+
+ $rules = ArrayHelper::toInteger($rules);
+
+ for ($i = 0, $n = count($groups); $i < $n; ++$i) {
+ if (Access::checkGroup((int) $groups[$i]->id, 'core.admin')) {
+ if (in_array((int) $groups[$i]->id, $rules) && !in_array((int) $groups[$i]->id, $data['rules'])) {
+ $data['rules'][] = (int) $groups[$i]->id;
+ } elseif (!in_array((int) $groups[$i]->id, $rules) && in_array((int) $groups[$i]->id, $data['rules'])) {
+ $this->setError(Text::_('JLIB_USER_ERROR_NOT_SUPERADMIN'));
+
+ return false;
+ }
+ }
+ }
+ }
+
+ return parent::validate($form, $data, $group);
+ }
}
diff --git a/administrator/components/com_users/src/Model/LevelsModel.php b/administrator/components/com_users/src/Model/LevelsModel.php
index 7bd91ce32c56e..66c2a924a4a55 100644
--- a/administrator/components/com_users/src/Model/LevelsModel.php
+++ b/administrator/components/com_users/src/Model/LevelsModel.php
@@ -1,4 +1,5 @@
setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.*'
- )
- );
- $query->from($db->quoteName('#__viewlevels') . ' AS a');
-
- // Add the level in the tree.
- $query->group('a.id, a.title, a.ordering, a.rules');
-
- // Filter the items over the search string if set.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . trim($search) . '%';
- $query->where('a.title LIKE :title')
- ->bind(':title', $search);
- }
- }
-
- $query->group('a.id');
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Method to adjust the ordering of a row.
- *
- * @param integer $pk The ID of the primary key to move.
- * @param integer $direction Increment, usually +1 or -1
- *
- * @return boolean False on failure or error, true otherwise.
- */
- public function reorder($pk, $direction = 0)
- {
- // Sanitize the id and adjustment.
- $pk = (!empty($pk)) ? $pk : (int) $this->getState('level.id');
- $user = Factory::getUser();
-
- // Get an instance of the record's table.
- $table = Table::getInstance('ViewLevel', 'Joomla\\CMS\Table\\');
-
- // Load the row.
- if (!$table->load($pk))
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Access checks.
- $allow = $user->authorise('core.edit.state', 'com_users');
-
- if (!$allow)
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
-
- return false;
- }
-
- // Move the row.
- // @todo: Where clause to restrict category.
- $table->move($pk);
-
- return true;
- }
-
- /**
- * Saves the manually set order of records.
- *
- * @param array $pks An array of primary key ids.
- * @param integer $order Order position
- *
- * @return boolean Boolean true on success, boolean false
- *
- * @throws \Exception
- */
- public function saveorder($pks, $order)
- {
- $table = Table::getInstance('viewlevel', 'Joomla\\CMS\Table\\');
- $user = Factory::getUser();
- $conditions = array();
-
- if (empty($pks))
- {
- Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_ERROR_LEVELS_NOLEVELS_SELECTED'), 'error');
-
- return false;
- }
-
- // Update ordering values
- foreach ($pks as $i => $pk)
- {
- $table->load((int) $pk);
-
- // Access checks.
- $allow = $user->authorise('core.edit.state', 'com_users');
-
- if (!$allow)
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
- }
- elseif ($table->ordering != $order[$i])
- {
- $table->ordering = $order[$i];
-
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
- }
-
- // Execute reorder for each category.
- foreach ($conditions as $cond)
- {
- $table->load($cond[0]);
- $table->reorder($cond[1]);
- }
-
- return true;
- }
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'title', 'a.title',
+ 'ordering', 'a.ordering',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function populateState($ordering = 'a.ordering', $direction = 'asc')
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_users');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.*'
+ )
+ );
+ $query->from($db->quoteName('#__viewlevels') . ' AS a');
+
+ // Add the level in the tree.
+ $query->group('a.id, a.title, a.ordering, a.rules');
+
+ // Filter the items over the search string if set.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } else {
+ $search = '%' . trim($search) . '%';
+ $query->where('a.title LIKE :title')
+ ->bind(':title', $search);
+ }
+ }
+
+ $query->group('a.id');
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to adjust the ordering of a row.
+ *
+ * @param integer $pk The ID of the primary key to move.
+ * @param integer $direction Increment, usually +1 or -1
+ *
+ * @return boolean False on failure or error, true otherwise.
+ */
+ public function reorder($pk, $direction = 0)
+ {
+ // Sanitize the id and adjustment.
+ $pk = (!empty($pk)) ? $pk : (int) $this->getState('level.id');
+ $user = Factory::getUser();
+
+ // Get an instance of the record's table.
+ $table = Table::getInstance('ViewLevel', 'Joomla\\CMS\Table\\');
+
+ // Load the row.
+ if (!$table->load($pk)) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Access checks.
+ $allow = $user->authorise('core.edit.state', 'com_users');
+
+ if (!$allow) {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
+
+ return false;
+ }
+
+ // Move the row.
+ // @todo: Where clause to restrict category.
+ $table->move($pk);
+
+ return true;
+ }
+
+ /**
+ * Saves the manually set order of records.
+ *
+ * @param array $pks An array of primary key ids.
+ * @param integer $order Order position
+ *
+ * @return boolean Boolean true on success, boolean false
+ *
+ * @throws \Exception
+ */
+ public function saveorder($pks, $order)
+ {
+ $table = Table::getInstance('viewlevel', 'Joomla\\CMS\Table\\');
+ $user = Factory::getUser();
+ $conditions = array();
+
+ if (empty($pks)) {
+ Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_ERROR_LEVELS_NOLEVELS_SELECTED'), 'error');
+
+ return false;
+ }
+
+ // Update ordering values
+ foreach ($pks as $i => $pk) {
+ $table->load((int) $pk);
+
+ // Access checks.
+ $allow = $user->authorise('core.edit.state', 'com_users');
+
+ if (!$allow) {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
+ } elseif ($table->ordering != $order[$i]) {
+ $table->ordering = $order[$i];
+
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+ }
+
+ // Execute reorder for each category.
+ foreach ($conditions as $cond) {
+ $table->load($cond[0]);
+ $table->reorder($cond[1]);
+ }
+
+ return true;
+ }
}
diff --git a/administrator/components/com_users/src/Model/MailModel.php b/administrator/components/com_users/src/Model/MailModel.php
index 35b3c5dfc696e..eaa2df60b88ba 100644
--- a/administrator/components/com_users/src/Model/MailModel.php
+++ b/administrator/components/com_users/src/Model/MailModel.php
@@ -1,4 +1,5 @@
loadForm('com_users.mail', 'mail', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_users.display.mail.data', array());
-
- $this->preprocessData('com_users.mail', $data);
-
- return $data;
- }
-
- /**
- * Method to preprocess the form
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception if there is an error loading the form.
- */
- protected function preprocessForm(Form $form, $data, $group = 'user')
- {
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Send the email
- *
- * @return boolean
- *
- * @throws \Exception
- */
- public function send()
- {
- $app = Factory::getApplication();
- $data = $app->input->post->get('jform', array(), 'array');
- $user = Factory::getUser();
- $access = new Access;
- $db = $this->getDatabase();
- $language = Factory::getLanguage();
-
- $mode = array_key_exists('mode', $data) ? (int) $data['mode'] : 0;
- $subject = array_key_exists('subject', $data) ? $data['subject'] : '';
- $grp = array_key_exists('group', $data) ? (int) $data['group'] : 0;
- $recurse = array_key_exists('recurse', $data) ? (int) $data['recurse'] : 0;
- $bcc = array_key_exists('bcc', $data) ? (int) $data['bcc'] : 0;
- $disabled = array_key_exists('disabled', $data) ? (int) $data['disabled'] : 0;
- $message_body = array_key_exists('message', $data) ? $data['message'] : '';
-
- // Automatically removes html formatting
- if (!$mode)
- {
- $message_body = InputFilter::getInstance()->clean($message_body, 'string');
- }
-
- // Check for a message body and subject
- if (!$message_body || !$subject)
- {
- $app->setUserState('com_users.display.mail.data', $data);
- $this->setError(Text::_('COM_USERS_MAIL_PLEASE_FILL_IN_THE_FORM_CORRECTLY'));
-
- return false;
- }
-
- // Get users in the group out of the ACL, if group is provided.
- $to = $grp !== 0 ? $access->getUsersByGroup($grp, $recurse) : array();
-
- // When group is provided but no users are found in the group.
- if ($grp !== 0 && !$to)
- {
- $rows = array();
- }
- else
- {
- // Get all users email and group except for senders
- $uid = (int) $user->id;
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('email'),
- $db->quoteName('name'),
- ]
- )
- ->from($db->quoteName('#__users'))
- ->where($db->quoteName('id') . ' != :id')
- ->bind(':id', $uid, ParameterType::INTEGER);
-
- if ($grp !== 0)
- {
- $query->whereIn($db->quoteName('id'), $to);
- }
-
- if ($disabled === 0)
- {
- $query->where($db->quoteName('block') . ' = 0');
- }
-
- $db->setQuery($query);
- $rows = $db->loadObjectList();
- }
-
- // Check to see if there are any users in this group before we continue
- if (!$rows)
- {
- $app->setUserState('com_users.display.mail.data', $data);
-
- if (in_array($user->id, $to))
- {
- $this->setError(Text::_('COM_USERS_MAIL_ONLY_YOU_COULD_BE_FOUND_IN_THIS_GROUP'));
- }
- else
- {
- $this->setError(Text::_('COM_USERS_MAIL_NO_USERS_COULD_BE_FOUND_IN_THIS_GROUP'));
- }
-
- return false;
- }
-
- // Get the Mailer
- $mailer = new MailTemplate('com_users.massmail.mail', $language->getTag());
- $params = ComponentHelper::getParams('com_users');
-
- try
- {
- // Build email message format.
- $data = [
- 'subject' => stripslashes($subject),
- 'body' => $message_body,
- 'subjectprefix' => $params->get('mailSubjectPrefix', ''),
- 'bodysuffix' => $params->get('mailBodySuffix', '')
- ];
- $mailer->addTemplateData($data);
-
- $recipientType = $bcc ? 'bcc' : 'to';
-
- // Add recipients
- foreach ($rows as $row)
- {
- $mailer->addRecipient($row->email, $row->name, $recipientType);
- }
-
- if ($bcc)
- {
- $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname'));
- }
-
- // Send the Mail
- $rs = $mailer->send();
- }
- catch (MailDisabledException | phpMailerException $exception)
- {
- try
- {
- Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
-
- $rs = false;
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
-
- $rs = false;
- }
- }
-
- // Check for an error
- if ($rs !== true)
- {
- $app->setUserState('com_users.display.mail.data', $data);
- $this->setError($mailer->ErrorInfo);
-
- return false;
- }
- elseif (empty($rs))
- {
- $app->setUserState('com_users.display.mail.data', $data);
- $this->setError(Text::_('COM_USERS_MAIL_THE_MAIL_COULD_NOT_BE_SENT'));
-
- return false;
- }
- else
- {
- /**
- * Fill the data (specially for the 'mode', 'group' and 'bcc': they could not exist in the array
- * when the box is not checked and in this case, the default value would be used instead of the '0'
- * one)
- */
- $data['mode'] = $mode;
- $data['subject'] = $subject;
- $data['group'] = $grp;
- $data['recurse'] = $recurse;
- $data['bcc'] = $bcc;
- $data['message'] = $message_body;
- $app->setUserState('com_users.display.mail.data', array());
- $app->enqueueMessage(Text::plural('COM_USERS_MAIL_EMAIL_SENT_TO_N_USERS', count($rows)), 'message');
-
- return true;
- }
- }
+ /**
+ * Method to get the row form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_users.mail', 'mail', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_users.display.mail.data', array());
+
+ $this->preprocessData('com_users.mail', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to preprocess the form
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception if there is an error loading the form.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'user')
+ {
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Send the email
+ *
+ * @return boolean
+ *
+ * @throws \Exception
+ */
+ public function send()
+ {
+ $app = Factory::getApplication();
+ $data = $app->input->post->get('jform', array(), 'array');
+ $user = Factory::getUser();
+ $access = new Access();
+ $db = $this->getDatabase();
+ $language = Factory::getLanguage();
+
+ $mode = array_key_exists('mode', $data) ? (int) $data['mode'] : 0;
+ $subject = array_key_exists('subject', $data) ? $data['subject'] : '';
+ $grp = array_key_exists('group', $data) ? (int) $data['group'] : 0;
+ $recurse = array_key_exists('recurse', $data) ? (int) $data['recurse'] : 0;
+ $bcc = array_key_exists('bcc', $data) ? (int) $data['bcc'] : 0;
+ $disabled = array_key_exists('disabled', $data) ? (int) $data['disabled'] : 0;
+ $message_body = array_key_exists('message', $data) ? $data['message'] : '';
+
+ // Automatically removes html formatting
+ if (!$mode) {
+ $message_body = InputFilter::getInstance()->clean($message_body, 'string');
+ }
+
+ // Check for a message body and subject
+ if (!$message_body || !$subject) {
+ $app->setUserState('com_users.display.mail.data', $data);
+ $this->setError(Text::_('COM_USERS_MAIL_PLEASE_FILL_IN_THE_FORM_CORRECTLY'));
+
+ return false;
+ }
+
+ // Get users in the group out of the ACL, if group is provided.
+ $to = $grp !== 0 ? $access->getUsersByGroup($grp, $recurse) : array();
+
+ // When group is provided but no users are found in the group.
+ if ($grp !== 0 && !$to) {
+ $rows = array();
+ } else {
+ // Get all users email and group except for senders
+ $uid = (int) $user->id;
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('email'),
+ $db->quoteName('name'),
+ ]
+ )
+ ->from($db->quoteName('#__users'))
+ ->where($db->quoteName('id') . ' != :id')
+ ->bind(':id', $uid, ParameterType::INTEGER);
+
+ if ($grp !== 0) {
+ $query->whereIn($db->quoteName('id'), $to);
+ }
+
+ if ($disabled === 0) {
+ $query->where($db->quoteName('block') . ' = 0');
+ }
+
+ $db->setQuery($query);
+ $rows = $db->loadObjectList();
+ }
+
+ // Check to see if there are any users in this group before we continue
+ if (!$rows) {
+ $app->setUserState('com_users.display.mail.data', $data);
+
+ if (in_array($user->id, $to)) {
+ $this->setError(Text::_('COM_USERS_MAIL_ONLY_YOU_COULD_BE_FOUND_IN_THIS_GROUP'));
+ } else {
+ $this->setError(Text::_('COM_USERS_MAIL_NO_USERS_COULD_BE_FOUND_IN_THIS_GROUP'));
+ }
+
+ return false;
+ }
+
+ // Get the Mailer
+ $mailer = new MailTemplate('com_users.massmail.mail', $language->getTag());
+ $params = ComponentHelper::getParams('com_users');
+
+ try {
+ // Build email message format.
+ $data = [
+ 'subject' => stripslashes($subject),
+ 'body' => $message_body,
+ 'subjectprefix' => $params->get('mailSubjectPrefix', ''),
+ 'bodysuffix' => $params->get('mailBodySuffix', '')
+ ];
+ $mailer->addTemplateData($data);
+
+ $recipientType = $bcc ? 'bcc' : 'to';
+
+ // Add recipients
+ foreach ($rows as $row) {
+ $mailer->addRecipient($row->email, $row->name, $recipientType);
+ }
+
+ if ($bcc) {
+ $mailer->addRecipient($app->get('mailfrom'), $app->get('fromname'));
+ }
+
+ // Send the Mail
+ $rs = $mailer->send();
+ } catch (MailDisabledException | phpMailerException $exception) {
+ try {
+ Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
+
+ $rs = false;
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
+
+ $rs = false;
+ }
+ }
+
+ // Check for an error
+ if ($rs !== true) {
+ $app->setUserState('com_users.display.mail.data', $data);
+ $this->setError($mailer->ErrorInfo);
+
+ return false;
+ } elseif (empty($rs)) {
+ $app->setUserState('com_users.display.mail.data', $data);
+ $this->setError(Text::_('COM_USERS_MAIL_THE_MAIL_COULD_NOT_BE_SENT'));
+
+ return false;
+ } else {
+ /**
+ * Fill the data (specially for the 'mode', 'group' and 'bcc': they could not exist in the array
+ * when the box is not checked and in this case, the default value would be used instead of the '0'
+ * one)
+ */
+ $data['mode'] = $mode;
+ $data['subject'] = $subject;
+ $data['group'] = $grp;
+ $data['recurse'] = $recurse;
+ $data['bcc'] = $bcc;
+ $data['message'] = $message_body;
+ $app->setUserState('com_users.display.mail.data', array());
+ $app->enqueueMessage(Text::plural('COM_USERS_MAIL_EMAIL_SENT_TO_N_USERS', count($rows)), 'message');
+
+ return true;
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Model/MethodModel.php b/administrator/components/com_users/src/Model/MethodModel.php
index c909f6cf8b78f..32df3181299a8 100644
--- a/administrator/components/com_users/src/Model/MethodModel.php
+++ b/administrator/components/com_users/src/Model/MethodModel.php
@@ -1,4 +1,5 @@
methodExists($method))
- {
- return [
- 'name' => $method,
- 'display' => '',
- 'shortinfo' => '',
- 'image' => '',
- 'canDisable' => true,
- 'allowMultiple' => true,
- ];
- }
-
- return $this->mfaMethods[$method];
- }
-
- /**
- * Is the specified MFA Method available?
- *
- * @param string $method The Method to check.
- *
- * @return boolean
- * @since 4.2.0
- */
- public function methodExists(string $method): bool
- {
- if (!is_array($this->mfaMethods))
- {
- $this->populateMfaMethods();
- }
-
- return isset($this->mfaMethods[$method]);
- }
-
- /**
- * @param User|null $user The user record. Null to use the currently logged in user.
- *
- * @return array
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function getRenderOptions(?User $user = null): SetupRenderOptions
- {
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
- }
-
- $renderOptions = new SetupRenderOptions;
-
- $event = new GetSetup($this->getRecord($user));
- $results = Factory::getApplication()
- ->getDispatcher()
- ->dispatch($event->getName(), $event)
- ->getArgument('result', []);
-
- if (empty($results))
- {
- return $renderOptions;
- }
-
- foreach ($results as $result)
- {
- if (empty($result))
- {
- continue;
- }
-
- return $renderOptions->merge($result);
- }
-
- return $renderOptions;
- }
-
- /**
- * Get the specified MFA record. It will return a fake default record when no record ID is specified.
- *
- * @param User|null $user The user record. Null to use the currently logged in user.
- *
- * @return MfaTable
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function getRecord(User $user = null): MfaTable
- {
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- $defaultRecord = $this->getDefaultRecord($user);
- $id = (int) $this->getState('id', 0);
-
- if ($id <= 0)
- {
- return $defaultRecord;
- }
-
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
- $loaded = $record->load(
- [
- 'user_id' => $user->id,
- 'id' => $id,
- ]
- );
-
- if (!$loaded)
- {
- return $defaultRecord;
- }
-
- if (!$this->methodExists($record->method))
- {
- return $defaultRecord;
- }
-
- return $record;
- }
-
- /**
- * Return the title to use for the page
- *
- * @return string
- *
- * @since 4.2.0
- */
- public function getPageTitle(): string
- {
- $task = $this->getState('task', 'edit');
-
- switch ($task)
- {
- case 'mfa':
- $key = 'COM_USERS_USER_MULTIFACTOR_AUTH';
- break;
-
- default:
- $key = sprintf('COM_USERS_MFA_%s_PAGE_HEAD', $task);
- break;
- }
-
- return Text::_($key);
- }
-
- /**
- * @param User|null $user The user record. Null to use the current user.
- *
- * @return MfaTable
- * @throws Exception
- *
- * @since 4.2.0
- */
- protected function getDefaultRecord(?User $user = null): MfaTable
- {
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- $method = $this->getState('method');
- $title = '';
-
- if (is_null($this->mfaMethods))
- {
- $this->populateMfaMethods();
- }
-
- if ($method && isset($this->mfaMethods[$method]))
- {
- $title = $this->mfaMethods[$method]['display'];
- }
-
- /** @var MfaTable $record */
- $record = $this->getTable('Mfa', 'Administrator');
-
- $record->bind(
- [
- 'id' => null,
- 'user_id' => $user->id,
- 'title' => $title,
- 'method' => $method,
- 'default' => 0,
- 'options' => [],
- ]
- );
-
- return $record;
- }
-
- /**
- * Populate the list of MFA Methods
- *
- * @return void
- * @since 4.2.0
- */
- private function populateMfaMethods(): void
- {
- $this->mfaMethods = [];
- $mfaMethods = MfaHelper::getMfaMethods();
-
- if (empty($mfaMethods))
- {
- return;
- }
-
- foreach ($mfaMethods as $method)
- {
- $this->mfaMethods[$method['name']] = $method;
- }
-
- // We also need to add the backup codes Method
- $this->mfaMethods['backupcodes'] = [
- 'name' => 'backupcodes',
- 'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
- 'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
- 'image' => 'media/com_users/images/emergency.svg',
- 'canDisable' => false,
- 'allowMultiple' => false,
- ];
- }
+ /**
+ * List of MFA Methods
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ protected $mfaMethods = null;
+
+ /**
+ * Get the specified MFA Method's record
+ *
+ * @param string $method The Method to retrieve.
+ *
+ * @return array
+ * @since 4.2.0
+ */
+ public function getMethod(string $method): array
+ {
+ if (!$this->methodExists($method)) {
+ return [
+ 'name' => $method,
+ 'display' => '',
+ 'shortinfo' => '',
+ 'image' => '',
+ 'canDisable' => true,
+ 'allowMultiple' => true,
+ ];
+ }
+
+ return $this->mfaMethods[$method];
+ }
+
+ /**
+ * Is the specified MFA Method available?
+ *
+ * @param string $method The Method to check.
+ *
+ * @return boolean
+ * @since 4.2.0
+ */
+ public function methodExists(string $method): bool
+ {
+ if (!is_array($this->mfaMethods)) {
+ $this->populateMfaMethods();
+ }
+
+ return isset($this->mfaMethods[$method]);
+ }
+
+ /**
+ * @param User|null $user The user record. Null to use the currently logged in user.
+ *
+ * @return array
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function getRenderOptions(?User $user = null): SetupRenderOptions
+ {
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
+ }
+
+ $renderOptions = new SetupRenderOptions();
+
+ $event = new GetSetup($this->getRecord($user));
+ $results = Factory::getApplication()
+ ->getDispatcher()
+ ->dispatch($event->getName(), $event)
+ ->getArgument('result', []);
+
+ if (empty($results)) {
+ return $renderOptions;
+ }
+
+ foreach ($results as $result) {
+ if (empty($result)) {
+ continue;
+ }
+
+ return $renderOptions->merge($result);
+ }
+
+ return $renderOptions;
+ }
+
+ /**
+ * Get the specified MFA record. It will return a fake default record when no record ID is specified.
+ *
+ * @param User|null $user The user record. Null to use the currently logged in user.
+ *
+ * @return MfaTable
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function getRecord(User $user = null): MfaTable
+ {
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ $defaultRecord = $this->getDefaultRecord($user);
+ $id = (int) $this->getState('id', 0);
+
+ if ($id <= 0) {
+ return $defaultRecord;
+ }
+
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+ $loaded = $record->load(
+ [
+ 'user_id' => $user->id,
+ 'id' => $id,
+ ]
+ );
+
+ if (!$loaded) {
+ return $defaultRecord;
+ }
+
+ if (!$this->methodExists($record->method)) {
+ return $defaultRecord;
+ }
+
+ return $record;
+ }
+
+ /**
+ * Return the title to use for the page
+ *
+ * @return string
+ *
+ * @since 4.2.0
+ */
+ public function getPageTitle(): string
+ {
+ $task = $this->getState('task', 'edit');
+
+ switch ($task) {
+ case 'mfa':
+ $key = 'COM_USERS_USER_MULTIFACTOR_AUTH';
+ break;
+
+ default:
+ $key = sprintf('COM_USERS_MFA_%s_PAGE_HEAD', $task);
+ break;
+ }
+
+ return Text::_($key);
+ }
+
+ /**
+ * @param User|null $user The user record. Null to use the current user.
+ *
+ * @return MfaTable
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ protected function getDefaultRecord(?User $user = null): MfaTable
+ {
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ $method = $this->getState('method');
+ $title = '';
+
+ if (is_null($this->mfaMethods)) {
+ $this->populateMfaMethods();
+ }
+
+ if ($method && isset($this->mfaMethods[$method])) {
+ $title = $this->mfaMethods[$method]['display'];
+ }
+
+ /** @var MfaTable $record */
+ $record = $this->getTable('Mfa', 'Administrator');
+
+ $record->bind(
+ [
+ 'id' => null,
+ 'user_id' => $user->id,
+ 'title' => $title,
+ 'method' => $method,
+ 'default' => 0,
+ 'options' => [],
+ ]
+ );
+
+ return $record;
+ }
+
+ /**
+ * Populate the list of MFA Methods
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function populateMfaMethods(): void
+ {
+ $this->mfaMethods = [];
+ $mfaMethods = MfaHelper::getMfaMethods();
+
+ if (empty($mfaMethods)) {
+ return;
+ }
+
+ foreach ($mfaMethods as $method) {
+ $this->mfaMethods[$method['name']] = $method;
+ }
+
+ // We also need to add the backup codes Method
+ $this->mfaMethods['backupcodes'] = [
+ 'name' => 'backupcodes',
+ 'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
+ 'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
+ 'image' => 'media/com_users/images/emergency.svg',
+ 'canDisable' => false,
+ 'allowMultiple' => false,
+ ];
+ }
}
diff --git a/administrator/components/com_users/src/Model/MethodsModel.php b/administrator/components/com_users/src/Model/MethodsModel.php
index 3b34a3470f56b..e010a703edef9 100644
--- a/administrator/components/com_users/src/Model/MethodsModel.php
+++ b/administrator/components/com_users/src/Model/MethodsModel.php
@@ -1,4 +1,5 @@
getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- if ($user->guest)
- {
- return [];
- }
-
- // Get an associative array of MFA Methods
- $rawMethods = MfaHelper::getMfaMethods();
- $methods = [];
-
- foreach ($rawMethods as $method)
- {
- $method['active'] = [];
- $methods[$method['name']] = $method;
- }
-
- // Put the user MFA records into the Methods array
- $userMfaRecords = MfaHelper::getUserMfaRecords($user->id);
-
- if (!empty($userMfaRecords))
- {
- foreach ($userMfaRecords as $record)
- {
- if (!isset($methods[$record->method]))
- {
- continue;
- }
-
- $methods[$record->method]->addActiveMethod($record);
- }
- }
-
- return $methods;
- }
-
- /**
- * Delete all Multi-factor Authentication Methods for the given user.
- *
- * @param User|null $user The user object to reset MFA for. Null to use the current user.
- *
- * @return void
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function deleteAll(?User $user = null): void
- {
- // Make sure we have a user object
- if (is_null($user))
- {
- $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
- }
-
- // If the user object is a guest (who can't have MFA) we abort with an error
- if ($user->guest)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__user_mfa'))
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->bind(':user_id', $user->id, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
- }
-
- /**
- * Format a relative timestamp. It deals with timestamps today and yesterday in a special manner. Example returns:
- * Yesterday, 13:12
- * Today, 08:33
- * January 1, 2015
- *
- * @param string $dateTimeText The database time string to use, e.g. "2017-01-13 13:25:36"
- *
- * @return string The formatted, human-readable date
- * @throws Exception
- *
- * @since 4.2.0
- */
- public function formatRelative(?string $dateTimeText): string
- {
- if (empty($dateTimeText))
- {
- return Text::_('JNEVER');
- }
-
- // The timestamp is given in UTC. Make sure Joomla! parses it as such.
- $utcTimeZone = new DateTimeZone('UTC');
- $jDate = new Date($dateTimeText, $utcTimeZone);
- $unixStamp = $jDate->toUnix();
-
- // I'm pretty sure we didn't have MFA in Joomla back in 1970 ;)
- if ($unixStamp < 0)
- {
- return Text::_('JNEVER');
- }
-
- // I need to display the date in the user's local timezone. That's how you do it.
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- $userTZ = $user->getParam('timezone', 'UTC');
- $tz = new DateTimeZone($userTZ);
- $jDate->setTimezone($tz);
-
- // Default format string: way in the past, the time of the day is not important
- $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_PAST');
- $containerString = Text::_('COM_USERS_MFA_LBL_PAST');
-
- // If the timestamp is within the last 72 hours we may need a special format
- if ($unixStamp > (time() - (72 * 3600)))
- {
- // Is this timestamp today?
- $jNow = new Date;
- $jNow->setTimezone($tz);
- $checkNow = $jNow->format('Ymd', true);
- $checkDate = $jDate->format('Ymd', true);
-
- if ($checkDate == $checkNow)
- {
- $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_TODAY');
- $containerString = Text::_('COM_USERS_MFA_LBL_TODAY');
- }
- else
- {
- // Is this timestamp yesterday?
- $jYesterday = clone $jNow;
- $jYesterday->setTime(0, 0, 0);
- $oneSecond = new DateInterval('PT1S');
- $jYesterday->sub($oneSecond);
- $checkYesterday = $jYesterday->format('Ymd', true);
-
- if ($checkDate == $checkYesterday)
- {
- $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_YESTERDAY');
- $containerString = Text::_('COM_USERS_MFA_LBL_YESTERDAY');
- }
- }
- }
-
- return sprintf($containerString, $jDate->format($formatString, true));
- }
-
- /**
- * Set the user's "don't show this again" flag.
- *
- * @param User $user The user to check
- * @param bool $flag True to set the flag, false to unset it (it will be set to 0, actually)
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function setFlag(User $user, bool $flag = true): void
- {
- $db = $this->getDatabase();
- $profileKey = 'mfa.dontshow';
- $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;
- }
-
- $exists = !is_null($result);
-
- $object = (object) [
- 'user_id' => $user->id,
- 'profile_key' => 'mfa.dontshow',
- 'profile_value' => ($flag ? 1 : 0),
- 'ordering' => 1,
- ];
-
- if (!$exists)
- {
- $db->insertObject('#__user_profiles', $object);
- }
- else
- {
- $db->updateObject('#__user_profiles', $object, ['user_id', 'profile_key']);
- }
- }
+ /**
+ * Returns a list of all available MFA methods and their currently active records for a given user.
+ *
+ * @param User|null $user The user object. Skip to use the current user.
+ *
+ * @return array
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function getMethods(?User $user = null): array
+ {
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ if ($user->guest) {
+ return [];
+ }
+
+ // Get an associative array of MFA Methods
+ $rawMethods = MfaHelper::getMfaMethods();
+ $methods = [];
+
+ foreach ($rawMethods as $method) {
+ $method['active'] = [];
+ $methods[$method['name']] = $method;
+ }
+
+ // Put the user MFA records into the Methods array
+ $userMfaRecords = MfaHelper::getUserMfaRecords($user->id);
+
+ if (!empty($userMfaRecords)) {
+ foreach ($userMfaRecords as $record) {
+ if (!isset($methods[$record->method])) {
+ continue;
+ }
+
+ $methods[$record->method]->addActiveMethod($record);
+ }
+ }
+
+ return $methods;
+ }
+
+ /**
+ * Delete all Multi-factor Authentication Methods for the given user.
+ *
+ * @param User|null $user The user object to reset MFA for. Null to use the current user.
+ *
+ * @return void
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function deleteAll(?User $user = null): void
+ {
+ // Make sure we have a user object
+ if (is_null($user)) {
+ $user = Factory::getApplication()->getIdentity() ?: Factory::getUser();
+ }
+
+ // If the user object is a guest (who can't have MFA) we abort with an error
+ if ($user->guest) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__user_mfa'))
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->bind(':user_id', $user->id, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+ }
+
+ /**
+ * Format a relative timestamp. It deals with timestamps today and yesterday in a special manner. Example returns:
+ * Yesterday, 13:12
+ * Today, 08:33
+ * January 1, 2015
+ *
+ * @param string $dateTimeText The database time string to use, e.g. "2017-01-13 13:25:36"
+ *
+ * @return string The formatted, human-readable date
+ * @throws Exception
+ *
+ * @since 4.2.0
+ */
+ public function formatRelative(?string $dateTimeText): string
+ {
+ if (empty($dateTimeText)) {
+ return Text::_('JNEVER');
+ }
+
+ // The timestamp is given in UTC. Make sure Joomla! parses it as such.
+ $utcTimeZone = new DateTimeZone('UTC');
+ $jDate = new Date($dateTimeText, $utcTimeZone);
+ $unixStamp = $jDate->toUnix();
+
+ // I'm pretty sure we didn't have MFA in Joomla back in 1970 ;)
+ if ($unixStamp < 0) {
+ return Text::_('JNEVER');
+ }
+
+ // I need to display the date in the user's local timezone. That's how you do it.
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ $userTZ = $user->getParam('timezone', 'UTC');
+ $tz = new DateTimeZone($userTZ);
+ $jDate->setTimezone($tz);
+
+ // Default format string: way in the past, the time of the day is not important
+ $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_PAST');
+ $containerString = Text::_('COM_USERS_MFA_LBL_PAST');
+
+ // If the timestamp is within the last 72 hours we may need a special format
+ if ($unixStamp > (time() - (72 * 3600))) {
+ // Is this timestamp today?
+ $jNow = new Date();
+ $jNow->setTimezone($tz);
+ $checkNow = $jNow->format('Ymd', true);
+ $checkDate = $jDate->format('Ymd', true);
+
+ if ($checkDate == $checkNow) {
+ $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_TODAY');
+ $containerString = Text::_('COM_USERS_MFA_LBL_TODAY');
+ } else {
+ // Is this timestamp yesterday?
+ $jYesterday = clone $jNow;
+ $jYesterday->setTime(0, 0, 0);
+ $oneSecond = new DateInterval('PT1S');
+ $jYesterday->sub($oneSecond);
+ $checkYesterday = $jYesterday->format('Ymd', true);
+
+ if ($checkDate == $checkYesterday) {
+ $formatString = Text::_('COM_USERS_MFA_LBL_DATE_FORMAT_YESTERDAY');
+ $containerString = Text::_('COM_USERS_MFA_LBL_YESTERDAY');
+ }
+ }
+ }
+
+ return sprintf($containerString, $jDate->format($formatString, true));
+ }
+
+ /**
+ * Set the user's "don't show this again" flag.
+ *
+ * @param User $user The user to check
+ * @param bool $flag True to set the flag, false to unset it (it will be set to 0, actually)
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function setFlag(User $user, bool $flag = true): void
+ {
+ $db = $this->getDatabase();
+ $profileKey = 'mfa.dontshow';
+ $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;
+ }
+
+ $exists = !is_null($result);
+
+ $object = (object) [
+ 'user_id' => $user->id,
+ 'profile_key' => 'mfa.dontshow',
+ 'profile_value' => ($flag ? 1 : 0),
+ 'ordering' => 1,
+ ];
+
+ if (!$exists) {
+ $db->insertObject('#__user_profiles', $object);
+ } else {
+ $db->updateObject('#__user_profiles', $object, ['user_id', 'profile_key']);
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Model/NoteModel.php b/administrator/components/com_users/src/Model/NoteModel.php
index 974f7a62d16f5..f46e2f1273a04 100644
--- a/administrator/components/com_users/src/Model/NoteModel.php
+++ b/administrator/components/com_users/src/Model/NoteModel.php
@@ -1,4 +1,5 @@
loadForm('com_users.note', 'note', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 2.5
- * @throws \Exception
- */
- public function getItem($pk = null)
- {
- $result = parent::getItem($pk);
-
- // Get the dispatcher and load the content plugins.
- PluginHelper::importPlugin('content');
-
- // Load the user plugins for backward compatibility (v3.3.3 and earlier).
- PluginHelper::importPlugin('user');
-
- // Trigger the data preparation event.
- Factory::getApplication()->triggerEvent('onContentPrepareData', array('com_users.note', $result));
-
- return $result;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function loadFormData()
- {
- // Get the application
- $app = Factory::getApplication();
-
- // Check the session for previously entered form data.
- $data = $app->getUserState('com_users.edit.note.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
-
- // Prime some default values.
- if ($this->getState('note.id') == 0)
- {
- $data->set('catid', $app->input->get('catid', $app->getUserState('com_users.notes.filter.category_id'), 'int'));
- }
-
- $userId = $app->input->get('u_id', 0, 'int');
-
- if ($userId != 0)
- {
- $data->user_id = $userId;
- }
- }
-
- $this->preprocessData('com_users.note', $data);
-
- return $data;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @return void
- *
- * @since 2.5
- * @throws \Exception
- */
- protected function populateState()
- {
- parent::populateState();
-
- $userId = Factory::getApplication()->input->get('u_id', 0, 'int');
- $this->setState('note.user_id', $userId);
- }
+ use VersionableModelTrait;
+
+ /**
+ * The type alias for this content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $typeAlias = 'com_users.note';
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|bool A Form object on success, false on failure
+ *
+ * @since 2.5
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_users.note', 'note', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 2.5
+ * @throws \Exception
+ */
+ public function getItem($pk = null)
+ {
+ $result = parent::getItem($pk);
+
+ // Get the dispatcher and load the content plugins.
+ PluginHelper::importPlugin('content');
+
+ // Load the user plugins for backward compatibility (v3.3.3 and earlier).
+ PluginHelper::importPlugin('user');
+
+ // Trigger the data preparation event.
+ Factory::getApplication()->triggerEvent('onContentPrepareData', array('com_users.note', $result));
+
+ return $result;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ // Get the application
+ $app = Factory::getApplication();
+
+ // Check the session for previously entered form data.
+ $data = $app->getUserState('com_users.edit.note.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+
+ // Prime some default values.
+ if ($this->getState('note.id') == 0) {
+ $data->set('catid', $app->input->get('catid', $app->getUserState('com_users.notes.filter.category_id'), 'int'));
+ }
+
+ $userId = $app->input->get('u_id', 0, 'int');
+
+ if ($userId != 0) {
+ $data->user_id = $userId;
+ }
+ }
+
+ $this->preprocessData('com_users.note', $data);
+
+ return $data;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 2.5
+ * @throws \Exception
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $userId = Factory::getApplication()->input->get('u_id', 0, 'int');
+ $this->setState('note.user_id', $userId);
+ }
}
diff --git a/administrator/components/com_users/src/Model/NotesModel.php b/administrator/components/com_users/src/Model/NotesModel.php
index c6778588cfd21..c2d6f736e7242 100644
--- a/administrator/components/com_users/src/Model/NotesModel.php
+++ b/administrator/components/com_users/src/Model/NotesModel.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState('list.select',
- 'a.id, a.subject, a.checked_out, a.checked_out_time,' .
- 'a.catid, a.created_time, a.review_time,' .
- 'a.state, a.publish_up, a.publish_down'
- )
- );
- $query->from('#__user_notes AS a');
-
- // Join over the category
- $query->select('c.title AS category_title, c.params AS category_params')
- ->join('LEFT', '#__categories AS c ON c.id = a.catid');
-
- // Join over the users for the note user.
- $query->select('u.name AS user_name')
- ->join('LEFT', '#__users AS u ON u.id = a.user_id');
-
- // Join over the users for the checked out user.
- $query->select('uc.name AS editor')
- ->join('LEFT', '#__users AS uc ON uc.id = a.checked_out');
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $search3 = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $search3, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'uid:') === 0)
- {
- $search4 = (int) substr($search, 4);
- $query->where($db->quoteName('a.user_id') . ' = :id');
- $query->bind(':id', $search4, ParameterType::INTEGER);
- }
- else
- {
- $search = '%' . trim($search) . '%';
- $query->where(
- '(' . $db->quoteName('a.subject') . ' LIKE :subject'
- . ' OR ' . $db->quoteName('u.name') . ' LIKE :name'
- . ' OR ' . $db->quoteName('u.username') . ' LIKE :username)'
- );
- $query->bind(':subject', $search);
- $query->bind(':name', $search);
- $query->bind(':username', $search);
- }
- }
-
- // Filter by published state
- $published = $this->getState('filter.published');
-
- if (is_numeric($published))
- {
- $query->where($db->quoteName('a.state') . ' = :state')
- ->bind(':state', $published, ParameterType::INTEGER);
- }
- elseif ($published !== '*')
- {
- $query->whereIn($db->quoteName('a.state'), [0, 1]);
- }
-
- // Filter by a single category.
- $categoryId = (int) $this->getState('filter.category_id');
-
- if ($categoryId)
- {
- $query->where($db->quoteName('a.catid') . ' = :catid')
- ->bind(':catid', $categoryId, ParameterType::INTEGER);
- }
-
- // Filter by a single user.
- $userId = (int) $this->getState('filter.user_id');
-
- if ($userId)
- {
- // Add the body and where filter.
- $query->select('a.body')
- ->where($db->quoteName('a.user_id') . ' = :user_id')
- ->bind(':user_id', $userId, ParameterType::INTEGER);
- }
-
- // Filter on the level.
- if ($level = $this->getState('filter.level'))
- {
- $level = (int) $level;
- $query->where($db->quoteName('c.level') . ' <= :level')
- ->bind(':level', $level, ParameterType::INTEGER);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 'a.review_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC')));
-
- return $query;
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 2.5
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.published');
- $id .= ':' . $this->getState('filter.category_id');
- $id .= ':' . $this->getState('filter.user_id');
- $id .= ':' . $this->getState('filter.level');
-
- return parent::getStoreId($id);
- }
-
- /**
- * Gets a user object if the user filter is set.
- *
- * @return User The User object
- *
- * @since 2.5
- */
- public function getUser()
- {
- $user = new User;
-
- // Filter by search in title
- $search = (int) $this->getState('filter.user_id');
-
- if ($search != 0)
- {
- $user->load((int) $search);
- }
-
- return $user;
- }
-
- /**
- * Method to auto-populate the model state.
- *
- * Note. Calling getState in this method will result in recursion.
- *
- * @param string $ordering An optional ordering field.
- * @param string $direction An optional direction (asc|desc).
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function populateState($ordering = 'a.review_time', $direction = 'desc')
- {
- // Adjust the context to support modal layouts.
- if ($layout = Factory::getApplication()->input->get('layout'))
- {
- $this->context .= '.' . $layout;
- }
-
- parent::populateState($ordering, $direction);
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ // Set the list ordering fields.
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'user_id', 'a.user_id',
+ 'u.name',
+ 'subject', 'a.subject',
+ 'catid', 'a.catid', 'category_id',
+ 'state', 'a.state', 'published',
+ 'c.title',
+ 'review_time', 'a.review_time',
+ 'publish_up', 'a.publish_up',
+ 'publish_down', 'a.publish_down',
+ 'level', 'c.level',
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery A DatabaseQuery object to retrieve the data set.
+ *
+ * @since 2.5
+ */
+ protected function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.id, a.subject, a.checked_out, a.checked_out_time,' .
+ 'a.catid, a.created_time, a.review_time,' .
+ 'a.state, a.publish_up, a.publish_down'
+ )
+ );
+ $query->from('#__user_notes AS a');
+
+ // Join over the category
+ $query->select('c.title AS category_title, c.params AS category_params')
+ ->join('LEFT', '#__categories AS c ON c.id = a.catid');
+
+ // Join over the users for the note user.
+ $query->select('u.name AS user_name')
+ ->join('LEFT', '#__users AS u ON u.id = a.user_id');
+
+ // Join over the users for the checked out user.
+ $query->select('uc.name AS editor')
+ ->join('LEFT', '#__users AS uc ON uc.id = a.checked_out');
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $search3 = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $search3, ParameterType::INTEGER);
+ } elseif (stripos($search, 'uid:') === 0) {
+ $search4 = (int) substr($search, 4);
+ $query->where($db->quoteName('a.user_id') . ' = :id');
+ $query->bind(':id', $search4, ParameterType::INTEGER);
+ } else {
+ $search = '%' . trim($search) . '%';
+ $query->where(
+ '(' . $db->quoteName('a.subject') . ' LIKE :subject'
+ . ' OR ' . $db->quoteName('u.name') . ' LIKE :name'
+ . ' OR ' . $db->quoteName('u.username') . ' LIKE :username)'
+ );
+ $query->bind(':subject', $search);
+ $query->bind(':name', $search);
+ $query->bind(':username', $search);
+ }
+ }
+
+ // Filter by published state
+ $published = $this->getState('filter.published');
+
+ if (is_numeric($published)) {
+ $query->where($db->quoteName('a.state') . ' = :state')
+ ->bind(':state', $published, ParameterType::INTEGER);
+ } elseif ($published !== '*') {
+ $query->whereIn($db->quoteName('a.state'), [0, 1]);
+ }
+
+ // Filter by a single category.
+ $categoryId = (int) $this->getState('filter.category_id');
+
+ if ($categoryId) {
+ $query->where($db->quoteName('a.catid') . ' = :catid')
+ ->bind(':catid', $categoryId, ParameterType::INTEGER);
+ }
+
+ // Filter by a single user.
+ $userId = (int) $this->getState('filter.user_id');
+
+ if ($userId) {
+ // Add the body and where filter.
+ $query->select('a.body')
+ ->where($db->quoteName('a.user_id') . ' = :user_id')
+ ->bind(':user_id', $userId, ParameterType::INTEGER);
+ }
+
+ // Filter on the level.
+ if ($level = $this->getState('filter.level')) {
+ $level = (int) $level;
+ $query->where($db->quoteName('c.level') . ' <= :level')
+ ->bind(':level', $level, ParameterType::INTEGER);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 'a.review_time')) . ' ' . $db->escape($this->getState('list.direction', 'DESC')));
+
+ return $query;
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 2.5
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.published');
+ $id .= ':' . $this->getState('filter.category_id');
+ $id .= ':' . $this->getState('filter.user_id');
+ $id .= ':' . $this->getState('filter.level');
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Gets a user object if the user filter is set.
+ *
+ * @return User The User object
+ *
+ * @since 2.5
+ */
+ public function getUser()
+ {
+ $user = new User();
+
+ // Filter by search in title
+ $search = (int) $this->getState('filter.user_id');
+
+ if ($search != 0) {
+ $user->load((int) $search);
+ }
+
+ return $user;
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function populateState($ordering = 'a.review_time', $direction = 'desc')
+ {
+ // Adjust the context to support modal layouts.
+ if ($layout = Factory::getApplication()->input->get('layout')) {
+ $this->context .= '.' . $layout;
+ }
+
+ parent::populateState($ordering, $direction);
+ }
}
diff --git a/administrator/components/com_users/src/Model/UserModel.php b/administrator/components/com_users/src/Model/UserModel.php
index c2240b21609d0..46eb36baea241 100644
--- a/administrator/components/com_users/src/Model/UserModel.php
+++ b/administrator/components/com_users/src/Model/UserModel.php
@@ -1,4 +1,5 @@
'onUserAfterDelete',
- 'event_after_save' => 'onUserAfterSave',
- 'event_before_delete' => 'onUserBeforeDelete',
- 'event_before_save' => 'onUserBeforeSave',
- 'events_map' => array('save' => 'user', 'delete' => 'user', 'validate' => 'user')
- ), $config
- );
-
- parent::__construct($config, $factory);
- }
-
- /**
- * Returns a reference to the a Table object, always creating it.
- *
- * @param string $type The table type to instantiate
- * @param string $prefix A prefix for the table class name. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return Table A database object
- *
- * @since 1.6
- */
- public function getTable($type = 'User', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
- {
- $table = Table::getInstance($type, $prefix, $config);
-
- return $table;
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return mixed Object on success, false on failure.
- *
- * @since 1.6
- */
- public function getItem($pk = null)
- {
- $pk = (!empty($pk)) ? $pk : (int) $this->getState('user.id');
-
- if ($this->_item === null)
- {
- $this->_item = array();
- }
-
- if (!isset($this->_item[$pk]))
- {
- $this->_item[$pk] = parent::getItem($pk);
- }
-
- return $this->_item[$pk];
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data An optional array of data for the form to interrogate.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A Form object on success, false on failure
- *
- * @since 1.6
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_users.user', 'user', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- $user = Factory::getUser();
-
- // If the user needs to change their password, mark the password fields as required
- if ($user->requireReset)
- {
- $form->setFieldAttribute('password', 'required', 'true');
- $form->setFieldAttribute('password2', 'required', 'true');
- }
-
- // When multilanguage is set, a user's default site language should also be a Content Language
- if (Multilanguage::isEnabled())
- {
- $form->setFieldAttribute('language', 'type', 'frontend_language', 'params');
- }
-
- $userId = (int) $form->getValue('id');
-
- // The user should not be able to set the requireReset value on their own account
- if ($userId === (int) $user->id)
- {
- $form->removeField('requireReset');
- }
-
- /**
- * If users without core.manage permission editing their own account, remove some fields which they should
- * not be allowed to change and prevent them to change user name if configured
- */
- if (!$user->authorise('core.manage', 'com_users') && (int) $user->id === $userId)
- {
- if (!ComponentHelper::getParams('com_users')->get('change_login_name'))
- {
- $form->setFieldAttribute('username', 'required', 'false');
- $form->setFieldAttribute('username', 'readonly', 'true');
- $form->setFieldAttribute('username', 'description', 'COM_USERS_USER_FIELD_NOCHANGE_USERNAME_DESC');
- }
-
- $form->removeField('lastResetTime');
- $form->removeField('resetCount');
- $form->removeField('sendEmail');
- $form->removeField('block');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState('com_users.edit.user.data', array());
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- $this->preprocessData('com_users.profile', $data, 'user');
-
- return $data;
- }
-
- /**
- * Override Joomla\CMS\MVC\Model\AdminModel::preprocessForm to ensure the correct plugin group is loaded.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 1.6
- *
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'user')
- {
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function save($data)
- {
- $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('user.id');
- $user = User::getInstance($pk);
-
- $my = Factory::getUser();
- $iAmSuperAdmin = $my->authorise('core.admin');
-
- // User cannot modify own user groups
- if ((int) $user->id == (int) $my->id && !$iAmSuperAdmin && isset($data['groups']))
- {
- // Form was probably tampered with
- Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_EDIT_OWN_GROUP'), 'warning');
-
- $data['groups'] = null;
- }
-
- if ($data['block'] && $pk == $my->id && !$my->block)
- {
- $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'));
-
- return false;
- }
-
- // Make sure user groups is selected when add/edit an account
- if (empty($data['groups']) && ((int) $user->id != (int) $my->id || $iAmSuperAdmin))
- {
- $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_SAVE_ACCOUNT_WITHOUT_GROUPS'));
-
- return false;
- }
-
- // Make sure that we are not removing ourself from Super Admin group
- if ($iAmSuperAdmin && $my->get('id') == $pk)
- {
- // Check that at least one of our new groups is Super Admin
- $stillSuperAdmin = false;
- $myNewGroups = $data['groups'];
-
- foreach ($myNewGroups as $group)
- {
- $stillSuperAdmin = $stillSuperAdmin ?: Access::checkGroup($group, 'core.admin');
- }
-
- if (!$stillSuperAdmin)
- {
- $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DEMOTE_SELF'));
-
- return false;
- }
- }
-
- // Bind the data.
- if (!$user->bind($data))
- {
- $this->setError($user->getError());
-
- return false;
- }
-
- // Store the data.
- if (!$user->save())
- {
- $this->setError($user->getError());
-
- return false;
- }
-
- // Destroy all active sessions for the user after changing the password or blocking him
- if ($data['password2'] || $data['block'])
- {
- UserHelper::destroyUserSessions($user->id, true);
- }
-
- $this->setState('user.id', $user->id);
-
- return true;
- }
-
- /**
- * Method to delete rows.
- *
- * @param array &$pks An array of item ids.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function delete(&$pks)
- {
- $user = Factory::getUser();
- $table = $this->getTable();
- $pks = (array) $pks;
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = $user->authorise('core.admin');
-
- PluginHelper::importPlugin($this->events_map['delete']);
-
- if (in_array($user->id, $pks))
- {
- $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DELETE_SELF'));
-
- return false;
- }
-
- // Iterate the items to delete each one.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- // Access checks.
- $allow = $user->authorise('core.delete', 'com_users');
-
- // Don't allow non-super-admin to delete a super admin
- $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
-
- if ($allow)
- {
- // Get users data for the users to delete.
- $user_to_delete = Factory::getUser($pk);
-
- // Fire the before delete event.
- Factory::getApplication()->triggerEvent($this->event_before_delete, array($table->getProperties()));
-
- if (!$table->delete($pk))
- {
- $this->setError($table->getError());
-
- return false;
- }
- else
- {
- // Trigger the after delete event.
- Factory::getApplication()->triggerEvent($this->event_after_delete, array($user_to_delete->getProperties(), true, $this->getError()));
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
- }
- }
- else
- {
- $this->setError($table->getError());
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Method to block user records.
- *
- * @param array &$pks The ids of the items to publish.
- * @param integer $value The value of the published state
- *
- * @return boolean True on success.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function block(&$pks, $value = 1)
- {
- $app = Factory::getApplication();
- $user = Factory::getUser();
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = $user->authorise('core.admin');
- $table = $this->getTable();
- $pks = (array) $pks;
-
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Prepare the logout options.
- $options = array(
- 'clientid' => $app->get('shared_session', '0') ? null : 0,
- );
-
- // Access checks.
- foreach ($pks as $i => $pk)
- {
- if ($value == 1 && $pk == $user->get('id'))
- {
- // Cannot block yourself.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'), 'error');
- }
- elseif ($table->load($pk))
- {
- $old = $table->getProperties();
- $allow = $user->authorise('core.edit.state', 'com_users');
-
- // Don't allow non-super-admin to delete a super admin
- $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
-
- if ($allow)
- {
- // Skip changing of same state
- if ($table->block == $value)
- {
- unset($pks[$i]);
- continue;
- }
-
- $table->block = (int) $value;
-
- // If unblocking, also change password reset count to zero to unblock reset
- if ($table->block === 0)
- {
- $table->resetCount = 0;
- }
-
- // Allow an exception to be thrown.
- try
- {
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($old, false, $table->getProperties()));
-
- if (in_array(false, $result, true))
- {
- // Plugin will have to raise its own error or throw an exception.
- return false;
- }
-
- // Store the table.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- if ($table->block)
- {
- UserHelper::destroyUserSessions($table->id);
- }
-
- // Trigger the after save event
- Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]);
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Log the user out.
- if ($value)
- {
- $app->logout($table->id, $options);
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
- }
- }
- }
-
- return true;
- }
-
- /**
- * Method to activate user records.
- *
- * @param array &$pks The ids of the items to activate.
- *
- * @return boolean True on success.
- *
- * @since 1.6
- * @throws \Exception
- */
- public function activate(&$pks)
- {
- $user = Factory::getUser();
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = $user->authorise('core.admin');
- $table = $this->getTable();
- $pks = (array) $pks;
-
- PluginHelper::importPlugin($this->events_map['save']);
-
- // Access checks.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk))
- {
- $old = $table->getProperties();
- $allow = $user->authorise('core.edit.state', 'com_users');
-
- // Don't allow non-super-admin to delete a super admin
- $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
-
- if (empty($table->activation))
- {
- // Ignore activated accounts.
- unset($pks[$i]);
- }
- elseif ($allow)
- {
- $table->block = 0;
- $table->activation = '';
-
- // Allow an exception to be thrown.
- try
- {
- if (!$table->check())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Trigger the before save event.
- $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($old, false, $table->getProperties()));
-
- if (in_array(false, $result, true))
- {
- // Plugin will have to raise it's own error or throw an exception.
- return false;
- }
-
- // Store the table.
- if (!$table->store())
- {
- $this->setError($table->getError());
-
- return false;
- }
-
- // Fire the after save event
- Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]);
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
- else
- {
- // Prune items that you can't change.
- unset($pks[$i]);
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
- }
- }
- }
-
- return true;
- }
-
- /**
- * Method to perform batch operations on an item or a set of items.
- *
- * @param array $commands An array of commands to perform.
- * @param array $pks An array of item ids.
- * @param array $contexts An array of item contexts.
- *
- * @return boolean Returns true on success, false on failure.
- *
- * @since 2.5
- */
- public function batch($commands, $pks, $contexts)
- {
- // Sanitize user ids.
- $pks = array_unique($pks);
- $pks = ArrayHelper::toInteger($pks);
-
- // Remove any values of zero.
- if (array_search(0, $pks, true))
- {
- unset($pks[array_search(0, $pks, true)]);
- }
-
- if (empty($pks))
- {
- $this->setError(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'));
-
- return false;
- }
-
- $done = false;
-
- if (!empty($commands['group_id']))
- {
- $cmd = ArrayHelper::getValue($commands, 'group_action', 'add');
-
- if (!$this->batchUser((int) $commands['group_id'], $pks, $cmd))
- {
- return false;
- }
-
- $done = true;
- }
-
- if (!empty($commands['reset_id']))
- {
- if (!$this->batchReset($pks, $commands['reset_id']))
- {
- return false;
- }
-
- $done = true;
- }
-
- if (!$done)
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
-
- return false;
- }
-
- // Clear the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Batch flag users as being required to reset their passwords
- *
- * @param array $userIds An array of user IDs on which to operate
- * @param string $action The action to perform
- *
- * @return boolean True on success, false on failure
- *
- * @since 3.2
- */
- public function batchReset($userIds, $action)
- {
- $userIds = ArrayHelper::toInteger($userIds);
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
-
- // Non-super super user cannot work with super-admin user.
- if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds))
- {
- $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER'));
-
- return false;
- }
-
- // Set the action to perform
- if ($action === 'yes')
- {
- $value = 1;
- }
- else
- {
- $value = 0;
- }
-
- // Prune out the current user if they are in the supplied user ID array
- $userIds = array_diff($userIds, array(Factory::getUser()->id));
-
- if (empty($userIds))
- {
- $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_REQUIRERESET_SELF'));
-
- return false;
- }
-
- // Get the DB object
- $db = $this->getDatabase();
-
- $userIds = ArrayHelper::toInteger($userIds);
-
- $query = $db->getQuery(true);
-
- // Update the reset flag
- $query->update($db->quoteName('#__users'))
- ->set($db->quoteName('requireReset') . ' = :requireReset')
- ->whereIn($db->quoteName('id'), $userIds)
- ->bind(':requireReset', $value, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Perform batch operations
- *
- * @param integer $groupId The group ID which assignments are being edited
- * @param array $userIds An array of user IDs on which to operate
- * @param string $action The action to perform
- *
- * @return boolean True on success, false on failure
- *
- * @since 1.6
- */
- public function batchUser($groupId, $userIds, $action)
- {
- $userIds = ArrayHelper::toInteger($userIds);
-
- // Check if I am a Super Admin
- $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
-
- // Non-super super user cannot work with super-admin user.
- if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds))
- {
- $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER'));
-
- return false;
- }
-
- // Non-super admin cannot work with super-admin group.
- if ((!$iAmSuperAdmin && Access::checkGroup($groupId, 'core.admin')) || $groupId < 1)
- {
- $this->setError(Text::_('COM_USERS_ERROR_INVALID_GROUP'));
-
- return false;
- }
-
- // Get the DB object
- $db = $this->getDatabase();
-
- switch ($action)
- {
- // Sets users to a selected group
- case 'set':
- $doDelete = 'all';
- $doAssign = true;
- break;
-
- // Remove users from a selected group
- case 'del':
- $doDelete = 'group';
- break;
-
- // Add users to a selected group
- case 'add':
- default:
- $doAssign = true;
- break;
- }
-
- // Remove the users from the group if requested.
- if (isset($doDelete))
- {
- $query = $db->getQuery(true);
-
- // Remove users from the group
- $query->delete($db->quoteName('#__user_usergroup_map'))
- ->whereIn($db->quoteName('user_id'), $userIds);
-
- // Only remove users from selected group
- if ($doDelete == 'group')
- {
- $query->where($db->quoteName('group_id') . ' = :group_id')
- ->bind(':group_id', $groupId, ParameterType::INTEGER);
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- // Assign the users to the group if requested.
- if (isset($doAssign))
- {
- $query = $db->getQuery(true);
-
- // First, we need to check if the user is already assigned to a group
- $query->select($db->quoteName('user_id'))
- ->from($db->quoteName('#__user_usergroup_map'))
- ->where($db->quoteName('group_id') . ' = :group_id')
- ->bind(':group_id', $groupId, ParameterType::INTEGER);
- $db->setQuery($query);
- $users = $db->loadColumn();
-
- // Build the values clause for the assignment query.
- $query->clear();
- $groups = false;
-
- foreach ($userIds as $id)
- {
- if (!in_array($id, $users))
- {
- $query->values($id . ',' . $groupId);
- $groups = true;
- }
- }
-
- // If we have no users to process, throw an error to notify the user
- if (!$groups)
- {
- $this->setError(Text::_('COM_USERS_ERROR_NO_ADDITIONS'));
-
- return false;
- }
-
- $query->insert($db->quoteName('#__user_usergroup_map'))
- ->columns(array($db->quoteName('user_id'), $db->quoteName('group_id')));
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Gets the available groups.
- *
- * @return array An array of groups
- *
- * @since 1.6
- */
- public function getGroups()
- {
- $user = Factory::getUser();
-
- if ($user->authorise('core.edit', 'com_users') && $user->authorise('core.manage', 'com_users'))
- {
- $model = $this->bootComponent('com_users')
- ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]);
-
- return $model->getItems();
- }
- else
- {
- return null;
- }
- }
-
- /**
- * Gets the groups this object is assigned to
- *
- * @param integer $userId The user ID to retrieve the groups for
- *
- * @return array An array of assigned groups
- *
- * @since 1.6
- */
- public function getAssignedGroups($userId = null)
- {
- $userId = (!empty($userId)) ? $userId : (int) $this->getState('user.id');
-
- if (empty($userId))
- {
- $result = array();
- $form = $this->getForm();
-
- if ($form)
- {
- $groupsIDs = $form->getValue('groups');
- }
-
- if (!empty($groupsIDs))
- {
- $result = $groupsIDs;
- }
- else
- {
- $params = ComponentHelper::getParams('com_users');
-
- if ($groupId = $params->get('new_usertype', $params->get('guest_usergroup', 1)))
- {
- $result[] = $groupId;
- }
- }
- }
- else
- {
- $result = UserHelper::getUserGroups($userId);
- }
-
- return $result;
- }
-
- /**
- * No longer used
- *
- * @param integer $userId Ignored
- *
- * @return \stdClass
- *
- * @since 3.2
- * @deprecated 4.2.0 Will be removed in 5.0
- */
- public function getOtpConfig($userId = null)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getUserMfaRecords() instead.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- // Return the configuration object
- return (object) array(
- 'method' => 'none',
- 'config' => array(),
- 'otep' => array()
- );
- }
-
- /**
- * No longer used
- *
- * @param integer $userId Ignored
- * @param \stdClass $otpConfig Ignored
- *
- * @return boolean True on success
- *
- * @since 3.2
- * @deprecated 4.2.0 Will be removed in 5.0
- */
- public function setOtpConfig($userId, $otpConfig)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return true;
- }
-
- /**
- * No longer used
- *
- * @return string
- *
- * @since 3.2
- * @deprecated 4.2.0 Will be removed in 5.0
- */
- public function getOtpConfigEncryptionKey()
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Use \Joomla\CMS\Factory::getApplication()->get(\'secret\') instead',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return Factory::getApplication()->get('secret');
- }
-
- /**
- * No longer used
- *
- * @param integer $userId Ignored
- *
- * @return array Empty array
- *
- * @since 3.2
- * @throws \Exception
- *
- * @deprecated 4.2.0 Will be removed in 5.0.
- */
- public function getTwofactorform($userId = null)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getConfigurationInterface()',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return [];
- }
-
- /**
- * No longer used
- *
- * @param integer $userId Ignored
- * @param integer $count Ignored
- *
- * @return array Empty array
- *
- * @since 3.2
- * @deprecated 4.2.0 Wil be removed in 5.0.
- */
- public function generateOteps($userId, $count = 10)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. See \Joomla\Component\Users\Administrator\Model\BackupcodesModel::saveBackupCodes()',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return [];
- }
-
- /**
- * No longer used. Always returns true.
- *
- * @param integer $userId Ignored
- * @param string $secretKey Ignored
- * @param array $options Ignored
- *
- * @return boolean Always true
- *
- * @since 3.2
- * @throws \Exception
- *
- * @deprecated 4.2.0 Will be removed in 5.0. MFA validation is done in the captive login.
- */
- public function isValidSecretKey($userId, $secretKey, $options = array())
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return true;
- }
-
- /**
- * No longer used
- *
- * @param integer $userId Ignored
- * @param string $otep Ignored
- * @param object $otpConfig Ignored
- *
- * @return boolean Always true
- *
- * @since 3.2
- * @deprecated 4.2.0 Will be removed in 5.0
- */
- public function isValidOtep($userId, $otep, $otpConfig = null)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- return true;
- }
+ /**
+ * An item.
+ *
+ * @var array
+ */
+ protected $_item = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ $config = array_merge(
+ array(
+ 'event_after_delete' => 'onUserAfterDelete',
+ 'event_after_save' => 'onUserAfterSave',
+ 'event_before_delete' => 'onUserBeforeDelete',
+ 'event_before_save' => 'onUserBeforeSave',
+ 'events_map' => array('save' => 'user', 'delete' => 'user', 'validate' => 'user')
+ ),
+ $config
+ );
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Returns a reference to the a Table object, always creating it.
+ *
+ * @param string $type The table type to instantiate
+ * @param string $prefix A prefix for the table class name. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return Table A database object
+ *
+ * @since 1.6
+ */
+ public function getTable($type = 'User', $prefix = 'Joomla\\CMS\\Table\\', $config = array())
+ {
+ $table = Table::getInstance($type, $prefix, $config);
+
+ return $table;
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return mixed Object on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItem($pk = null)
+ {
+ $pk = (!empty($pk)) ? $pk : (int) $this->getState('user.id');
+
+ if ($this->_item === null) {
+ $this->_item = array();
+ }
+
+ if (!isset($this->_item[$pk])) {
+ $this->_item[$pk] = parent::getItem($pk);
+ }
+
+ return $this->_item[$pk];
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A Form object on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_users.user', 'user', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $user = Factory::getUser();
+
+ // If the user needs to change their password, mark the password fields as required
+ if ($user->requireReset) {
+ $form->setFieldAttribute('password', 'required', 'true');
+ $form->setFieldAttribute('password2', 'required', 'true');
+ }
+
+ // When multilanguage is set, a user's default site language should also be a Content Language
+ if (Multilanguage::isEnabled()) {
+ $form->setFieldAttribute('language', 'type', 'frontend_language', 'params');
+ }
+
+ $userId = (int) $form->getValue('id');
+
+ // The user should not be able to set the requireReset value on their own account
+ if ($userId === (int) $user->id) {
+ $form->removeField('requireReset');
+ }
+
+ /**
+ * If users without core.manage permission editing their own account, remove some fields which they should
+ * not be allowed to change and prevent them to change user name if configured
+ */
+ if (!$user->authorise('core.manage', 'com_users') && (int) $user->id === $userId) {
+ if (!ComponentHelper::getParams('com_users')->get('change_login_name')) {
+ $form->setFieldAttribute('username', 'required', 'false');
+ $form->setFieldAttribute('username', 'readonly', 'true');
+ $form->setFieldAttribute('username', 'description', 'COM_USERS_USER_FIELD_NOCHANGE_USERNAME_DESC');
+ }
+
+ $form->removeField('lastResetTime');
+ $form->removeField('resetCount');
+ $form->removeField('sendEmail');
+ $form->removeField('block');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState('com_users.edit.user.data', array());
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ $this->preprocessData('com_users.profile', $data, 'user');
+
+ return $data;
+ }
+
+ /**
+ * Override Joomla\CMS\MVC\Model\AdminModel::preprocessForm to ensure the correct plugin group is loaded.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 1.6
+ *
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'user')
+ {
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function save($data)
+ {
+ $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('user.id');
+ $user = User::getInstance($pk);
+
+ $my = Factory::getUser();
+ $iAmSuperAdmin = $my->authorise('core.admin');
+
+ // User cannot modify own user groups
+ if ((int) $user->id == (int) $my->id && !$iAmSuperAdmin && isset($data['groups'])) {
+ // Form was probably tampered with
+ Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_EDIT_OWN_GROUP'), 'warning');
+
+ $data['groups'] = null;
+ }
+
+ if ($data['block'] && $pk == $my->id && !$my->block) {
+ $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'));
+
+ return false;
+ }
+
+ // Make sure user groups is selected when add/edit an account
+ if (empty($data['groups']) && ((int) $user->id != (int) $my->id || $iAmSuperAdmin)) {
+ $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_SAVE_ACCOUNT_WITHOUT_GROUPS'));
+
+ return false;
+ }
+
+ // Make sure that we are not removing ourself from Super Admin group
+ if ($iAmSuperAdmin && $my->get('id') == $pk) {
+ // Check that at least one of our new groups is Super Admin
+ $stillSuperAdmin = false;
+ $myNewGroups = $data['groups'];
+
+ foreach ($myNewGroups as $group) {
+ $stillSuperAdmin = $stillSuperAdmin ?: Access::checkGroup($group, 'core.admin');
+ }
+
+ if (!$stillSuperAdmin) {
+ $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DEMOTE_SELF'));
+
+ return false;
+ }
+ }
+
+ // Bind the data.
+ if (!$user->bind($data)) {
+ $this->setError($user->getError());
+
+ return false;
+ }
+
+ // Store the data.
+ if (!$user->save()) {
+ $this->setError($user->getError());
+
+ return false;
+ }
+
+ // Destroy all active sessions for the user after changing the password or blocking him
+ if ($data['password2'] || $data['block']) {
+ UserHelper::destroyUserSessions($user->id, true);
+ }
+
+ $this->setState('user.id', $user->id);
+
+ return true;
+ }
+
+ /**
+ * Method to delete rows.
+ *
+ * @param array &$pks An array of item ids.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function delete(&$pks)
+ {
+ $user = Factory::getUser();
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = $user->authorise('core.admin');
+
+ PluginHelper::importPlugin($this->events_map['delete']);
+
+ if (in_array($user->id, $pks)) {
+ $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_DELETE_SELF'));
+
+ return false;
+ }
+
+ // Iterate the items to delete each one.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ // Access checks.
+ $allow = $user->authorise('core.delete', 'com_users');
+
+ // Don't allow non-super-admin to delete a super admin
+ $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
+
+ if ($allow) {
+ // Get users data for the users to delete.
+ $user_to_delete = Factory::getUser($pk);
+
+ // Fire the before delete event.
+ Factory::getApplication()->triggerEvent($this->event_before_delete, array($table->getProperties()));
+
+ if (!$table->delete($pk)) {
+ $this->setError($table->getError());
+
+ return false;
+ } else {
+ // Trigger the after delete event.
+ Factory::getApplication()->triggerEvent($this->event_after_delete, array($user_to_delete->getProperties(), true, $this->getError()));
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_CORE_DELETE_NOT_PERMITTED'), 'error');
+ }
+ } else {
+ $this->setError($table->getError());
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to block user records.
+ *
+ * @param array &$pks The ids of the items to publish.
+ * @param integer $value The value of the published state
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function block(&$pks, $value = 1)
+ {
+ $app = Factory::getApplication();
+ $user = Factory::getUser();
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = $user->authorise('core.admin');
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Prepare the logout options.
+ $options = array(
+ 'clientid' => $app->get('shared_session', '0') ? null : 0,
+ );
+
+ // Access checks.
+ foreach ($pks as $i => $pk) {
+ if ($value == 1 && $pk == $user->get('id')) {
+ // Cannot block yourself.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'), 'error');
+ } elseif ($table->load($pk)) {
+ $old = $table->getProperties();
+ $allow = $user->authorise('core.edit.state', 'com_users');
+
+ // Don't allow non-super-admin to delete a super admin
+ $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
+
+ if ($allow) {
+ // Skip changing of same state
+ if ($table->block == $value) {
+ unset($pks[$i]);
+ continue;
+ }
+
+ $table->block = (int) $value;
+
+ // If unblocking, also change password reset count to zero to unblock reset
+ if ($table->block === 0) {
+ $table->resetCount = 0;
+ }
+
+ // Allow an exception to be thrown.
+ try {
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($old, false, $table->getProperties()));
+
+ if (in_array(false, $result, true)) {
+ // Plugin will have to raise its own error or throw an exception.
+ return false;
+ }
+
+ // Store the table.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ if ($table->block) {
+ UserHelper::destroyUserSessions($table->id);
+ }
+
+ // Trigger the after save event
+ Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]);
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Log the user out.
+ if ($value) {
+ $app->logout($table->id, $options);
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to activate user records.
+ *
+ * @param array &$pks The ids of the items to activate.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function activate(&$pks)
+ {
+ $user = Factory::getUser();
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = $user->authorise('core.admin');
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ PluginHelper::importPlugin($this->events_map['save']);
+
+ // Access checks.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk)) {
+ $old = $table->getProperties();
+ $allow = $user->authorise('core.edit.state', 'com_users');
+
+ // Don't allow non-super-admin to delete a super admin
+ $allow = (!$iAmSuperAdmin && Access::check($pk, 'core.admin')) ? false : $allow;
+
+ if (empty($table->activation)) {
+ // Ignore activated accounts.
+ unset($pks[$i]);
+ } elseif ($allow) {
+ $table->block = 0;
+ $table->activation = '';
+
+ // Allow an exception to be thrown.
+ try {
+ if (!$table->check()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Trigger the before save event.
+ $result = Factory::getApplication()->triggerEvent($this->event_before_save, array($old, false, $table->getProperties()));
+
+ if (in_array(false, $result, true)) {
+ // Plugin will have to raise it's own error or throw an exception.
+ return false;
+ }
+
+ // Store the table.
+ if (!$table->store()) {
+ $this->setError($table->getError());
+
+ return false;
+ }
+
+ // Fire the after save event
+ Factory::getApplication()->triggerEvent($this->event_after_save, [$table->getProperties(), false, true, null]);
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ } else {
+ // Prune items that you can't change.
+ unset($pks[$i]);
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), 'error');
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to perform batch operations on an item or a set of items.
+ *
+ * @param array $commands An array of commands to perform.
+ * @param array $pks An array of item ids.
+ * @param array $contexts An array of item contexts.
+ *
+ * @return boolean Returns true on success, false on failure.
+ *
+ * @since 2.5
+ */
+ public function batch($commands, $pks, $contexts)
+ {
+ // Sanitize user ids.
+ $pks = array_unique($pks);
+ $pks = ArrayHelper::toInteger($pks);
+
+ // Remove any values of zero.
+ if (array_search(0, $pks, true)) {
+ unset($pks[array_search(0, $pks, true)]);
+ }
+
+ if (empty($pks)) {
+ $this->setError(Text::_('COM_USERS_USERS_NO_ITEM_SELECTED'));
+
+ return false;
+ }
+
+ $done = false;
+
+ if (!empty($commands['group_id'])) {
+ $cmd = ArrayHelper::getValue($commands, 'group_action', 'add');
+
+ if (!$this->batchUser((int) $commands['group_id'], $pks, $cmd)) {
+ return false;
+ }
+
+ $done = true;
+ }
+
+ if (!empty($commands['reset_id'])) {
+ if (!$this->batchReset($pks, $commands['reset_id'])) {
+ return false;
+ }
+
+ $done = true;
+ }
+
+ if (!$done) {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
+
+ return false;
+ }
+
+ // Clear the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Batch flag users as being required to reset their passwords
+ *
+ * @param array $userIds An array of user IDs on which to operate
+ * @param string $action The action to perform
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function batchReset($userIds, $action)
+ {
+ $userIds = ArrayHelper::toInteger($userIds);
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
+
+ // Non-super super user cannot work with super-admin user.
+ if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds)) {
+ $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER'));
+
+ return false;
+ }
+
+ // Set the action to perform
+ if ($action === 'yes') {
+ $value = 1;
+ } else {
+ $value = 0;
+ }
+
+ // Prune out the current user if they are in the supplied user ID array
+ $userIds = array_diff($userIds, array(Factory::getUser()->id));
+
+ if (empty($userIds)) {
+ $this->setError(Text::_('COM_USERS_USERS_ERROR_CANNOT_REQUIRERESET_SELF'));
+
+ return false;
+ }
+
+ // Get the DB object
+ $db = $this->getDatabase();
+
+ $userIds = ArrayHelper::toInteger($userIds);
+
+ $query = $db->getQuery(true);
+
+ // Update the reset flag
+ $query->update($db->quoteName('#__users'))
+ ->set($db->quoteName('requireReset') . ' = :requireReset')
+ ->whereIn($db->quoteName('id'), $userIds)
+ ->bind(':requireReset', $value, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Perform batch operations
+ *
+ * @param integer $groupId The group ID which assignments are being edited
+ * @param array $userIds An array of user IDs on which to operate
+ * @param string $action The action to perform
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @since 1.6
+ */
+ public function batchUser($groupId, $userIds, $action)
+ {
+ $userIds = ArrayHelper::toInteger($userIds);
+
+ // Check if I am a Super Admin
+ $iAmSuperAdmin = Factory::getUser()->authorise('core.admin');
+
+ // Non-super super user cannot work with super-admin user.
+ if (!$iAmSuperAdmin && UserHelper::checkSuperUserInUsers($userIds)) {
+ $this->setError(Text::_('COM_USERS_ERROR_CANNOT_BATCH_SUPERUSER'));
+
+ return false;
+ }
+
+ // Non-super admin cannot work with super-admin group.
+ if ((!$iAmSuperAdmin && Access::checkGroup($groupId, 'core.admin')) || $groupId < 1) {
+ $this->setError(Text::_('COM_USERS_ERROR_INVALID_GROUP'));
+
+ return false;
+ }
+
+ // Get the DB object
+ $db = $this->getDatabase();
+
+ switch ($action) {
+ // Sets users to a selected group
+ case 'set':
+ $doDelete = 'all';
+ $doAssign = true;
+ break;
+
+ // Remove users from a selected group
+ case 'del':
+ $doDelete = 'group';
+ break;
+
+ // Add users to a selected group
+ case 'add':
+ default:
+ $doAssign = true;
+ break;
+ }
+
+ // Remove the users from the group if requested.
+ if (isset($doDelete)) {
+ $query = $db->getQuery(true);
+
+ // Remove users from the group
+ $query->delete($db->quoteName('#__user_usergroup_map'))
+ ->whereIn($db->quoteName('user_id'), $userIds);
+
+ // Only remove users from selected group
+ if ($doDelete == 'group') {
+ $query->where($db->quoteName('group_id') . ' = :group_id')
+ ->bind(':group_id', $groupId, ParameterType::INTEGER);
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ // Assign the users to the group if requested.
+ if (isset($doAssign)) {
+ $query = $db->getQuery(true);
+
+ // First, we need to check if the user is already assigned to a group
+ $query->select($db->quoteName('user_id'))
+ ->from($db->quoteName('#__user_usergroup_map'))
+ ->where($db->quoteName('group_id') . ' = :group_id')
+ ->bind(':group_id', $groupId, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $users = $db->loadColumn();
+
+ // Build the values clause for the assignment query.
+ $query->clear();
+ $groups = false;
+
+ foreach ($userIds as $id) {
+ if (!in_array($id, $users)) {
+ $query->values($id . ',' . $groupId);
+ $groups = true;
+ }
+ }
+
+ // If we have no users to process, throw an error to notify the user
+ if (!$groups) {
+ $this->setError(Text::_('COM_USERS_ERROR_NO_ADDITIONS'));
+
+ return false;
+ }
+
+ $query->insert($db->quoteName('#__user_usergroup_map'))
+ ->columns(array($db->quoteName('user_id'), $db->quoteName('group_id')));
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets the available groups.
+ *
+ * @return array An array of groups
+ *
+ * @since 1.6
+ */
+ public function getGroups()
+ {
+ $user = Factory::getUser();
+
+ if ($user->authorise('core.edit', 'com_users') && $user->authorise('core.manage', 'com_users')) {
+ $model = $this->bootComponent('com_users')
+ ->getMVCFactory()->createModel('Groups', 'Administrator', ['ignore_request' => true]);
+
+ return $model->getItems();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the groups this object is assigned to
+ *
+ * @param integer $userId The user ID to retrieve the groups for
+ *
+ * @return array An array of assigned groups
+ *
+ * @since 1.6
+ */
+ public function getAssignedGroups($userId = null)
+ {
+ $userId = (!empty($userId)) ? $userId : (int) $this->getState('user.id');
+
+ if (empty($userId)) {
+ $result = array();
+ $form = $this->getForm();
+
+ if ($form) {
+ $groupsIDs = $form->getValue('groups');
+ }
+
+ if (!empty($groupsIDs)) {
+ $result = $groupsIDs;
+ } else {
+ $params = ComponentHelper::getParams('com_users');
+
+ if ($groupId = $params->get('new_usertype', $params->get('guest_usergroup', 1))) {
+ $result[] = $groupId;
+ }
+ }
+ } else {
+ $result = UserHelper::getUserGroups($userId);
+ }
+
+ return $result;
+ }
+
+ /**
+ * No longer used
+ *
+ * @param integer $userId Ignored
+ *
+ * @return \stdClass
+ *
+ * @since 3.2
+ * @deprecated 4.2.0 Will be removed in 5.0
+ */
+ public function getOtpConfig($userId = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getUserMfaRecords() instead.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ // Return the configuration object
+ return (object) array(
+ 'method' => 'none',
+ 'config' => array(),
+ 'otep' => array()
+ );
+ }
+
+ /**
+ * No longer used
+ *
+ * @param integer $userId Ignored
+ * @param \stdClass $otpConfig Ignored
+ *
+ * @return boolean True on success
+ *
+ * @since 3.2
+ * @deprecated 4.2.0 Will be removed in 5.0
+ */
+ public function setOtpConfig($userId, $otpConfig)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return true;
+ }
+
+ /**
+ * No longer used
+ *
+ * @return string
+ *
+ * @since 3.2
+ * @deprecated 4.2.0 Will be removed in 5.0
+ */
+ public function getOtpConfigEncryptionKey()
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Use \Joomla\CMS\Factory::getApplication()->get(\'secret\') instead',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return Factory::getApplication()->get('secret');
+ }
+
+ /**
+ * No longer used
+ *
+ * @param integer $userId Ignored
+ *
+ * @return array Empty array
+ *
+ * @since 3.2
+ * @throws \Exception
+ *
+ * @deprecated 4.2.0 Will be removed in 5.0.
+ */
+ public function getTwofactorform($userId = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Use \Joomla\Component\Users\Administrator\Helper\Mfa::getConfigurationInterface()',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return [];
+ }
+
+ /**
+ * No longer used
+ *
+ * @param integer $userId Ignored
+ * @param integer $count Ignored
+ *
+ * @return array Empty array
+ *
+ * @since 3.2
+ * @deprecated 4.2.0 Wil be removed in 5.0.
+ */
+ public function generateOteps($userId, $count = 10)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. See \Joomla\Component\Users\Administrator\Model\BackupcodesModel::saveBackupCodes()',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return [];
+ }
+
+ /**
+ * No longer used. Always returns true.
+ *
+ * @param integer $userId Ignored
+ * @param string $secretKey Ignored
+ * @param array $options Ignored
+ *
+ * @return boolean Always true
+ *
+ * @since 3.2
+ * @throws \Exception
+ *
+ * @deprecated 4.2.0 Will be removed in 5.0. MFA validation is done in the captive login.
+ */
+ public function isValidSecretKey($userId, $secretKey, $options = array())
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return true;
+ }
+
+ /**
+ * No longer used
+ *
+ * @param integer $userId Ignored
+ * @param string $otep Ignored
+ * @param object $otpConfig Ignored
+ *
+ * @return boolean Always true
+ *
+ * @since 3.2
+ * @deprecated 4.2.0 Will be removed in 5.0
+ */
+ public function isValidOtep($userId, $otep, $otpConfig = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. Multi-factor Authentication actions are handled by plugins in the multifactorauth folder.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return true;
+ }
}
diff --git a/administrator/components/com_users/src/Model/UsersModel.php b/administrator/components/com_users/src/Model/UsersModel.php
index 7ec7fd620a985..30de749e7b5da 100644
--- a/administrator/components/com_users/src/Model/UsersModel.php
+++ b/administrator/components/com_users/src/Model/UsersModel.php
@@ -1,4 +1,5 @@
input->get('layout', 'default', 'cmd'))
- {
- $this->context .= '.' . $layout;
- }
-
- $groups = json_decode(base64_decode($app->input->get('groups', '', 'BASE64')));
-
- if (isset($groups))
- {
- $groups = ArrayHelper::toInteger($groups);
- }
-
- $this->setState('filter.groups', $groups);
-
- $excluded = json_decode(base64_decode($app->input->get('excluded', '', 'BASE64')));
-
- if (isset($excluded))
- {
- $excluded = ArrayHelper::toInteger($excluded);
- }
-
- $this->setState('filter.excluded', $excluded);
-
- // Load the parameters.
- $params = ComponentHelper::getParams('com_users');
- $this->setState('params', $params);
-
- // List state information.
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a store id based on model configuration state.
- *
- * This is necessary because the model is used by the component and
- * different modules that might need different sets of data or different
- * ordering requirements.
- *
- * @param string $id A prefix for the store id.
- *
- * @return string A store id.
- *
- * @since 1.6
- */
- protected function getStoreId($id = '')
- {
- // Compile the store id.
- $id .= ':' . $this->getState('filter.search');
- $id .= ':' . $this->getState('filter.active');
- $id .= ':' . $this->getState('filter.state');
- $id .= ':' . $this->getState('filter.group_id');
- $id .= ':' . $this->getState('filter.range');
-
- if (PluginHelper::isEnabled('multifactorauth'))
- {
- $id .= ':' . $this->getState('filter.mfa');
- }
-
- return parent::getStoreId($id);
- }
-
- /**
- * Gets the list of users and adds expensive joins to the result set.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 1.6
- */
- public function getItems()
- {
- // Get a storage key.
- $store = $this->getStoreId();
-
- // Try to load the data from internal storage.
- if (empty($this->cache[$store]))
- {
- $groups = $this->getState('filter.groups');
- $groupId = $this->getState('filter.group_id');
-
- if (isset($groups) && (empty($groups) || $groupId && !in_array($groupId, $groups)))
- {
- $items = array();
- }
- else
- {
- $items = parent::getItems();
- }
-
- // Bail out on an error or empty list.
- if (empty($items))
- {
- $this->cache[$store] = $items;
-
- return $items;
- }
-
- // Joining the groups with the main query is a performance hog.
- // Find the information only on the result set.
-
- // First pass: get list of the user ids and reset the counts.
- $userIds = array();
-
- foreach ($items as $item)
- {
- $userIds[] = (int) $item->id;
+ /**
+ * A list of filter variables to not merge into the model's state
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $filterForbiddenList = array('groups', 'excluded');
+
+ /**
+ * Override parent constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ *
+ * @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
+ * @since 3.2
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null)
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'a.id',
+ 'name', 'a.name',
+ 'username', 'a.username',
+ 'email', 'a.email',
+ 'block', 'a.block',
+ 'sendEmail', 'a.sendEmail',
+ 'registerDate', 'a.registerDate',
+ 'lastvisitDate', 'a.lastvisitDate',
+ 'activation', 'a.activation',
+ 'active',
+ 'group_id',
+ 'range',
+ 'lastvisitrange',
+ 'state',
+ 'mfa'
+ );
+ }
+
+ parent::__construct($config, $factory);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function populateState($ordering = 'a.name', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+
+ // Adjust the context to support modal layouts.
+ if ($layout = $app->input->get('layout', 'default', 'cmd')) {
+ $this->context .= '.' . $layout;
+ }
+
+ $groups = json_decode(base64_decode($app->input->get('groups', '', 'BASE64')));
+
+ if (isset($groups)) {
+ $groups = ArrayHelper::toInteger($groups);
+ }
+
+ $this->setState('filter.groups', $groups);
+
+ $excluded = json_decode(base64_decode($app->input->get('excluded', '', 'BASE64')));
+
+ if (isset($excluded)) {
+ $excluded = ArrayHelper::toInteger($excluded);
+ }
+
+ $this->setState('filter.excluded', $excluded);
+
+ // Load the parameters.
+ $params = ComponentHelper::getParams('com_users');
+ $this->setState('params', $params);
+
+ // List state information.
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.active');
+ $id .= ':' . $this->getState('filter.state');
+ $id .= ':' . $this->getState('filter.group_id');
+ $id .= ':' . $this->getState('filter.range');
+
+ if (PluginHelper::isEnabled('multifactorauth')) {
+ $id .= ':' . $this->getState('filter.mfa');
+ }
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Gets the list of users and adds expensive joins to the result set.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ // Get a storage key.
+ $store = $this->getStoreId();
+
+ // Try to load the data from internal storage.
+ if (empty($this->cache[$store])) {
+ $groups = $this->getState('filter.groups');
+ $groupId = $this->getState('filter.group_id');
+
+ if (isset($groups) && (empty($groups) || $groupId && !in_array($groupId, $groups))) {
+ $items = array();
+ } else {
+ $items = parent::getItems();
+ }
+
+ // Bail out on an error or empty list.
+ if (empty($items)) {
+ $this->cache[$store] = $items;
+
+ return $items;
+ }
+
+ // Joining the groups with the main query is a performance hog.
+ // Find the information only on the result set.
+
+ // First pass: get list of the user ids and reset the counts.
+ $userIds = array();
+
+ foreach ($items as $item) {
+ $userIds[] = (int) $item->id;
// phpcs:ignore
$item->group_count = 0;
// phpcs:ignore
$item->group_names = '';
// phpcs:ignore
$item->note_count = 0;
- }
-
- // Get the counts from the database only for the users in the list.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Join over the group mapping table.
- $query->select('map.user_id, COUNT(map.group_id) AS group_count')
- ->from('#__user_usergroup_map AS map')
- ->whereIn($db->quoteName('map.user_id'), $userIds)
- ->group('map.user_id')
- // Join over the user groups table.
- ->join('LEFT', '#__usergroups AS g2 ON g2.id = map.group_id');
-
- $db->setQuery($query);
-
- // Load the counts into an array indexed on the user id field.
- try
- {
- $userGroups = $db->loadObjectList('user_id');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- $query->clear()
- ->select('n.user_id, COUNT(n.id) As note_count')
- ->from('#__user_notes AS n')
- ->whereIn($db->quoteName('n.user_id'), $userIds)
- ->where('n.state >= 0')
- ->group('n.user_id');
-
- $db->setQuery($query);
-
- // Load the counts into an array indexed on the aro.value field (the user id).
- try
- {
- $userNotes = $db->loadObjectList('user_id');
- }
- catch (\RuntimeException $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- // Second pass: collect the group counts into the master items array.
- foreach ($items as &$item)
- {
- if (isset($userGroups[$item->id]))
- {
+ }
+
+ // Get the counts from the database only for the users in the list.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Join over the group mapping table.
+ $query->select('map.user_id, COUNT(map.group_id) AS group_count')
+ ->from('#__user_usergroup_map AS map')
+ ->whereIn($db->quoteName('map.user_id'), $userIds)
+ ->group('map.user_id')
+ // Join over the user groups table.
+ ->join('LEFT', '#__usergroups AS g2 ON g2.id = map.group_id');
+
+ $db->setQuery($query);
+
+ // Load the counts into an array indexed on the user id field.
+ try {
+ $userGroups = $db->loadObjectList('user_id');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ $query->clear()
+ ->select('n.user_id, COUNT(n.id) As note_count')
+ ->from('#__user_notes AS n')
+ ->whereIn($db->quoteName('n.user_id'), $userIds)
+ ->where('n.state >= 0')
+ ->group('n.user_id');
+
+ $db->setQuery($query);
+
+ // Load the counts into an array indexed on the aro.value field (the user id).
+ try {
+ $userNotes = $db->loadObjectList('user_id');
+ } catch (\RuntimeException $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ // Second pass: collect the group counts into the master items array.
+ foreach ($items as &$item) {
+ if (isset($userGroups[$item->id])) {
// phpcs:ignore
$item->group_count = $userGroups[$item->id]->group_count;
- // Group_concat in other databases is not supported
+ // Group_concat in other databases is not supported
// phpcs:ignore
$item->group_names = $this->getUserDisplayedGroups($item->id);
- }
+ }
- if (isset($userNotes[$item->id]))
- {
+ if (isset($userNotes[$item->id])) {
// phpcs:ignore
$item->note_count = $userNotes[$item->id]->note_count;
- }
- }
-
- // Add the items to the internal cache.
- $this->cache[$store] = $items;
- }
-
- return $this->cache[$store];
- }
-
- /**
- * Get the filter form
- *
- * @param array $data data
- * @param boolean $loadData load current data
- *
- * @return Form|null The \JForm object or null if the form can't be found
- *
- * @since 4.2.0
- */
- public function getFilterForm($data = [], $loadData = true)
- {
- $form = parent::getFilterForm($data, $loadData);
-
- if ($form && !PluginHelper::isEnabled('multifactorauth'))
- {
- $form->removeField('mfa', 'filter');
- }
-
- return $form;
- }
-
-
- /**
- * Build an SQL query to load the list data.
- *
- * @return DatabaseQuery
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- // Create a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select the required fields from the table.
- $query->select(
- $this->getState(
- 'list.select',
- 'a.*'
- )
- );
-
- $query->from($db->quoteName('#__users') . ' AS a');
-
- // Include MFA information
- if (PluginHelper::isEnabled('multifactorauth'))
- {
- $subQuery = $db->getQuery(true)
- ->select(
- [
- 'MIN(' . $db->quoteName('user_id') . ') AS ' . $db->quoteName('uid'),
- 'COUNT(*) AS ' . $db->quoteName('mfaRecords')
- ]
- )
- ->from($db->quoteName('#__user_mfa'))
- ->group($db->quoteName('user_id'));
- $query->select($db->quoteName('mfa.mfaRecords'))
- ->join(
- 'left',
- '(' . $subQuery . ') AS ' . $db->quoteName('mfa'),
- $db->quoteName('mfa.uid') . ' = ' . $db->quoteName('a.id')
- );
-
- $mfaState = $this->getState('filter.mfa');
-
- if (is_numeric($mfaState))
- {
- $mfaState = (int) $mfaState;
-
- if ($mfaState === 1)
- {
- $query->where(
- '((' . $db->quoteName('mfa.mfaRecords') . ' > 0) OR (' .
- $db->quoteName('a.otpKey') . ' IS NOT NULL AND ' .
- $db->quoteName('a.otpKey') . ' != ' . $db->quote('') . '))'
- );
- }
- else
- {
- $query->where(
- '((' . $db->quoteName('mfa.mfaRecords') . ' = 0 OR ' .
- $db->quoteName('mfa.mfaRecords') . ' IS NULL) AND (' .
- $db->quoteName('a.otpKey') . ' IS NULL OR ' .
- $db->quoteName('a.otpKey') . ' = ' . $db->quote('') . '))'
- );
- }
- }
- }
-
- // If the model is set to check item state, add to the query.
- $state = $this->getState('filter.state');
-
- if (is_numeric($state))
- {
- $query->where($db->quoteName('a.block') . ' = :state')
- ->bind(':state', $state, ParameterType::INTEGER);
- }
-
- // If the model is set to check the activated state, add to the query.
- $active = $this->getState('filter.active');
-
- if (is_numeric($active))
- {
- if ($active == '0')
- {
- $query->whereIn($db->quoteName('a.activation'), ['', '0']);
- }
- elseif ($active == '1')
- {
- $query->where($query->length($db->quoteName('a.activation')) . ' > 1');
- }
- }
-
- // Filter the items over the group id if set.
- $groupId = $this->getState('filter.group_id');
- $groups = $this->getState('filter.groups');
-
- if ($groupId || isset($groups))
- {
- $query->join('LEFT', '#__user_usergroup_map AS map2 ON map2.user_id = a.id')
- ->group(
- $db->quoteName(
- array(
- 'a.id',
- 'a.name',
- 'a.username',
- 'a.password',
- 'a.block',
- 'a.sendEmail',
- 'a.registerDate',
- 'a.lastvisitDate',
- 'a.activation',
- 'a.params',
- 'a.email',
- 'a.lastResetTime',
- 'a.resetCount',
- 'a.otpKey',
- 'a.otep',
- 'a.requireReset'
- )
- )
- );
-
- if ($groupId)
- {
- $groupId = (int) $groupId;
- $query->where($db->quoteName('map2.group_id') . ' = :group_id')
- ->bind(':group_id', $groupId, ParameterType::INTEGER);
- }
-
- if (isset($groups))
- {
- $query->whereIn($db->quoteName('map2.group_id'), $groups);
- }
- }
-
- // Filter the items over the search string if set.
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- if (stripos($search, 'id:') === 0)
- {
- $ids = (int) substr($search, 3);
- $query->where($db->quoteName('a.id') . ' = :id');
- $query->bind(':id', $ids, ParameterType::INTEGER);
- }
- elseif (stripos($search, 'username:') === 0)
- {
- $search = '%' . substr($search, 9) . '%';
- $query->where($db->quoteName('a.username') . ' LIKE :username');
- $query->bind(':username', $search);
- }
- else
- {
- $search = '%' . trim($search) . '%';
-
- // Add the clauses to the query.
- $query->where(
- '(' . $db->quoteName('a.name') . ' LIKE :name'
- . ' OR ' . $db->quoteName('a.username') . ' LIKE :username'
- . ' OR ' . $db->quoteName('a.email') . ' LIKE :email)'
- )
- ->bind(':name', $search)
- ->bind(':username', $search)
- ->bind(':email', $search);
- }
- }
-
- // Add filter for registration time ranges select list. UI Visitors get a range of predefined
- // values. API users can do a full range based on ISO8601
- $range = $this->getState('filter.range');
- $registrationStart = $this->getState('filter.registrationDateStart');
- $registrationEnd = $this->getState('filter.registrationDateEnd');
-
- // Apply the range filter.
- if ($range || ($registrationStart && $registrationEnd))
- {
- if ($range)
- {
- $dates = $this->buildDateRange($range);
- }
- else
- {
- $dates = [
- 'dNow' => $registrationEnd,
- 'dStart' => $registrationStart,
- ];
- }
-
- if ($dates['dStart'] !== false)
- {
- $dStart = $dates['dStart']->format('Y-m-d H:i:s');
-
- if ($dates['dNow'] === false)
- {
- $query->where($db->quoteName('a.registerDate') . ' < :registerDate');
- $query->bind(':registerDate', $dStart);
- }
- else
- {
- $dNow = $dates['dNow']->format('Y-m-d H:i:s');
-
- $query->where($db->quoteName('a.registerDate') . ' BETWEEN :registerDate1 AND :registerDate2');
- $query->bind(':registerDate1', $dStart);
- $query->bind(':registerDate2', $dNow);
- }
- }
- }
-
- // Add filter for last visit time ranges select list. UI Visitors get a range of predefined
- // values. API users can do a full range based on ISO8601
- $lastvisitrange = $this->getState('filter.lastvisitrange');
- $lastVisitStart = $this->getState('filter.lastVisitStart');
- $lastVisitEnd = $this->getState('filter.lastVisitEnd');
-
- // Apply the range filter.
- if ($lastvisitrange || ($lastVisitStart && $lastVisitEnd))
- {
- if ($lastvisitrange)
- {
- $dates = $this->buildDateRange($lastvisitrange);
- }
- else
- {
- $dates = [
- 'dNow' => $lastVisitEnd,
- 'dStart' => $lastVisitStart,
- ];
- }
-
- if ($dates['dStart'] === false)
- {
- $query->where($db->quoteName('a.lastvisitDate') . ' IS NULL');
- }
- else
- {
- $query->where($db->quoteName('a.lastvisitDate') . ' IS NOT NULL');
-
- $dStart = $dates['dStart']->format('Y-m-d H:i:s');
-
- if ($dates['dNow'] === false)
- {
- $query->where($db->quoteName('a.lastvisitDate') . ' < :lastvisitDate');
- $query->bind(':lastvisitDate', $dStart);
- }
- else
- {
- $dNow = $dates['dNow']->format('Y-m-d H:i:s');
-
- $query->where($db->quoteName('a.lastvisitDate') . ' BETWEEN :lastvisitDate1 AND :lastvisitDate2');
- $query->bind(':lastvisitDate1', $dStart);
- $query->bind(':lastvisitDate2', $dNow);
- }
- }
- }
-
- // Filter by excluded users
- $excluded = $this->getState('filter.excluded');
-
- if (!empty($excluded))
- {
- $query->whereNotIn($db->quoteName('id'), $excluded);
- }
-
- // Add the list ordering clause.
- $query->order(
- $db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
- );
-
- return $query;
- }
-
- /**
- * Construct the date range to filter on.
- *
- * @param string $range The textual range to construct the filter for.
- *
- * @return array The date range to filter on.
- *
- * @since 3.6.0
- * @throws \Exception
- */
- private function buildDateRange($range)
- {
- // Get UTC for now.
- $dNow = new Date;
- $dStart = clone $dNow;
-
- switch ($range)
- {
- case 'past_week':
- $dStart->modify('-7 day');
- break;
-
- case 'past_1month':
- $dStart->modify('-1 month');
- break;
-
- case 'past_3month':
- $dStart->modify('-3 month');
- break;
-
- case 'past_6month':
- $dStart->modify('-6 month');
- $arr = [];
- break;
-
- case 'post_year':
- $dNow = false;
- case 'past_year':
- $dStart->modify('-1 year');
- break;
-
- case 'today':
- // Ranges that need to align with local 'days' need special treatment.
- $app = Factory::getApplication();
- $offset = $app->get('offset');
-
- // Reset the start time to be the beginning of today, local time.
- $dStart = new Date('now', $offset);
- $dStart->setTime(0, 0, 0);
-
- // Now change the timezone back to UTC.
- $tz = new \DateTimeZone('GMT');
- $dStart->setTimezone($tz);
- break;
- case 'never':
- $dNow = false;
- $dStart = false;
- break;
- }
-
- return array('dNow' => $dNow, 'dStart' => $dStart);
- }
-
- /**
- * SQL server change
- *
- * @param integer $userId User identifier
- *
- * @return string Groups titles imploded :$
- */
- protected function getUserDisplayedGroups($userId)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__usergroups', 'ug'))
- ->join('LEFT', $db->quoteName('#__user_usergroup_map', 'map') . ' ON (ug.id = map.group_id)')
- ->where($db->quoteName('map.user_id') . ' = :user_id')
- ->bind(':user_id', $userId, ParameterType::INTEGER);
-
- try
- {
- $result = $db->setQuery($query)->loadColumn();
- }
- catch (\RuntimeException $e)
- {
- $result = array();
- }
-
- return implode("\n", $result);
- }
+ }
+ }
+
+ // Add the items to the internal cache.
+ $this->cache[$store] = $items;
+ }
+
+ return $this->cache[$store];
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return Form|null The \JForm object or null if the form can't be found
+ *
+ * @since 4.2.0
+ */
+ public function getFilterForm($data = [], $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ if ($form && !PluginHelper::isEnabled('multifactorauth')) {
+ $form->removeField('mfa', 'filter');
+ }
+
+ return $form;
+ }
+
+
+ /**
+ * Build an SQL query to load the list data.
+ *
+ * @return DatabaseQuery
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ // Create a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select the required fields from the table.
+ $query->select(
+ $this->getState(
+ 'list.select',
+ 'a.*'
+ )
+ );
+
+ $query->from($db->quoteName('#__users') . ' AS a');
+
+ // Include MFA information
+ if (PluginHelper::isEnabled('multifactorauth')) {
+ $subQuery = $db->getQuery(true)
+ ->select(
+ [
+ 'MIN(' . $db->quoteName('user_id') . ') AS ' . $db->quoteName('uid'),
+ 'COUNT(*) AS ' . $db->quoteName('mfaRecords')
+ ]
+ )
+ ->from($db->quoteName('#__user_mfa'))
+ ->group($db->quoteName('user_id'));
+ $query->select($db->quoteName('mfa.mfaRecords'))
+ ->join(
+ 'left',
+ '(' . $subQuery . ') AS ' . $db->quoteName('mfa'),
+ $db->quoteName('mfa.uid') . ' = ' . $db->quoteName('a.id')
+ );
+
+ $mfaState = $this->getState('filter.mfa');
+
+ if (is_numeric($mfaState)) {
+ $mfaState = (int) $mfaState;
+
+ if ($mfaState === 1) {
+ $query->where(
+ '((' . $db->quoteName('mfa.mfaRecords') . ' > 0) OR (' .
+ $db->quoteName('a.otpKey') . ' IS NOT NULL AND ' .
+ $db->quoteName('a.otpKey') . ' != ' . $db->quote('') . '))'
+ );
+ } else {
+ $query->where(
+ '((' . $db->quoteName('mfa.mfaRecords') . ' = 0 OR ' .
+ $db->quoteName('mfa.mfaRecords') . ' IS NULL) AND (' .
+ $db->quoteName('a.otpKey') . ' IS NULL OR ' .
+ $db->quoteName('a.otpKey') . ' = ' . $db->quote('') . '))'
+ );
+ }
+ }
+ }
+
+ // If the model is set to check item state, add to the query.
+ $state = $this->getState('filter.state');
+
+ if (is_numeric($state)) {
+ $query->where($db->quoteName('a.block') . ' = :state')
+ ->bind(':state', $state, ParameterType::INTEGER);
+ }
+
+ // If the model is set to check the activated state, add to the query.
+ $active = $this->getState('filter.active');
+
+ if (is_numeric($active)) {
+ if ($active == '0') {
+ $query->whereIn($db->quoteName('a.activation'), ['', '0']);
+ } elseif ($active == '1') {
+ $query->where($query->length($db->quoteName('a.activation')) . ' > 1');
+ }
+ }
+
+ // Filter the items over the group id if set.
+ $groupId = $this->getState('filter.group_id');
+ $groups = $this->getState('filter.groups');
+
+ if ($groupId || isset($groups)) {
+ $query->join('LEFT', '#__user_usergroup_map AS map2 ON map2.user_id = a.id')
+ ->group(
+ $db->quoteName(
+ array(
+ 'a.id',
+ 'a.name',
+ 'a.username',
+ 'a.password',
+ 'a.block',
+ 'a.sendEmail',
+ 'a.registerDate',
+ 'a.lastvisitDate',
+ 'a.activation',
+ 'a.params',
+ 'a.email',
+ 'a.lastResetTime',
+ 'a.resetCount',
+ 'a.otpKey',
+ 'a.otep',
+ 'a.requireReset'
+ )
+ )
+ );
+
+ if ($groupId) {
+ $groupId = (int) $groupId;
+ $query->where($db->quoteName('map2.group_id') . ' = :group_id')
+ ->bind(':group_id', $groupId, ParameterType::INTEGER);
+ }
+
+ if (isset($groups)) {
+ $query->whereIn($db->quoteName('map2.group_id'), $groups);
+ }
+ }
+
+ // Filter the items over the search string if set.
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ if (stripos($search, 'id:') === 0) {
+ $ids = (int) substr($search, 3);
+ $query->where($db->quoteName('a.id') . ' = :id');
+ $query->bind(':id', $ids, ParameterType::INTEGER);
+ } elseif (stripos($search, 'username:') === 0) {
+ $search = '%' . substr($search, 9) . '%';
+ $query->where($db->quoteName('a.username') . ' LIKE :username');
+ $query->bind(':username', $search);
+ } else {
+ $search = '%' . trim($search) . '%';
+
+ // Add the clauses to the query.
+ $query->where(
+ '(' . $db->quoteName('a.name') . ' LIKE :name'
+ . ' OR ' . $db->quoteName('a.username') . ' LIKE :username'
+ . ' OR ' . $db->quoteName('a.email') . ' LIKE :email)'
+ )
+ ->bind(':name', $search)
+ ->bind(':username', $search)
+ ->bind(':email', $search);
+ }
+ }
+
+ // Add filter for registration time ranges select list. UI Visitors get a range of predefined
+ // values. API users can do a full range based on ISO8601
+ $range = $this->getState('filter.range');
+ $registrationStart = $this->getState('filter.registrationDateStart');
+ $registrationEnd = $this->getState('filter.registrationDateEnd');
+
+ // Apply the range filter.
+ if ($range || ($registrationStart && $registrationEnd)) {
+ if ($range) {
+ $dates = $this->buildDateRange($range);
+ } else {
+ $dates = [
+ 'dNow' => $registrationEnd,
+ 'dStart' => $registrationStart,
+ ];
+ }
+
+ if ($dates['dStart'] !== false) {
+ $dStart = $dates['dStart']->format('Y-m-d H:i:s');
+
+ if ($dates['dNow'] === false) {
+ $query->where($db->quoteName('a.registerDate') . ' < :registerDate');
+ $query->bind(':registerDate', $dStart);
+ } else {
+ $dNow = $dates['dNow']->format('Y-m-d H:i:s');
+
+ $query->where($db->quoteName('a.registerDate') . ' BETWEEN :registerDate1 AND :registerDate2');
+ $query->bind(':registerDate1', $dStart);
+ $query->bind(':registerDate2', $dNow);
+ }
+ }
+ }
+
+ // Add filter for last visit time ranges select list. UI Visitors get a range of predefined
+ // values. API users can do a full range based on ISO8601
+ $lastvisitrange = $this->getState('filter.lastvisitrange');
+ $lastVisitStart = $this->getState('filter.lastVisitStart');
+ $lastVisitEnd = $this->getState('filter.lastVisitEnd');
+
+ // Apply the range filter.
+ if ($lastvisitrange || ($lastVisitStart && $lastVisitEnd)) {
+ if ($lastvisitrange) {
+ $dates = $this->buildDateRange($lastvisitrange);
+ } else {
+ $dates = [
+ 'dNow' => $lastVisitEnd,
+ 'dStart' => $lastVisitStart,
+ ];
+ }
+
+ if ($dates['dStart'] === false) {
+ $query->where($db->quoteName('a.lastvisitDate') . ' IS NULL');
+ } else {
+ $query->where($db->quoteName('a.lastvisitDate') . ' IS NOT NULL');
+
+ $dStart = $dates['dStart']->format('Y-m-d H:i:s');
+
+ if ($dates['dNow'] === false) {
+ $query->where($db->quoteName('a.lastvisitDate') . ' < :lastvisitDate');
+ $query->bind(':lastvisitDate', $dStart);
+ } else {
+ $dNow = $dates['dNow']->format('Y-m-d H:i:s');
+
+ $query->where($db->quoteName('a.lastvisitDate') . ' BETWEEN :lastvisitDate1 AND :lastvisitDate2');
+ $query->bind(':lastvisitDate1', $dStart);
+ $query->bind(':lastvisitDate2', $dNow);
+ }
+ }
+ }
+
+ // Filter by excluded users
+ $excluded = $this->getState('filter.excluded');
+
+ if (!empty($excluded)) {
+ $query->whereNotIn($db->quoteName('id'), $excluded);
+ }
+
+ // Add the list ordering clause.
+ $query->order(
+ $db->quoteName($db->escape($this->getState('list.ordering', 'a.name'))) . ' ' . $db->escape($this->getState('list.direction', 'ASC'))
+ );
+
+ return $query;
+ }
+
+ /**
+ * Construct the date range to filter on.
+ *
+ * @param string $range The textual range to construct the filter for.
+ *
+ * @return array The date range to filter on.
+ *
+ * @since 3.6.0
+ * @throws \Exception
+ */
+ private function buildDateRange($range)
+ {
+ // Get UTC for now.
+ $dNow = new Date();
+ $dStart = clone $dNow;
+
+ switch ($range) {
+ case 'past_week':
+ $dStart->modify('-7 day');
+ break;
+
+ case 'past_1month':
+ $dStart->modify('-1 month');
+ break;
+
+ case 'past_3month':
+ $dStart->modify('-3 month');
+ break;
+
+ case 'past_6month':
+ $dStart->modify('-6 month');
+ $arr = [];
+ break;
+
+ case 'post_year':
+ $dNow = false;
+
+ // No break
+
+ case 'past_year':
+ $dStart->modify('-1 year');
+ break;
+
+ case 'today':
+ // Ranges that need to align with local 'days' need special treatment.
+ $app = Factory::getApplication();
+ $offset = $app->get('offset');
+
+ // Reset the start time to be the beginning of today, local time.
+ $dStart = new Date('now', $offset);
+ $dStart->setTime(0, 0, 0);
+
+ // Now change the timezone back to UTC.
+ $tz = new \DateTimeZone('GMT');
+ $dStart->setTimezone($tz);
+ break;
+ case 'never':
+ $dNow = false;
+ $dStart = false;
+ break;
+ }
+
+ return array('dNow' => $dNow, 'dStart' => $dStart);
+ }
+
+ /**
+ * SQL server change
+ *
+ * @param integer $userId User identifier
+ *
+ * @return string Groups titles imploded :$
+ */
+ protected function getUserDisplayedGroups($userId)
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__usergroups', 'ug'))
+ ->join('LEFT', $db->quoteName('#__user_usergroup_map', 'map') . ' ON (ug.id = map.group_id)')
+ ->where($db->quoteName('map.user_id') . ' = :user_id')
+ ->bind(':user_id', $userId, ParameterType::INTEGER);
+
+ try {
+ $result = $db->setQuery($query)->loadColumn();
+ } catch (\RuntimeException $e) {
+ $result = array();
+ }
+
+ return implode("\n", $result);
+ }
}
diff --git a/administrator/components/com_users/src/Service/Encrypt.php b/administrator/components/com_users/src/Service/Encrypt.php
index 935be2eff6144..eb1ca97d6b0e3 100644
--- a/administrator/components/com_users/src/Service/Encrypt.php
+++ b/administrator/components/com_users/src/Service/Encrypt.php
@@ -1,4 +1,5 @@
initialize();
- }
-
- /**
- * Encrypt the plaintext $data and return the ciphertext prefixed by ###AES128###
- *
- * @param string $data The plaintext data
- *
- * @return string The ciphertext, prefixed by ###AES128###
- *
- * @since 4.2.0
- */
- public function encrypt(string $data): string
- {
- if (!is_object($this->aes))
- {
- return $data;
- }
-
- $this->aes->setPassword($this->getPassword(), false);
- $encrypted = $this->aes->encryptString($data, true);
-
- return '###AES128###' . $encrypted;
- }
-
- /**
- * Decrypt the ciphertext, prefixed by ###AES128###, and return the plaintext.
- *
- * @param string $data The ciphertext, prefixed by ###AES128###
- * @param bool $legacy Use legacy key expansion? Use it to decrypt data encrypted with FOF 3.
- *
- * @return string The plaintext data
- *
- * @since 4.2.0
- */
- public function decrypt(string $data, bool $legacy = false): string
- {
- if (substr($data, 0, 12) != '###AES128###')
- {
- return $data;
- }
-
- $data = substr($data, 12);
-
- if (!is_object($this->aes))
- {
- return $data;
- }
-
- $this->aes->setPassword($this->getPassword(), $legacy);
- $decrypted = $this->aes->decryptString($data, true, $legacy);
-
- // Decrypted data is null byte padded. We have to remove the padding before proceeding.
- return rtrim($decrypted, "\0");
- }
-
- /**
- * Initialize the AES cryptography object
- *
- * @return void
- * @since 4.2.0
- */
- private function initialize(): void
- {
- if (is_object($this->aes))
- {
- return;
- }
-
- $password = $this->getPassword();
-
- if (empty($password))
- {
- return;
- }
-
- $this->aes = new Aes('cbc');
- $this->aes->setPassword($password);
- }
-
- /**
- * Returns the password used to encrypt information in the component
- *
- * @return string
- *
- * @since 4.2.0
- */
- private function getPassword(): string
- {
- try
- {
- return Factory::getApplication()->get('secret', '');
- }
- catch (\Exception $e)
- {
- return '';
- }
- }
+ /**
+ * The encryption engine used by this service
+ *
+ * @var Aes
+ * @since 4.2.0
+ */
+ private $aes;
+
+ /**
+ * EncryptService constructor.
+ *
+ * @since 4.2.0
+ */
+ public function __construct()
+ {
+ $this->initialize();
+ }
+
+ /**
+ * Encrypt the plaintext $data and return the ciphertext prefixed by ###AES128###
+ *
+ * @param string $data The plaintext data
+ *
+ * @return string The ciphertext, prefixed by ###AES128###
+ *
+ * @since 4.2.0
+ */
+ public function encrypt(string $data): string
+ {
+ if (!is_object($this->aes)) {
+ return $data;
+ }
+
+ $this->aes->setPassword($this->getPassword(), false);
+ $encrypted = $this->aes->encryptString($data, true);
+
+ return '###AES128###' . $encrypted;
+ }
+
+ /**
+ * Decrypt the ciphertext, prefixed by ###AES128###, and return the plaintext.
+ *
+ * @param string $data The ciphertext, prefixed by ###AES128###
+ * @param bool $legacy Use legacy key expansion? Use it to decrypt data encrypted with FOF 3.
+ *
+ * @return string The plaintext data
+ *
+ * @since 4.2.0
+ */
+ public function decrypt(string $data, bool $legacy = false): string
+ {
+ if (substr($data, 0, 12) != '###AES128###') {
+ return $data;
+ }
+
+ $data = substr($data, 12);
+
+ if (!is_object($this->aes)) {
+ return $data;
+ }
+
+ $this->aes->setPassword($this->getPassword(), $legacy);
+ $decrypted = $this->aes->decryptString($data, true, $legacy);
+
+ // Decrypted data is null byte padded. We have to remove the padding before proceeding.
+ return rtrim($decrypted, "\0");
+ }
+
+ /**
+ * Initialize the AES cryptography object
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function initialize(): void
+ {
+ if (is_object($this->aes)) {
+ return;
+ }
+
+ $password = $this->getPassword();
+
+ if (empty($password)) {
+ return;
+ }
+
+ $this->aes = new Aes('cbc');
+ $this->aes->setPassword($password);
+ }
+
+ /**
+ * Returns the password used to encrypt information in the component
+ *
+ * @return string
+ *
+ * @since 4.2.0
+ */
+ private function getPassword(): string
+ {
+ try {
+ return Factory::getApplication()->get('secret', '');
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Service/HTML/Users.php b/administrator/components/com_users/src/Service/HTML/Users.php
index 1f8e9edfe6194..a3c045ccd3f5d 100644
--- a/administrator/components/com_users/src/Service/HTML/Users.php
+++ b/administrator/components/com_users/src/Service/HTML/Users.php
@@ -1,4 +1,5 @@
element if the specified file exists, otherwise, a null string
- *
- * @since 2.5
- * @throws \Exception
- */
- public function image($src)
- {
- $src = preg_replace('#[^A-Z0-9\-_\./]#i', '', $src);
- $file = JPATH_SITE . '/' . $src;
-
- Path::check($file);
-
- if (!file_exists($file))
- {
- return '';
- }
-
- return ' ';
- }
-
- /**
- * Displays an icon to add a note for this user.
- *
- * @param integer $userId The user ID
- *
- * @return string A link to add a note
- *
- * @since 2.5
- */
- public function addNote($userId)
- {
- $title = Text::_('COM_USERS_ADD_NOTE');
-
- return ''
- . ' ' . $title . ' ';
- }
-
- /**
- * Displays an icon to filter the notes list on this user.
- *
- * @param integer $count The number of notes for the user
- * @param integer $userId The user ID
- *
- * @return string A link to apply a filter
- *
- * @since 2.5
- */
- public function filterNotes($count, $userId)
- {
- if (empty($count))
- {
- return '';
- }
-
- $title = Text::_('COM_USERS_FILTER_NOTES');
-
- return ' ' . $title . ' ';
- }
-
- /**
- * Displays a note icon.
- *
- * @param integer $count The number of notes for the user
- * @param integer $userId The user ID
- *
- * @return string A link to a modal window with the user notes
- *
- * @since 2.5
- */
- public function notes($count, $userId)
- {
- if (empty($count))
- {
- return '';
- }
-
- $title = Text::plural('COM_USERS_N_USER_NOTES', $count);
-
- return ' ' . $title . ' ';
- }
-
- /**
- * Renders the modal html.
- *
- * @param integer $count The number of notes for the user
- * @param integer $userId The user ID
- *
- * @return string The html for the rendered modal
- *
- * @since 3.4.1
- */
- public function notesModal($count, $userId)
- {
- if (empty($count))
- {
- return '';
- }
-
- $title = Text::plural('COM_USERS_N_USER_NOTES', $count);
- $footer = ''
- . Text::_('JTOOLBAR_CLOSE') . ' ';
-
- return HTMLHelper::_(
- 'bootstrap.renderModal',
- 'userModal_' . (int) $userId,
- array(
- 'title' => $title,
- 'backdrop' => 'static',
- 'keyboard' => true,
- 'closeButton' => true,
- 'footer' => $footer,
- 'url' => Route::_('index.php?option=com_users&view=notes&tmpl=component&layout=modal&filter[user_id]=' . (int) $userId),
- 'height' => '300px',
- 'width' => '800px',
- )
- );
-
- }
-
- /**
- * Build an array of block/unblock user states to be used by jgrid.state,
- * State options will be different for any user
- * and for currently logged in user
- *
- * @param boolean $self True if state array is for currently logged in user
- *
- * @return array a list of possible states to display
- *
- * @since 3.0
- */
- public function blockStates( $self = false)
- {
- if ($self)
- {
- $states = array(
- 1 => array(
- 'task' => 'unblock',
- 'text' => '',
- 'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
- 'inactive_title' => '',
- 'tip' => true,
- 'active_class' => 'unpublish',
- 'inactive_class' => 'unpublish',
- ),
- 0 => array(
- 'task' => 'block',
- 'text' => '',
- 'active_title' => '',
- 'inactive_title' => 'COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF',
- 'tip' => true,
- 'active_class' => 'publish',
- 'inactive_class' => 'publish',
- )
- );
- }
- else
- {
- $states = array(
- 1 => array(
- 'task' => 'unblock',
- 'text' => '',
- 'active_title' => 'COM_USERS_TOOLBAR_UNBLOCK',
- 'inactive_title' => '',
- 'tip' => true,
- 'active_class' => 'unpublish',
- 'inactive_class' => 'unpublish',
- ),
- 0 => array(
- 'task' => 'block',
- 'text' => '',
- 'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
- 'inactive_title' => '',
- 'tip' => true,
- 'active_class' => 'publish',
- 'inactive_class' => 'publish',
- )
- );
- }
-
- return $states;
- }
-
- /**
- * Build an array of activate states to be used by jgrid.state,
- *
- * @return array a list of possible states to display
- *
- * @since 3.0
- */
- public function activateStates()
- {
- $states = array(
- 1 => array(
- 'task' => 'activate',
- 'text' => '',
- 'active_title' => 'COM_USERS_TOOLBAR_ACTIVATE',
- 'inactive_title' => '',
- 'tip' => true,
- 'active_class' => 'unpublish',
- 'inactive_class' => 'unpublish',
- ),
- 0 => array(
- 'task' => '',
- 'text' => '',
- 'active_title' => '',
- 'inactive_title' => 'COM_USERS_ACTIVATED',
- 'tip' => true,
- 'active_class' => 'publish',
- 'inactive_class' => 'publish',
- )
- );
-
- return $states;
- }
-
- /**
- * Get the sanitized value
- *
- * @param mixed $value Value of the field
- *
- * @return mixed String/void
- *
- * @since 1.6
- */
- public function value($value)
- {
- if (is_string($value))
- {
- $value = trim($value);
- }
-
- if (empty($value))
- {
- return Text::_('COM_USERS_PROFILE_VALUE_NOT_FOUND');
- }
-
- elseif (!is_array($value))
- {
- return htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
- }
- }
-
- /**
- * Get the space symbol
- *
- * @param mixed $value Value of the field
- *
- * @return string
- *
- * @since 1.6
- */
- public function spacer($value)
- {
- return '';
- }
-
- /**
- * Get the sanitized template style
- *
- * @param mixed $value Value of the field
- *
- * @return mixed String/void
- *
- * @since 1.6
- */
- public function templatestyle($value)
- {
- if (empty($value))
- {
- return static::value($value);
- }
- else
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__template_styles'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $value, ParameterType::INTEGER);
- $db->setQuery($query);
- $title = $db->loadResult();
-
- if ($title)
- {
- return htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
- }
- else
- {
- return static::value('');
- }
- }
- }
-
- /**
- * Get the sanitized language
- *
- * @param mixed $value Value of the field
- *
- * @return mixed String/void
- *
- * @since 1.6
- */
- public function admin_language($value)
- {
- if (!$value)
- {
- return static::value($value);
- }
-
- $path = LanguageHelper::getLanguagePath(JPATH_ADMINISTRATOR, $value);
- $file = $path . '/langmetadata.xml';
-
- if (!is_file($file))
- {
- // For language packs from before 4.0.
- $file = $path . '/' . $value . '.xml';
-
- if (!is_file($file))
- {
- return static::value($value);
- }
- }
-
- $result = LanguageHelper::parseXMLLanguageFile($file);
-
- if ($result)
- {
- return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
- }
-
- return static::value($value);
- }
-
- /**
- * Get the sanitized language
- *
- * @param mixed $value Value of the field
- *
- * @return mixed String/void
- *
- * @since 1.6
- */
- public function language($value)
- {
- if (!$value)
- {
- return static::value($value);
- }
-
- $path = LanguageHelper::getLanguagePath(JPATH_SITE, $value);
- $file = $path . '/langmetadata.xml';
-
- if (!is_file($file))
- {
- // For language packs from before 4.0.
- $file = $path . '/' . $value . '.xml';
-
- if (!is_file($file))
- {
- return static::value($value);
- }
- }
-
- $result = LanguageHelper::parseXMLLanguageFile($file);
-
- if ($result)
- {
- return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
- }
-
- return static::value($value);
- }
-
- /**
- * Get the sanitized editor name
- *
- * @param mixed $value Value of the field
- *
- * @return mixed String/void
- *
- * @since 1.6
- */
- public function editor($value)
- {
- if (empty($value))
- {
- return static::value($value);
- }
- else
- {
- $db = Factory::getDbo();
- $lang = Factory::getLanguage();
- $query = $db->getQuery(true)
- ->select($db->quoteName('name'))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('element') . ' = :element')
- ->where($db->quoteName('folder') . ' = ' . $db->quote('editors'))
- ->bind(':element', $value);
- $db->setQuery($query);
- $title = $db->loadResult();
-
- if ($title)
- {
- $lang->load("plg_editors_$value.sys", JPATH_ADMINISTRATOR)
- || $lang->load("plg_editors_$value.sys", JPATH_PLUGINS . '/editors/' . $value);
- $lang->load($title . '.sys');
-
- return Text::_($title);
- }
- else
- {
- return static::value('');
- }
- }
- }
+ /**
+ * Display an image.
+ *
+ * @param string $src The source of the image
+ *
+ * @return string A element if the specified file exists, otherwise, a null string
+ *
+ * @since 2.5
+ * @throws \Exception
+ */
+ public function image($src)
+ {
+ $src = preg_replace('#[^A-Z0-9\-_\./]#i', '', $src);
+ $file = JPATH_SITE . '/' . $src;
+
+ Path::check($file);
+
+ if (!file_exists($file)) {
+ return '';
+ }
+
+ return ' ';
+ }
+
+ /**
+ * Displays an icon to add a note for this user.
+ *
+ * @param integer $userId The user ID
+ *
+ * @return string A link to add a note
+ *
+ * @since 2.5
+ */
+ public function addNote($userId)
+ {
+ $title = Text::_('COM_USERS_ADD_NOTE');
+
+ return ''
+ . ' ' . $title . ' ';
+ }
+
+ /**
+ * Displays an icon to filter the notes list on this user.
+ *
+ * @param integer $count The number of notes for the user
+ * @param integer $userId The user ID
+ *
+ * @return string A link to apply a filter
+ *
+ * @since 2.5
+ */
+ public function filterNotes($count, $userId)
+ {
+ if (empty($count)) {
+ return '';
+ }
+
+ $title = Text::_('COM_USERS_FILTER_NOTES');
+
+ return ' ' . $title . ' ';
+ }
+
+ /**
+ * Displays a note icon.
+ *
+ * @param integer $count The number of notes for the user
+ * @param integer $userId The user ID
+ *
+ * @return string A link to a modal window with the user notes
+ *
+ * @since 2.5
+ */
+ public function notes($count, $userId)
+ {
+ if (empty($count)) {
+ return '';
+ }
+
+ $title = Text::plural('COM_USERS_N_USER_NOTES', $count);
+
+ return ' ' . $title . ' ';
+ }
+
+ /**
+ * Renders the modal html.
+ *
+ * @param integer $count The number of notes for the user
+ * @param integer $userId The user ID
+ *
+ * @return string The html for the rendered modal
+ *
+ * @since 3.4.1
+ */
+ public function notesModal($count, $userId)
+ {
+ if (empty($count)) {
+ return '';
+ }
+
+ $title = Text::plural('COM_USERS_N_USER_NOTES', $count);
+ $footer = ''
+ . Text::_('JTOOLBAR_CLOSE') . ' ';
+
+ return HTMLHelper::_(
+ 'bootstrap.renderModal',
+ 'userModal_' . (int) $userId,
+ array(
+ 'title' => $title,
+ 'backdrop' => 'static',
+ 'keyboard' => true,
+ 'closeButton' => true,
+ 'footer' => $footer,
+ 'url' => Route::_('index.php?option=com_users&view=notes&tmpl=component&layout=modal&filter[user_id]=' . (int) $userId),
+ 'height' => '300px',
+ 'width' => '800px',
+ )
+ );
+ }
+
+ /**
+ * Build an array of block/unblock user states to be used by jgrid.state,
+ * State options will be different for any user
+ * and for currently logged in user
+ *
+ * @param boolean $self True if state array is for currently logged in user
+ *
+ * @return array a list of possible states to display
+ *
+ * @since 3.0
+ */
+ public function blockStates($self = false)
+ {
+ if ($self) {
+ $states = array(
+ 1 => array(
+ 'task' => 'unblock',
+ 'text' => '',
+ 'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
+ 'inactive_title' => '',
+ 'tip' => true,
+ 'active_class' => 'unpublish',
+ 'inactive_class' => 'unpublish',
+ ),
+ 0 => array(
+ 'task' => 'block',
+ 'text' => '',
+ 'active_title' => '',
+ 'inactive_title' => 'COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF',
+ 'tip' => true,
+ 'active_class' => 'publish',
+ 'inactive_class' => 'publish',
+ )
+ );
+ } else {
+ $states = array(
+ 1 => array(
+ 'task' => 'unblock',
+ 'text' => '',
+ 'active_title' => 'COM_USERS_TOOLBAR_UNBLOCK',
+ 'inactive_title' => '',
+ 'tip' => true,
+ 'active_class' => 'unpublish',
+ 'inactive_class' => 'unpublish',
+ ),
+ 0 => array(
+ 'task' => 'block',
+ 'text' => '',
+ 'active_title' => 'COM_USERS_TOOLBAR_BLOCK',
+ 'inactive_title' => '',
+ 'tip' => true,
+ 'active_class' => 'publish',
+ 'inactive_class' => 'publish',
+ )
+ );
+ }
+
+ return $states;
+ }
+
+ /**
+ * Build an array of activate states to be used by jgrid.state,
+ *
+ * @return array a list of possible states to display
+ *
+ * @since 3.0
+ */
+ public function activateStates()
+ {
+ $states = array(
+ 1 => array(
+ 'task' => 'activate',
+ 'text' => '',
+ 'active_title' => 'COM_USERS_TOOLBAR_ACTIVATE',
+ 'inactive_title' => '',
+ 'tip' => true,
+ 'active_class' => 'unpublish',
+ 'inactive_class' => 'unpublish',
+ ),
+ 0 => array(
+ 'task' => '',
+ 'text' => '',
+ 'active_title' => '',
+ 'inactive_title' => 'COM_USERS_ACTIVATED',
+ 'tip' => true,
+ 'active_class' => 'publish',
+ 'inactive_class' => 'publish',
+ )
+ );
+
+ return $states;
+ }
+
+ /**
+ * Get the sanitized value
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return mixed String/void
+ *
+ * @since 1.6
+ */
+ public function value($value)
+ {
+ if (is_string($value)) {
+ $value = trim($value);
+ }
+
+ if (empty($value)) {
+ return Text::_('COM_USERS_PROFILE_VALUE_NOT_FOUND');
+ } elseif (!is_array($value)) {
+ return htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
+ }
+ }
+
+ /**
+ * Get the space symbol
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return string
+ *
+ * @since 1.6
+ */
+ public function spacer($value)
+ {
+ return '';
+ }
+
+ /**
+ * Get the sanitized template style
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return mixed String/void
+ *
+ * @since 1.6
+ */
+ public function templatestyle($value)
+ {
+ if (empty($value)) {
+ return static::value($value);
+ } else {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__template_styles'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $value, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $title = $db->loadResult();
+
+ if ($title) {
+ return htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
+ } else {
+ return static::value('');
+ }
+ }
+ }
+
+ /**
+ * Get the sanitized language
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return mixed String/void
+ *
+ * @since 1.6
+ */
+ public function admin_language($value)
+ {
+ if (!$value) {
+ return static::value($value);
+ }
+
+ $path = LanguageHelper::getLanguagePath(JPATH_ADMINISTRATOR, $value);
+ $file = $path . '/langmetadata.xml';
+
+ if (!is_file($file)) {
+ // For language packs from before 4.0.
+ $file = $path . '/' . $value . '.xml';
+
+ if (!is_file($file)) {
+ return static::value($value);
+ }
+ }
+
+ $result = LanguageHelper::parseXMLLanguageFile($file);
+
+ if ($result) {
+ return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
+ }
+
+ return static::value($value);
+ }
+
+ /**
+ * Get the sanitized language
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return mixed String/void
+ *
+ * @since 1.6
+ */
+ public function language($value)
+ {
+ if (!$value) {
+ return static::value($value);
+ }
+
+ $path = LanguageHelper::getLanguagePath(JPATH_SITE, $value);
+ $file = $path . '/langmetadata.xml';
+
+ if (!is_file($file)) {
+ // For language packs from before 4.0.
+ $file = $path . '/' . $value . '.xml';
+
+ if (!is_file($file)) {
+ return static::value($value);
+ }
+ }
+
+ $result = LanguageHelper::parseXMLLanguageFile($file);
+
+ if ($result) {
+ return htmlspecialchars($result['name'], ENT_COMPAT, 'UTF-8');
+ }
+
+ return static::value($value);
+ }
+
+ /**
+ * Get the sanitized editor name
+ *
+ * @param mixed $value Value of the field
+ *
+ * @return mixed String/void
+ *
+ * @since 1.6
+ */
+ public function editor($value)
+ {
+ if (empty($value)) {
+ return static::value($value);
+ } else {
+ $db = Factory::getDbo();
+ $lang = Factory::getLanguage();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('name'))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('element') . ' = :element')
+ ->where($db->quoteName('folder') . ' = ' . $db->quote('editors'))
+ ->bind(':element', $value);
+ $db->setQuery($query);
+ $title = $db->loadResult();
+
+ if ($title) {
+ $lang->load("plg_editors_$value.sys", JPATH_ADMINISTRATOR)
+ || $lang->load("plg_editors_$value.sys", JPATH_PLUGINS . '/editors/' . $value);
+ $lang->load($title . '.sys');
+
+ return Text::_($title);
+ } else {
+ return static::value('');
+ }
+ }
+ }
}
diff --git a/administrator/components/com_users/src/Table/MfaTable.php b/administrator/components/com_users/src/Table/MfaTable.php
index 4e201e279c051..324e175f4e093 100644
--- a/administrator/components/com_users/src/Table/MfaTable.php
+++ b/administrator/components/com_users/src/Table/MfaTable.php
@@ -1,4 +1,5 @@
encryptService = new Encrypt;
- }
-
- /**
- * Method to store a row in the database from the Table instance properties.
- *
- * If a primary key value is set the row with that primary key value will be updated with the instance property values.
- * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return boolean True on success.
- *
- * @since 4.2.0
- */
- public function store($updateNulls = true)
- {
- // Encrypt the options before saving them
- $this->options = $this->encryptService->encrypt(json_encode($this->options ?: []));
-
- // Set last_used date to null if empty or zero date
+ /**
+ * Table constructor
+ *
+ * @param DatabaseDriver $db Database driver object
+ * @param DispatcherInterface|null $dispatcher Events dispatcher object
+ *
+ * @since 4.2.0
+ */
+ public function __construct(DatabaseDriver $db, DispatcherInterface $dispatcher = null)
+ {
+ parent::__construct('#__user_mfa', 'id', $db, $dispatcher);
+
+ $this->encryptService = new Encrypt();
+ }
+
+ /**
+ * Method to store a row in the database from the Table instance properties.
+ *
+ * If a primary key value is set the row with that primary key value will be updated with the instance property values.
+ * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.2.0
+ */
+ public function store($updateNulls = true)
+ {
+ // Encrypt the options before saving them
+ $this->options = $this->encryptService->encrypt(json_encode($this->options ?: []));
+
+ // Set last_used date to null if empty or zero date
// phpcs:ignore
if (!((int) $this->last_used))
- {
+ {
// phpcs:ignore
$this->last_used = null;
- }
+ }
// phpcs:ignore
$records = MfaHelper::getUserMfaRecords($this->user_id);
- if ($this->id)
- {
- // Existing record. Remove it from the list of records.
- $records = array_filter(
- $records,
- function ($rec) {
- return $rec->id != $this->id;
- }
- );
- }
-
- // Update the dates on a new record
- if (empty($this->id))
- {
+ if ($this->id) {
+ // Existing record. Remove it from the list of records.
+ $records = array_filter(
+ $records,
+ function ($rec) {
+ return $rec->id != $this->id;
+ }
+ );
+ }
+
+ // Update the dates on a new record
+ if (empty($this->id)) {
// phpcs:ignore
$this->created_on = Date::getInstance()->toSql();
// phpcs:ignore
$this->last_used = null;
- }
-
- // Do I need to mark this record as the default?
- if ($this->default == 0)
- {
- $hasDefaultRecord = array_reduce(
- $records,
- function ($carry, $record)
- {
- return $carry || ($record->default == 1);
- },
- false
- );
-
- $this->default = $hasDefaultRecord ? 0 : 1;
- }
-
- // Let's find out if we are saving a new MFA method record without having backup codes yet.
- $mustCreateBackupCodes = false;
-
- if (empty($this->id) && $this->method !== 'backupcodes')
- {
- // Do I have any backup records?
- $hasBackupCodes = array_reduce(
- $records,
- function (bool $carry, $record)
- {
- return $carry || $record->method === 'backupcodes';
- },
- false
- );
-
- $mustCreateBackupCodes = !$hasBackupCodes;
-
- // If the only other entry is the backup records one I need to make this the default method
- if ($hasBackupCodes && count($records) === 1)
- {
- $this->default = 1;
- }
- }
-
- // Store the record
- try
- {
- $result = parent::store($updateNulls);
- }
- catch (Throwable $e)
- {
- $this->setError($e->getMessage());
-
- $result = false;
- }
-
- // Decrypt the options (they must be decrypted in memory)
- $this->decryptOptions();
-
- if ($result)
- {
- // If this record is the default unset the default flag from all other records
- $this->switchDefaultRecord();
-
- // Do I need to generate backup codes?
- if ($mustCreateBackupCodes)
- {
- $this->generateBackupCodes();
- }
- }
-
- return $result;
- }
-
- /**
- * Method to load a row from the database by primary key and bind the fields to the Table instance properties.
- *
- * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match.
- * If not set the instance property value is used.
- * @param boolean $reset True to reset the default values before loading the new row.
- *
- * @return boolean True if successful. False if row not found.
- *
- * @since 4.2.0
- * @throws \InvalidArgumentException
- * @throws RuntimeException
- * @throws \UnexpectedValueException
- */
- public function load($keys = null, $reset = true)
- {
- $result = parent::load($keys, $reset);
-
- if ($result)
- {
- $this->decryptOptions();
- }
-
- return $result;
- }
-
- /**
- * Method to delete a row from the database table by primary key value.
- *
- * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
- *
- * @return boolean True on success.
- *
- * @since 4.2.0
- * @throws \UnexpectedValueException
- */
- public function delete($pk = null)
- {
- $record = $this;
-
- if ($pk != $this->id)
- {
- $record = clone $this;
- $record->reset();
- $result = $record->load($pk);
-
- if (!$result)
- {
- // If the record does not exist I will stomp my feet and deny your request
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
-
- $user = Factory::getApplication()->getIdentity()
- ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- // The user must be a registered user, not a guest
- if ($user->guest)
- {
- throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- // Save flags used onAfterDelete
- $this->deleteFlags[$record->id] = [
- 'default' => $record->default,
+ }
+
+ // Do I need to mark this record as the default?
+ if ($this->default == 0) {
+ $hasDefaultRecord = array_reduce(
+ $records,
+ function ($carry, $record) {
+ return $carry || ($record->default == 1);
+ },
+ false
+ );
+
+ $this->default = $hasDefaultRecord ? 0 : 1;
+ }
+
+ // Let's find out if we are saving a new MFA method record without having backup codes yet.
+ $mustCreateBackupCodes = false;
+
+ if (empty($this->id) && $this->method !== 'backupcodes') {
+ // Do I have any backup records?
+ $hasBackupCodes = array_reduce(
+ $records,
+ function (bool $carry, $record) {
+ return $carry || $record->method === 'backupcodes';
+ },
+ false
+ );
+
+ $mustCreateBackupCodes = !$hasBackupCodes;
+
+ // If the only other entry is the backup records one I need to make this the default method
+ if ($hasBackupCodes && count($records) === 1) {
+ $this->default = 1;
+ }
+ }
+
+ // Store the record
+ try {
+ $result = parent::store($updateNulls);
+ } catch (Throwable $e) {
+ $this->setError($e->getMessage());
+
+ $result = false;
+ }
+
+ // Decrypt the options (they must be decrypted in memory)
+ $this->decryptOptions();
+
+ if ($result) {
+ // If this record is the default unset the default flag from all other records
+ $this->switchDefaultRecord();
+
+ // Do I need to generate backup codes?
+ if ($mustCreateBackupCodes) {
+ $this->generateBackupCodes();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to load a row from the database by primary key and bind the fields to the Table instance properties.
+ *
+ * @param mixed $keys An optional primary key value to load the row by, or an array of fields to match.
+ * If not set the instance property value is used.
+ * @param boolean $reset True to reset the default values before loading the new row.
+ *
+ * @return boolean True if successful. False if row not found.
+ *
+ * @since 4.2.0
+ * @throws \InvalidArgumentException
+ * @throws RuntimeException
+ * @throws \UnexpectedValueException
+ */
+ public function load($keys = null, $reset = true)
+ {
+ $result = parent::load($keys, $reset);
+
+ if ($result) {
+ $this->decryptOptions();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to delete a row from the database table by primary key value.
+ *
+ * @param mixed $pk An optional primary key value to delete. If not set the instance property value is used.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.2.0
+ * @throws \UnexpectedValueException
+ */
+ public function delete($pk = null)
+ {
+ $record = $this;
+
+ if ($pk != $this->id) {
+ $record = clone $this;
+ $record->reset();
+ $result = $record->load($pk);
+
+ if (!$result) {
+ // If the record does not exist I will stomp my feet and deny your request
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+
+ $user = Factory::getApplication()->getIdentity()
+ ?? Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ // The user must be a registered user, not a guest
+ if ($user->guest) {
+ throw new RuntimeException(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ // Save flags used onAfterDelete
+ $this->deleteFlags[$record->id] = [
+ 'default' => $record->default,
// phpcs:ignore
'numRecords' => $this->getNumRecords($record->user_id),
// phpcs:ignore
'user_id' => $record->user_id,
- 'method' => $record->method,
- ];
+ 'method' => $record->method,
+ ];
- if (\is_null($pk))
- {
+ if (\is_null($pk)) {
// phpcs:ignore
$pk = [$this->_tbl_key => $this->id];
- }
- elseif (!\is_array($pk))
- {
+ } elseif (!\is_array($pk)) {
// phpcs:ignore
$pk = [$this->_tbl_key => $pk];
- }
-
- $isDeleted = parent::delete($pk);
-
- if ($isDeleted)
- {
- $this->afterDelete($pk);
- }
-
- return $isDeleted;
- }
-
- /**
- * Decrypt the possibly encrypted options
- *
- * @return void
- * @since 4.2.0
- */
- private function decryptOptions(): void
- {
- // Try with modern decryption
- $decrypted = @json_decode($this->encryptService->decrypt($this->options ?? ''), true);
-
- if (is_string($decrypted))
- {
- $decrypted = @json_decode($decrypted, true);
- }
-
- // Fall back to legacy decryption
- if (!is_array($decrypted))
- {
- $decrypted = @json_decode($this->encryptService->decrypt($this->options ?? '', true), true);
-
- if (is_string($decrypted))
- {
- $decrypted = @json_decode($decrypted, true);
- }
- }
-
- $this->options = $decrypted ?: [];
- }
-
- /**
- * If this record is set to be the default, unset the default flag from the other records for the same user.
- *
- * @return void
- * @since 4.2.0
- */
- private function switchDefaultRecord(): void
- {
- if (!$this->default)
- {
- return;
- }
-
- /**
- * This record is marked as default, therefore we need to unset the default flag from all other records for this
- * user.
- */
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__user_mfa'))
- ->set($db->quoteName('default') . ' = 0')
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->where($db->quoteName('id') . ' != :id')
+ }
+
+ $isDeleted = parent::delete($pk);
+
+ if ($isDeleted) {
+ $this->afterDelete($pk);
+ }
+
+ return $isDeleted;
+ }
+
+ /**
+ * Decrypt the possibly encrypted options
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function decryptOptions(): void
+ {
+ // Try with modern decryption
+ $decrypted = @json_decode($this->encryptService->decrypt($this->options ?? ''), true);
+
+ if (is_string($decrypted)) {
+ $decrypted = @json_decode($decrypted, true);
+ }
+
+ // Fall back to legacy decryption
+ if (!is_array($decrypted)) {
+ $decrypted = @json_decode($this->encryptService->decrypt($this->options ?? '', true), true);
+
+ if (is_string($decrypted)) {
+ $decrypted = @json_decode($decrypted, true);
+ }
+ }
+
+ $this->options = $decrypted ?: [];
+ }
+
+ /**
+ * If this record is set to be the default, unset the default flag from the other records for the same user.
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function switchDefaultRecord(): void
+ {
+ if (!$this->default) {
+ return;
+ }
+
+ /**
+ * This record is marked as default, therefore we need to unset the default flag from all other records for this
+ * user.
+ */
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__user_mfa'))
+ ->set($db->quoteName('default') . ' = 0')
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->where($db->quoteName('id') . ' != :id')
// phpcs:ignore
->bind(':user_id', $this->user_id, ParameterType::INTEGER)
- ->bind(':id', $this->id, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
- }
-
- /**
- * Regenerate backup code is the flag is set.
- *
- * @return void
- * @throws Exception
- * @since 4.2.0
- */
- private function generateBackupCodes(): void
- {
- /** @var MVCFactoryInterface $factory */
- $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
-
- /** @var BackupcodesModel $backupCodes */
- $backupCodes = $factory->createModel('Backupcodes', 'Administrator');
+ ->bind(':id', $this->id, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+ }
+
+ /**
+ * Regenerate backup code is the flag is set.
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ private function generateBackupCodes(): void
+ {
+ /** @var MVCFactoryInterface $factory */
+ $factory = Factory::getApplication()->bootComponent('com_users')->getMVCFactory();
+
+ /** @var BackupcodesModel $backupCodes */
+ $backupCodes = $factory->createModel('Backupcodes', 'Administrator');
// phpcs:ignore
$user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($this->user_id);
- $backupCodes->regenerateBackupCodes($user);
- }
-
- /**
- * Runs after successfully deleting a record
- *
- * @param int|array $pk The promary key of the deleted record
- *
- * @return void
- * @since 4.2.0
- */
- private function afterDelete($pk): void
- {
- if (is_array($pk))
- {
+ $backupCodes->regenerateBackupCodes($user);
+ }
+
+ /**
+ * Runs after successfully deleting a record
+ *
+ * @param int|array $pk The promary key of the deleted record
+ *
+ * @return void
+ * @since 4.2.0
+ */
+ private function afterDelete($pk): void
+ {
+ if (is_array($pk)) {
// phpcs:ignore
$pk = $pk[$this->_tbl_key] ?? array_shift($pk);
- }
-
- if (!isset($this->deleteFlags[$pk]))
- {
- return;
- }
-
- if (($this->deleteFlags[$pk]['numRecords'] <= 2) && ($this->deleteFlags[$pk]['method'] != 'backupcodes'))
- {
- /**
- * This was the second to last MFA record in the database (the last one is the `backupcodes`). Therefore, we
- * need to delete the remaining entry and go away. We don't trigger this if the Method we are deleting was
- * the `backupcodes` because we might just be regenerating the backup codes.
- */
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__user_mfa'))
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
- $db->setQuery($query)->execute();
-
- unset($this->deleteFlags[$pk]);
-
- return;
- }
-
- // This was the default record. Promote the next available record to default.
- if ($this->deleteFlags[$pk]['default'])
- {
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__user_mfa'))
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->where($db->quoteName('method') . ' != ' . $db->quote('backupcodes'))
- ->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
- $ids = $db->setQuery($query)->loadColumn();
-
- if (empty($ids))
- {
- return;
- }
-
- $id = array_shift($ids);
- $query = $db->getQuery(true)
- ->update($db->quoteName('#__user_mfa'))
- ->set($db->quoteName('default') . ' = 1')
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
- $db->setQuery($query)->execute();
- }
- }
-
- /**
- * Get the number of MFA records for a give user ID
- *
- * @param int $userId The user ID to check
- *
- * @return integer
- *
- * @since 4.2.0
- */
- private function getNumRecords(int $userId): int
- {
- $db = $this->getDbo();
- $query = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName('#__user_mfa'))
- ->where($db->quoteName('user_id') . ' = :user_id')
- ->bind(':user_id', $userId, ParameterType::INTEGER);
- $numOldRecords = $db->setQuery($query)->loadResult();
-
- return (int) $numOldRecords;
- }
+ }
+
+ if (!isset($this->deleteFlags[$pk])) {
+ return;
+ }
+
+ if (($this->deleteFlags[$pk]['numRecords'] <= 2) && ($this->deleteFlags[$pk]['method'] != 'backupcodes')) {
+ /**
+ * This was the second to last MFA record in the database (the last one is the `backupcodes`). Therefore, we
+ * need to delete the remaining entry and go away. We don't trigger this if the Method we are deleting was
+ * the `backupcodes` because we might just be regenerating the backup codes.
+ */
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__user_mfa'))
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+
+ unset($this->deleteFlags[$pk]);
+
+ return;
+ }
+
+ // This was the default record. Promote the next available record to default.
+ if ($this->deleteFlags[$pk]['default']) {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__user_mfa'))
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->where($db->quoteName('method') . ' != ' . $db->quote('backupcodes'))
+ ->bind(':user_id', $this->deleteFlags[$pk]['user_id'], ParameterType::INTEGER);
+ $ids = $db->setQuery($query)->loadColumn();
+
+ if (empty($ids)) {
+ return;
+ }
+
+ $id = array_shift($ids);
+ $query = $db->getQuery(true)
+ ->update($db->quoteName('#__user_mfa'))
+ ->set($db->quoteName('default') . ' = 1')
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+ $db->setQuery($query)->execute();
+ }
+ }
+
+ /**
+ * Get the number of MFA records for a give user ID
+ *
+ * @param int $userId The user ID to check
+ *
+ * @return integer
+ *
+ * @since 4.2.0
+ */
+ private function getNumRecords(int $userId): int
+ {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName('#__user_mfa'))
+ ->where($db->quoteName('user_id') . ' = :user_id')
+ ->bind(':user_id', $userId, ParameterType::INTEGER);
+ $numOldRecords = $db->setQuery($query)->loadResult();
+
+ return (int) $numOldRecords;
+ }
}
diff --git a/administrator/components/com_users/src/Table/NoteTable.php b/administrator/components/com_users/src/Table/NoteTable.php
index b7b22b26a5a87..72744912d5a4e 100644
--- a/administrator/components/com_users/src/Table/NoteTable.php
+++ b/administrator/components/com_users/src/Table/NoteTable.php
@@ -1,4 +1,5 @@
typeAlias = 'com_users.note';
- parent::__construct('#__user_notes', 'id', $db);
-
- $this->setColumnAlias('published', 'state');
- }
-
- /**
- * Overloaded store method for the notes table.
- *
- * @param boolean $updateNulls Toggle whether null values should be updated.
- *
- * @return boolean True on success, false on failure.
- *
- * @since 2.5
- */
- public function store($updateNulls = true)
- {
- $date = Factory::getDate()->toSql();
- $userId = Factory::getUser()->get('id');
-
- if (!((int) $this->review_time))
- {
- $this->review_time = null;
- }
-
- if ($this->id)
- {
- // Existing item
- $this->modified_time = $date;
- $this->modified_user_id = $userId;
- }
- else
- {
- // New record.
- $this->created_time = $date;
- $this->created_user_id = $userId;
- $this->modified_time = $date;
- $this->modified_user_id = $userId;
- }
-
- // Attempt to store the data.
- return parent::store($updateNulls);
- }
-
- /**
- * Method to perform sanity checks on the Table instance properties to ensure they are safe to store in the database.
- *
- * @return boolean True if the instance is sane and able to be stored in the database.
- *
- * @since 4.0.0
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (empty($this->modified_time))
- {
- $this->modified_time = $this->created_time;
- }
-
- if (empty($this->modified_user_id))
- {
- $this->modified_user_id = $this->created_user_id;
- }
-
- return true;
- }
-
- /**
- * Get the type alias for the history table
- *
- * @return string The alias as described above
- *
- * @since 4.0.0
- */
- public function getTypeAlias()
- {
- return $this->typeAlias;
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * Constructor
+ *
+ * @param DatabaseDriver $db Database object
+ *
+ * @since 2.5
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = 'com_users.note';
+ parent::__construct('#__user_notes', 'id', $db);
+
+ $this->setColumnAlias('published', 'state');
+ }
+
+ /**
+ * Overloaded store method for the notes table.
+ *
+ * @param boolean $updateNulls Toggle whether null values should be updated.
+ *
+ * @return boolean True on success, false on failure.
+ *
+ * @since 2.5
+ */
+ public function store($updateNulls = true)
+ {
+ $date = Factory::getDate()->toSql();
+ $userId = Factory::getUser()->get('id');
+
+ if (!((int) $this->review_time)) {
+ $this->review_time = null;
+ }
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_time = $date;
+ $this->modified_user_id = $userId;
+ } else {
+ // New record.
+ $this->created_time = $date;
+ $this->created_user_id = $userId;
+ $this->modified_time = $date;
+ $this->modified_user_id = $userId;
+ }
+
+ // Attempt to store the data.
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to perform sanity checks on the Table instance properties to ensure they are safe to store in the database.
+ *
+ * @return boolean True if the instance is sane and able to be stored in the database.
+ *
+ * @since 4.0.0
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (empty($this->modified_time)) {
+ $this->modified_time = $this->created_time;
+ }
+
+ if (empty($this->modified_user_id)) {
+ $this->modified_user_id = $this->created_user_id;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the type alias for the history table
+ *
+ * @return string The alias as described above
+ *
+ * @since 4.0.0
+ */
+ public function getTypeAlias()
+ {
+ return $this->typeAlias;
+ }
}
diff --git a/administrator/components/com_users/src/View/Captive/HtmlView.php b/administrator/components/com_users/src/View/Captive/HtmlView.php
index 739de5d1e5b7d..a569c5b6ff346 100644
--- a/administrator/components/com_users/src/View/Captive/HtmlView.php
+++ b/administrator/components/com_users/src/View/Captive/HtmlView.php
@@ -1,4 +1,5 @@
setSiteTemplateStyle();
-
- $app = Factory::getApplication();
- $user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
-
- PluginHelper::importPlugin('multifactorauth');
- $event = new BeforeDisplayMethods($user);
- $app->getDispatcher()->dispatch($event->getName(), $event);
-
- /** @var CaptiveModel $model */
- $model = $this->getModel();
-
- // Load data from the model
- $this->isAdmin = $app->isClient('administrator');
- $this->records = $this->get('records');
- $this->record = $this->get('record');
- $this->mfaMethods = MfaHelper::getMfaMethods();
-
- if (!empty($this->records))
- {
- /** @var BackupcodesModel $codesModel */
- $codesModel = $this->getModel('Backupcodes');
- $backupCodesRecord = $codesModel->getBackupCodesRecord();
-
- if (!is_null($backupCodesRecord))
- {
- $backupCodesRecord->title = Text::_('COM_USERS_USER_BACKUPCODES');
- $this->records[] = $backupCodesRecord;
- }
- }
-
- // If we only have one record there's no point asking the user to select a MFA Method
- if (empty($this->record) && !empty($this->records))
- {
- // Default to the first record
- $this->record = reset($this->records);
-
- // If we have multiple records try to make this record the default
- if (count($this->records) > 1)
- {
- foreach ($this->records as $record)
- {
- if ($record->default)
- {
- $this->record = $record;
-
- break;
- }
- }
- }
- }
-
- // Set the correct layout based on the availability of a MFA record
- $this->setLayout('default');
-
- // If we have no record selected or explicitly asked to run the 'select' task use the correct layout
- if (is_null($this->record) || ($model->getState('task') == 'select'))
- {
- $this->setLayout('select');
- }
-
- switch ($this->getLayout())
- {
- case 'select':
- $this->allowEntryBatching = 1;
-
- $event = new NotifyActionLog('onComUsersCaptiveShowSelect', []);
- Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
- break;
-
- case 'default':
- default:
- $this->renderOptions = $model->loadCaptiveRenderOptions($this->record);
- $this->allowEntryBatching = $this->renderOptions['allowEntryBatching'] ?? 0;
-
- $event = new NotifyActionLog(
- 'onComUsersCaptiveShowCaptive',
- [
- $this->escape($this->record->title),
- ]
- );
- Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
- break;
- }
-
- // Which title should I use for the page?
- $this->title = $this->get('PageTitle');
-
- // Back-end: always show a title in the 'title' module position, not in the page body
- if ($this->isAdmin)
- {
- ToolbarHelper::title(Text::_('COM_USERS_HEADING_MFA'), 'users user-lock');
- $this->title = '';
- }
-
- if ($this->isAdmin && $this->getLayout() === 'default')
- {
- $bar = Toolbar::getInstance();
- $button = (new BasicButton('user-mfa-submit'))
- ->text($this->renderOptions['submit_text'])
- ->icon($this->renderOptions['submit_icon']);
- $bar->appendButton($button);
-
- $button = (new BasicButton('user-mfa-logout'))
- ->text('COM_USERS_MFA_LOGOUT')
- ->buttonClass('btn btn-danger')
- ->icon('icon icon-lock');
- $bar->appendButton($button);
-
- if (count($this->records) > 1)
- {
- $arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
- $button = (new BasicButton('user-mfa-choose-another'))
- ->text('COM_USERS_MFA_USE_DIFFERENT_METHOD')
- ->icon('icon-' . $arrow);
- $bar->appendButton($button);
- }
- }
-
- // Display the view
- parent::display($tpl);
- }
+ use SiteTemplateTrait;
+
+ /**
+ * The MFA Method records for the current user which correspond to enabled plugins
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ public $records = [];
+
+ /**
+ * The currently selected MFA Method record against which we'll be authenticating
+ *
+ * @var null|stdClass
+ * @since 4.2.0
+ */
+ public $record = null;
+
+ /**
+ * The Captive MFA page's rendering options
+ *
+ * @var array|null
+ * @since 4.2.0
+ */
+ public $renderOptions = null;
+
+ /**
+ * The title to display at the top of the page
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ public $title = '';
+
+ /**
+ * Is this an administrator page?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $isAdmin = false;
+
+ /**
+ * Does the currently selected Method allow authenticating against all of its records?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $allowEntryBatching = false;
+
+ /**
+ * All enabled MFA Methods (plugins)
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ public $mfaMethods;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void A string if successful, otherwise an Error object.
+ *
+ * @throws Exception
+ * @since 4.2.0
+ */
+ public function display($tpl = null)
+ {
+ $this->setSiteTemplateStyle();
+
+ $app = Factory::getApplication();
+ $user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+
+ PluginHelper::importPlugin('multifactorauth');
+ $event = new BeforeDisplayMethods($user);
+ $app->getDispatcher()->dispatch($event->getName(), $event);
+
+ /** @var CaptiveModel $model */
+ $model = $this->getModel();
+
+ // Load data from the model
+ $this->isAdmin = $app->isClient('administrator');
+ $this->records = $this->get('records');
+ $this->record = $this->get('record');
+ $this->mfaMethods = MfaHelper::getMfaMethods();
+
+ if (!empty($this->records)) {
+ /** @var BackupcodesModel $codesModel */
+ $codesModel = $this->getModel('Backupcodes');
+ $backupCodesRecord = $codesModel->getBackupCodesRecord();
+
+ if (!is_null($backupCodesRecord)) {
+ $backupCodesRecord->title = Text::_('COM_USERS_USER_BACKUPCODES');
+ $this->records[] = $backupCodesRecord;
+ }
+ }
+
+ // If we only have one record there's no point asking the user to select a MFA Method
+ if (empty($this->record) && !empty($this->records)) {
+ // Default to the first record
+ $this->record = reset($this->records);
+
+ // If we have multiple records try to make this record the default
+ if (count($this->records) > 1) {
+ foreach ($this->records as $record) {
+ if ($record->default) {
+ $this->record = $record;
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Set the correct layout based on the availability of a MFA record
+ $this->setLayout('default');
+
+ // If we have no record selected or explicitly asked to run the 'select' task use the correct layout
+ if (is_null($this->record) || ($model->getState('task') == 'select')) {
+ $this->setLayout('select');
+ }
+
+ switch ($this->getLayout()) {
+ case 'select':
+ $this->allowEntryBatching = 1;
+
+ $event = new NotifyActionLog('onComUsersCaptiveShowSelect', []);
+ Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
+ break;
+
+ case 'default':
+ default:
+ $this->renderOptions = $model->loadCaptiveRenderOptions($this->record);
+ $this->allowEntryBatching = $this->renderOptions['allowEntryBatching'] ?? 0;
+
+ $event = new NotifyActionLog(
+ 'onComUsersCaptiveShowCaptive',
+ [
+ $this->escape($this->record->title),
+ ]
+ );
+ Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
+ break;
+ }
+
+ // Which title should I use for the page?
+ $this->title = $this->get('PageTitle');
+
+ // Back-end: always show a title in the 'title' module position, not in the page body
+ if ($this->isAdmin) {
+ ToolbarHelper::title(Text::_('COM_USERS_HEADING_MFA'), 'users user-lock');
+ $this->title = '';
+ }
+
+ if ($this->isAdmin && $this->getLayout() === 'default') {
+ $bar = Toolbar::getInstance();
+ $button = (new BasicButton('user-mfa-submit'))
+ ->text($this->renderOptions['submit_text'])
+ ->icon($this->renderOptions['submit_icon']);
+ $bar->appendButton($button);
+
+ $button = (new BasicButton('user-mfa-logout'))
+ ->text('COM_USERS_MFA_LOGOUT')
+ ->buttonClass('btn btn-danger')
+ ->icon('icon icon-lock');
+ $bar->appendButton($button);
+
+ if (count($this->records) > 1) {
+ $arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
+ $button = (new BasicButton('user-mfa-choose-another'))
+ ->text('COM_USERS_MFA_USE_DIFFERENT_METHOD')
+ ->icon('icon-' . $arrow);
+ $bar->appendButton($button);
+ }
+ }
+
+ // Display the view
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_users/src/View/Debuggroup/HtmlView.php b/administrator/components/com_users/src/View/Debuggroup/HtmlView.php
index 77dc0693c26fc..6b4c2d8b43002 100644
--- a/administrator/components/com_users/src/View/Debuggroup/HtmlView.php
+++ b/administrator/components/com_users/src/View/Debuggroup/HtmlView.php
@@ -1,4 +1,5 @@
getCurrentUser()->authorise('core.manage', 'com_users'))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $this->actions = $this->get('DebugActions');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->group = $this->get('Group');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_GROUP_TITLE', $this->group->id, $this->escape($this->group->title)), 'users groups');
- ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_users');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Permissions_for_Group');
- }
+ /**
+ * List of component actions
+ *
+ * @var array
+ */
+ protected $actions;
+
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * The id and title for the user group.
+ *
+ * @var \stdClass
+ * @since 4.0.0
+ */
+ protected $group;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ // Access check.
+ if (!$this->getCurrentUser()->authorise('core.manage', 'com_users')) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $this->actions = $this->get('DebugActions');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->group = $this->get('Group');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_GROUP_TITLE', $this->group->id, $this->escape($this->group->title)), 'users groups');
+ ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_users');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Permissions_for_Group');
+ }
}
diff --git a/administrator/components/com_users/src/View/Debuguser/HtmlView.php b/administrator/components/com_users/src/View/Debuguser/HtmlView.php
index 7d2c463949056..aca918d8a6b5a 100644
--- a/administrator/components/com_users/src/View/Debuguser/HtmlView.php
+++ b/administrator/components/com_users/src/View/Debuguser/HtmlView.php
@@ -1,4 +1,5 @@
getCurrentUser()->authorise('core.manage', 'com_users'))
- {
- throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
- }
-
- $this->actions = $this->get('DebugActions');
- $this->items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->user = $this->get('User');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_USER_TITLE', $this->user->id, $this->escape($this->user->name)), 'users user');
- ToolbarHelper::cancel('user.cancel', 'JTOOLBAR_CLOSE');
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_users');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Permissions_for_User');
- }
+ /**
+ * List of component actions
+ *
+ * @var array
+ */
+ protected $actions;
+
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * The user object of the user being debugged.
+ *
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ // Access check.
+ if (!$this->getCurrentUser()->authorise('core.manage', 'com_users')) {
+ throw new NotAllowed(Text::_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+
+ $this->actions = $this->get('DebugActions');
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->user = $this->get('User');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::sprintf('COM_USERS_VIEW_DEBUG_USER_TITLE', $this->user->id, $this->escape($this->user->name)), 'users user');
+ ToolbarHelper::cancel('user.cancel', 'JTOOLBAR_CLOSE');
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_users');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Permissions_for_User');
+ }
}
diff --git a/administrator/components/com_users/src/View/Group/HtmlView.php b/administrator/components/com_users/src/View/Group/HtmlView.php
index ab9e05db06366..f1454eb75cef4 100644
--- a/administrator/components/com_users/src/View/Group/HtmlView.php
+++ b/administrator/components/com_users/src/View/Group/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->item = $this->get('Item');
- $this->form = $this->get('Form');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $isNew = ($this->item->id == 0);
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_GROUP_TITLE' : 'COM_USERS_VIEW_EDIT_GROUP_TITLE'), 'users-cog groups-add');
-
- $toolbarButtons = [];
-
- if ($canDo->get('core.edit') || $canDo->get('core.create'))
- {
- ToolbarHelper::apply('group.apply');
- $toolbarButtons[] = ['save', 'group.save'];
- }
-
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'group.save2new'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'group.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('group.cancel');
- }
- else
- {
- ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Users:_New_or_Edit_Group');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $item;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->item = $this->get('Item');
+ $this->form = $this->get('Form');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $isNew = ($this->item->id == 0);
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_GROUP_TITLE' : 'COM_USERS_VIEW_EDIT_GROUP_TITLE'), 'users-cog groups-add');
+
+ $toolbarButtons = [];
+
+ if ($canDo->get('core.edit') || $canDo->get('core.create')) {
+ ToolbarHelper::apply('group.apply');
+ $toolbarButtons[] = ['save', 'group.save'];
+ }
+
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'group.save2new'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'group.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('group.cancel');
+ } else {
+ ToolbarHelper::cancel('group.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Users:_New_or_Edit_Group');
+ }
}
diff --git a/administrator/components/com_users/src/View/Groups/HtmlView.php b/administrator/components/com_users/src/View/Groups/HtmlView.php
index fe17b424c0bc9..d670ee1419cfd 100644
--- a/administrator/components/com_users/src/View/Groups/HtmlView.php
+++ b/administrator/components/com_users/src/View/Groups/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::_('COM_USERS_VIEW_GROUPS_TITLE'), 'users-cog groups');
-
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::addNew('group.add');
- }
-
- if ($canDo->get('core.delete'))
- {
- ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'groups.delete', 'JTOOLBAR_DELETE');
- ToolbarHelper::divider();
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_users');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Users:_Groups');
- }
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::_('COM_USERS_VIEW_GROUPS_TITLE'), 'users-cog groups');
+
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::addNew('group.add');
+ }
+
+ if ($canDo->get('core.delete')) {
+ ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'groups.delete', 'JTOOLBAR_DELETE');
+ ToolbarHelper::divider();
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_users');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Users:_Groups');
+ }
}
diff --git a/administrator/components/com_users/src/View/Level/HtmlView.php b/administrator/components/com_users/src/View/Level/HtmlView.php
index 8d53c2c7d2ae3..1da02db23346b 100644
--- a/administrator/components/com_users/src/View/Level/HtmlView.php
+++ b/administrator/components/com_users/src/View/Level/HtmlView.php
@@ -1,4 +1,5 @@
form = $this->get('Form');
- $this->item = $this->get('Item');
- $this->state = $this->get('State');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $isNew = ($this->item->id == 0);
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_LEVEL_TITLE' : 'COM_USERS_VIEW_EDIT_LEVEL_TITLE'), 'user-lock levels-add');
-
- $toolbarButtons = [];
-
- if ($canDo->get('core.edit') || $canDo->get('core.create'))
- {
- ToolbarHelper::apply('level.apply');
- $toolbarButtons[] = ['save', 'level.save'];
- }
-
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'level.save2new'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && $canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2copy', 'level.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('level.cancel');
- }
- else
- {
- ToolbarHelper::cancel('level.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Users:_Edit_Viewing_Access_Level');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $item;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+ $this->state = $this->get('State');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $isNew = ($this->item->id == 0);
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::_($isNew ? 'COM_USERS_VIEW_NEW_LEVEL_TITLE' : 'COM_USERS_VIEW_EDIT_LEVEL_TITLE'), 'user-lock levels-add');
+
+ $toolbarButtons = [];
+
+ if ($canDo->get('core.edit') || $canDo->get('core.create')) {
+ ToolbarHelper::apply('level.apply');
+ $toolbarButtons[] = ['save', 'level.save'];
+ }
+
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'level.save2new'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && $canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2copy', 'level.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('level.cancel');
+ } else {
+ ToolbarHelper::cancel('level.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Users:_Edit_Viewing_Access_Level');
+ }
}
diff --git a/administrator/components/com_users/src/View/Levels/HtmlView.php b/administrator/components/com_users/src/View/Levels/HtmlView.php
index 6c4429c6c64a7..93427db1bbaa8 100644
--- a/administrator/components/com_users/src/View/Levels/HtmlView.php
+++ b/administrator/components/com_users/src/View/Levels/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_users');
-
- ToolbarHelper::title(Text::_('COM_USERS_VIEW_LEVELS_TITLE'), 'user-lock levels');
-
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::addNew('level.add');
- }
-
- if ($canDo->get('core.delete'))
- {
- ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'level.delete', 'JTOOLBAR_DELETE');
- ToolbarHelper::divider();
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- ToolbarHelper::preferences('com_users');
- ToolbarHelper::divider();
- }
-
- ToolbarHelper::help('Users:_Viewing_Access_Levels');
- }
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_users');
+
+ ToolbarHelper::title(Text::_('COM_USERS_VIEW_LEVELS_TITLE'), 'user-lock levels');
+
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::addNew('level.add');
+ }
+
+ if ($canDo->get('core.delete')) {
+ ToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', 'level.delete', 'JTOOLBAR_DELETE');
+ ToolbarHelper::divider();
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ ToolbarHelper::preferences('com_users');
+ ToolbarHelper::divider();
+ }
+
+ ToolbarHelper::help('Users:_Viewing_Access_Levels');
+ }
}
diff --git a/administrator/components/com_users/src/View/Mail/HtmlView.php b/administrator/components/com_users/src/View/Mail/HtmlView.php
index beb275ee904b6..cdb336d463f7c 100644
--- a/administrator/components/com_users/src/View/Mail/HtmlView.php
+++ b/administrator/components/com_users/src/View/Mail/HtmlView.php
@@ -1,4 +1,5 @@
get('massmailoff', 0) == 1)
- {
- Factory::getApplication()->redirect(Route::_('index.php', false));
- }
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ // Redirect to admin index if mass mailer disabled in conf
+ if (Factory::getApplication()->get('massmailoff', 0) == 1) {
+ Factory::getApplication()->redirect(Route::_('index.php', false));
+ }
- // Get data from the model
- $this->form = $this->get('Form');
+ // Get data from the model
+ $this->form = $this->get('Form');
- $this->addToolbar();
- parent::display($tpl);
- }
+ $this->addToolbar();
+ parent::display($tpl);
+ }
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
- ToolbarHelper::title(Text::_('COM_USERS_MASS_MAIL'), 'users massmail');
- ToolbarHelper::custom('mail.send', 'envelope', '', 'COM_USERS_TOOLBAR_MAIL_SEND_MAIL', false);
- ToolbarHelper::cancel('mail.cancel');
- ToolbarHelper::divider();
- ToolbarHelper::preferences('com_users');
- ToolbarHelper::divider();
- ToolbarHelper::help('Mass_Mail_Users');
- }
+ ToolbarHelper::title(Text::_('COM_USERS_MASS_MAIL'), 'users massmail');
+ ToolbarHelper::custom('mail.send', 'envelope', '', 'COM_USERS_TOOLBAR_MAIL_SEND_MAIL', false);
+ ToolbarHelper::cancel('mail.cancel');
+ ToolbarHelper::divider();
+ ToolbarHelper::preferences('com_users');
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Mass_Mail_Users');
+ }
}
diff --git a/administrator/components/com_users/src/View/Method/HtmlView.php b/administrator/components/com_users/src/View/Method/HtmlView.php
index 450c7f19f1c79..bbdaf82728016 100644
--- a/administrator/components/com_users/src/View/Method/HtmlView.php
+++ b/administrator/components/com_users/src/View/Method/HtmlView.php
@@ -1,4 +1,5 @@
user))
- {
- $this->user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- /** @var MethodModel $model */
- $model = $this->getModel();
- $this->setLayout('edit');
- $this->renderOptions = $model->getRenderOptions($this->user);
- $this->record = $model->getRecord($this->user);
- $this->title = $model->getPageTitle();
- $this->isAdmin = $app->isClient('administrator');
-
- // Backup codes are a special case, rendered with a special layout
- if ($this->record->method == 'backupcodes')
- {
- $this->setLayout('backupcodes');
-
- $backupCodes = $this->record->options;
-
- if (!is_array($backupCodes))
- {
- $backupCodes = [];
- }
-
- $backupCodes = array_filter(
- $backupCodes,
- function ($x) {
- return !empty($x);
- }
- );
-
- if (count($backupCodes) % 2 != 0)
- {
- $backupCodes[] = '';
- }
-
- /**
- * The call to array_merge resets the array indices. This is necessary since array_filter kept the indices,
- * meaning our elements are completely out of order.
- */
- $this->backupCodes = array_merge($backupCodes);
- }
-
- // Set up the isEditExisting property.
- $this->isEditExisting = !empty($this->record->id);
-
- // Back-end: always show a title in the 'title' module position, not in the page body
- if ($this->isAdmin)
- {
- ToolbarHelper::title($this->title, 'users user-lock');
-
- $helpUrl = $this->renderOptions['help_url'];
-
- if (!empty($helpUrl))
- {
- ToolbarHelper::help('', false, $helpUrl);
- }
-
- $this->title = '';
- }
-
- $returnUrl = empty($this->returnURL) ? '' : base64_decode($this->returnURL);
- $returnUrl = $returnUrl ?: Route::_('index.php?option=com_users&task=methods.display&user_id=' . $this->user->id);
-
- if ($this->isAdmin && $this->getLayout() === 'edit')
- {
- $bar = Toolbar::getInstance();
- $button = (new BasicButton('user-mfa-edit-save'))
- ->text($this->renderOptions['submit_text'])
- ->icon($this->renderOptions['submit_icon'])
- ->onclick('document.getElementById(\'user-mfa-edit-save\').click()');
-
- if ($this->renderOptions['show_submit'] || $this->isEditExisting)
- {
- $bar->appendButton($button);
- }
-
- $button = (new LinkButton('user-mfa-edit-cancel'))
- ->text('JCANCEL')
- ->buttonClass('btn btn-danger')
- ->icon('icon-cancel-2')
- ->url($returnUrl);
- $bar->appendButton($button);
- }
- elseif ($this->isAdmin && $this->getLayout() === 'backupcodes')
- {
- $bar = Toolbar::getInstance();
-
- $arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
- $button = (new LinkButton('user-mfa-edit-cancel'))
- ->text('JTOOLBAR_BACK')
- ->icon('icon-' . $arrow)
- ->url($returnUrl);
- $bar->appendButton($button);
-
- $button = (new LinkButton('user-mfa-edit-cancel'))
- ->text('COM_USERS_MFA_BACKUPCODES_RESET')
- ->buttonClass('btn btn-danger')
- ->icon('icon-refresh')
- ->url(
- Route::_(
- sprintf(
- "index.php?option=com_users&task=method.regenerateBackupCodes&user_id=%s&%s=1&returnurl=%s",
- $this->user->id,
- Factory::getApplication()->getFormToken(),
- base64_encode($returnUrl)
- )
- )
- );
- $bar->appendButton($button);
- }
-
- // Display the view
- parent::display($tpl);
- }
+ /**
+ * Is this an administrator page?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $isAdmin = false;
+
+ /**
+ * The editor page render options
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ public $renderOptions = [];
+
+ /**
+ * The MFA Method record being edited
+ *
+ * @var object
+ * @since 4.2.0
+ */
+ public $record = null;
+
+ /**
+ * The title text for this page
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ public $title = '';
+
+ /**
+ * The return URL to use for all links and forms
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ public $returnURL = null;
+
+ /**
+ * The user object used to display this page
+ *
+ * @var User
+ * @since 4.2.0
+ */
+ public $user = null;
+
+ /**
+ * The backup codes for the current user. Only applies when the backup codes record is being "edited"
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ public $backupCodes = [];
+
+ /**
+ * Am I editing an existing Method? If it's false then I'm adding a new Method.
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $isEditExisting = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ * @see \JViewLegacy::loadTemplate()
+ * @since 4.2.0
+ */
+ public function display($tpl = null): void
+ {
+ $app = Factory::getApplication();
+
+ if (empty($this->user)) {
+ $this->user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ /** @var MethodModel $model */
+ $model = $this->getModel();
+ $this->setLayout('edit');
+ $this->renderOptions = $model->getRenderOptions($this->user);
+ $this->record = $model->getRecord($this->user);
+ $this->title = $model->getPageTitle();
+ $this->isAdmin = $app->isClient('administrator');
+
+ // Backup codes are a special case, rendered with a special layout
+ if ($this->record->method == 'backupcodes') {
+ $this->setLayout('backupcodes');
+
+ $backupCodes = $this->record->options;
+
+ if (!is_array($backupCodes)) {
+ $backupCodes = [];
+ }
+
+ $backupCodes = array_filter(
+ $backupCodes,
+ function ($x) {
+ return !empty($x);
+ }
+ );
+
+ if (count($backupCodes) % 2 != 0) {
+ $backupCodes[] = '';
+ }
+
+ /**
+ * The call to array_merge resets the array indices. This is necessary since array_filter kept the indices,
+ * meaning our elements are completely out of order.
+ */
+ $this->backupCodes = array_merge($backupCodes);
+ }
+
+ // Set up the isEditExisting property.
+ $this->isEditExisting = !empty($this->record->id);
+
+ // Back-end: always show a title in the 'title' module position, not in the page body
+ if ($this->isAdmin) {
+ ToolbarHelper::title($this->title, 'users user-lock');
+
+ $helpUrl = $this->renderOptions['help_url'];
+
+ if (!empty($helpUrl)) {
+ ToolbarHelper::help('', false, $helpUrl);
+ }
+
+ $this->title = '';
+ }
+
+ $returnUrl = empty($this->returnURL) ? '' : base64_decode($this->returnURL);
+ $returnUrl = $returnUrl ?: Route::_('index.php?option=com_users&task=methods.display&user_id=' . $this->user->id);
+
+ if ($this->isAdmin && $this->getLayout() === 'edit') {
+ $bar = Toolbar::getInstance();
+ $button = (new BasicButton('user-mfa-edit-save'))
+ ->text($this->renderOptions['submit_text'])
+ ->icon($this->renderOptions['submit_icon'])
+ ->onclick('document.getElementById(\'user-mfa-edit-save\').click()');
+
+ if ($this->renderOptions['show_submit'] || $this->isEditExisting) {
+ $bar->appendButton($button);
+ }
+
+ $button = (new LinkButton('user-mfa-edit-cancel'))
+ ->text('JCANCEL')
+ ->buttonClass('btn btn-danger')
+ ->icon('icon-cancel-2')
+ ->url($returnUrl);
+ $bar->appendButton($button);
+ } elseif ($this->isAdmin && $this->getLayout() === 'backupcodes') {
+ $bar = Toolbar::getInstance();
+
+ $arrow = Factory::getApplication()->getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
+ $button = (new LinkButton('user-mfa-edit-cancel'))
+ ->text('JTOOLBAR_BACK')
+ ->icon('icon-' . $arrow)
+ ->url($returnUrl);
+ $bar->appendButton($button);
+
+ $button = (new LinkButton('user-mfa-edit-cancel'))
+ ->text('COM_USERS_MFA_BACKUPCODES_RESET')
+ ->buttonClass('btn btn-danger')
+ ->icon('icon-refresh')
+ ->url(
+ Route::_(
+ sprintf(
+ "index.php?option=com_users&task=method.regenerateBackupCodes&user_id=%s&%s=1&returnurl=%s",
+ $this->user->id,
+ Factory::getApplication()->getFormToken(),
+ base64_encode($returnUrl)
+ )
+ )
+ );
+ $bar->appendButton($button);
+ }
+
+ // Display the view
+ parent::display($tpl);
+ }
}
diff --git a/administrator/components/com_users/src/View/Methods/HtmlView.php b/administrator/components/com_users/src/View/Methods/HtmlView.php
index 6a2b19de241f0..e8156398acddd 100644
--- a/administrator/components/com_users/src/View/Methods/HtmlView.php
+++ b/administrator/components/com_users/src/View/Methods/HtmlView.php
@@ -1,4 +1,5 @@
setSiteTemplateStyle();
-
- $app = Factory::getApplication();
-
- if (empty($this->user))
- {
- $this->user = Factory::getApplication()->getIdentity()
- ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
- }
-
- /** @var MethodsModel $model */
- $model = $this->getModel();
-
- if ($this->getLayout() !== 'firsttime')
- {
- $this->setLayout('default');
- }
-
- $this->methods = $model->getMethods($this->user);
- $this->isAdmin = $app->isClient('administrator');
- $activeRecords = 0;
-
- foreach ($this->methods as $methodName => $method)
- {
- $methodActiveRecords = count($method['active']);
-
- if (!$methodActiveRecords)
- {
- continue;
- }
-
- $activeRecords += $methodActiveRecords;
- $this->mfaActive = true;
-
- foreach ($method['active'] as $record)
- {
- if ($record->default)
- {
- $this->defaultMethod = $methodName;
-
- break;
- }
- }
- }
-
- // If there are no backup codes yet we should create new ones
- /** @var BackupcodesModel $model */
- $model = $this->getModel('backupcodes');
- $backupCodes = $model->getBackupCodes($this->user);
-
- if ($activeRecords && empty($backupCodes))
- {
- $model->regenerateBackupCodes($this->user);
- }
-
- $backupCodesRecord = $model->getBackupCodesRecord($this->user);
-
- if (!is_null($backupCodesRecord))
- {
- $this->methods = array_merge(
- [
- 'backupcodes' => new MethodDescriptor(
- [
- 'name' => 'backupcodes',
- 'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
- 'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
- 'image' => 'media/com_users/images/emergency.svg',
- 'canDisable' => false,
- 'active' => [$backupCodesRecord],
- ]
- )
- ],
- $this->methods
- );
- }
-
- $this->isMandatoryMFASetup = $activeRecords === 0 && $app->getSession()->get('com_users.mandatory_mfa_setup', 0) === 1;
-
- // Back-end: always show a title in the 'title' module position, not in the page body
- if ($this->isAdmin)
- {
- ToolbarHelper::title(Text::_('COM_USERS_MFA_LIST_PAGE_HEAD'), 'users user-lock');
-
- if (Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_users'))
- {
- ToolbarHelper::back('JTOOLBAR_BACK', Route::_('index.php?option=com_users'));
- }
- }
-
- // Display the view
- parent::display($tpl);
-
- $event = new NotifyActionLog('onComUsersViewMethodsAfterDisplay', [$this]);
- Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
-
- Text::script('JGLOBAL_CONFIRM_DELETE');
- }
+ use SiteTemplateTrait;
+
+ /**
+ * Is this an administrator page?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $isAdmin = false;
+
+ /**
+ * The MFA Methods available for this user
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ public $methods = [];
+
+ /**
+ * The return URL to use for all links and forms
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ public $returnURL = null;
+
+ /**
+ * Are there any active MFA Methods at all?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $mfaActive = false;
+
+ /**
+ * Which Method has the default record?
+ *
+ * @var string
+ * @since 4.2.0
+ */
+ public $defaultMethod = '';
+
+ /**
+ * The user object used to display this page
+ *
+ * @var User
+ * @since 4.2.0
+ */
+ public $user = null;
+
+ /**
+ * Is this page part of the mandatory Multi-factor Authentication setup?
+ *
+ * @var boolean
+ * @since 4.2.0
+ */
+ public $isMandatoryMFASetup = false;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @throws \Exception
+ * @see \JViewLegacy::loadTemplate()
+ * @since 4.2.0
+ */
+ public function display($tpl = null): void
+ {
+ $this->setSiteTemplateStyle();
+
+ $app = Factory::getApplication();
+
+ if (empty($this->user)) {
+ $this->user = Factory::getApplication()->getIdentity()
+ ?: Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById(0);
+ }
+
+ /** @var MethodsModel $model */
+ $model = $this->getModel();
+
+ if ($this->getLayout() !== 'firsttime') {
+ $this->setLayout('default');
+ }
+
+ $this->methods = $model->getMethods($this->user);
+ $this->isAdmin = $app->isClient('administrator');
+ $activeRecords = 0;
+
+ foreach ($this->methods as $methodName => $method) {
+ $methodActiveRecords = count($method['active']);
+
+ if (!$methodActiveRecords) {
+ continue;
+ }
+
+ $activeRecords += $methodActiveRecords;
+ $this->mfaActive = true;
+
+ foreach ($method['active'] as $record) {
+ if ($record->default) {
+ $this->defaultMethod = $methodName;
+
+ break;
+ }
+ }
+ }
+
+ // If there are no backup codes yet we should create new ones
+ /** @var BackupcodesModel $model */
+ $model = $this->getModel('backupcodes');
+ $backupCodes = $model->getBackupCodes($this->user);
+
+ if ($activeRecords && empty($backupCodes)) {
+ $model->regenerateBackupCodes($this->user);
+ }
+
+ $backupCodesRecord = $model->getBackupCodesRecord($this->user);
+
+ if (!is_null($backupCodesRecord)) {
+ $this->methods = array_merge(
+ [
+ 'backupcodes' => new MethodDescriptor(
+ [
+ 'name' => 'backupcodes',
+ 'display' => Text::_('COM_USERS_USER_BACKUPCODES'),
+ 'shortinfo' => Text::_('COM_USERS_USER_BACKUPCODES_DESC'),
+ 'image' => 'media/com_users/images/emergency.svg',
+ 'canDisable' => false,
+ 'active' => [$backupCodesRecord],
+ ]
+ )
+ ],
+ $this->methods
+ );
+ }
+
+ $this->isMandatoryMFASetup = $activeRecords === 0 && $app->getSession()->get('com_users.mandatory_mfa_setup', 0) === 1;
+
+ // Back-end: always show a title in the 'title' module position, not in the page body
+ if ($this->isAdmin) {
+ ToolbarHelper::title(Text::_('COM_USERS_MFA_LIST_PAGE_HEAD'), 'users user-lock');
+
+ if (Factory::getApplication()->getIdentity()->authorise('core.manage', 'com_users')) {
+ ToolbarHelper::back('JTOOLBAR_BACK', Route::_('index.php?option=com_users'));
+ }
+ }
+
+ // Display the view
+ parent::display($tpl);
+
+ $event = new NotifyActionLog('onComUsersViewMethodsAfterDisplay', [$this]);
+ Factory::getApplication()->getDispatcher()->dispatch($event->getName(), $event);
+
+ Text::script('JGLOBAL_CONFIRM_DELETE');
+ }
}
diff --git a/administrator/components/com_users/src/View/Note/HtmlView.php b/administrator/components/com_users/src/View/Note/HtmlView.php
index 48ac938246924..5181cb441f23f 100644
--- a/administrator/components/com_users/src/View/Note/HtmlView.php
+++ b/administrator/components/com_users/src/View/Note/HtmlView.php
@@ -1,4 +1,5 @@
state = $this->get('State');
- $this->item = $this->get('Item');
- $this->form = $this->get('Form');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- parent::display($tpl);
- $this->addToolbar();
- }
-
- /**
- * Display the toolbar.
- *
- * @return void
- *
- * @since 2.5
- * @throws \Exception
- */
- protected function addToolbar()
- {
- $input = Factory::getApplication()->input;
- $input->set('hidemainmenu', 1);
-
- $user = $this->getCurrentUser();
- $isNew = ($this->item->id == 0);
- $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
-
- // Since we don't track these assets at the item level, use the category id.
- $canDo = ContentHelper::getActions('com_users', 'category', $this->item->catid);
-
- ToolbarHelper::title(Text::_('COM_USERS_NOTES'), 'users user');
-
- $toolbarButtons = [];
-
- // If not checked out, can save the item.
- if (!$checkedOut && ($canDo->get('core.edit') || count($user->getAuthorisedCategories('com_users', 'core.create'))))
- {
- ToolbarHelper::apply('note.apply');
- $toolbarButtons[] = ['save', 'note.save'];
- }
-
- if (!$checkedOut && count($user->getAuthorisedCategories('com_users', 'core.create')))
- {
- $toolbarButtons[] = ['save2new', 'note.save2new'];
- }
-
- // If an existing item, can save to a copy.
- if (!$isNew && (count($user->getAuthorisedCategories('com_users', 'core.create')) > 0))
- {
- $toolbarButtons[] = ['save2copy', 'note.save2copy'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('note.cancel');
- }
- else
- {
- ToolbarHelper::cancel('note.cancel', 'JTOOLBAR_CLOSE');
-
- if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit'))
- {
- ToolbarHelper::versions('com_users.note', $this->item->id);
- }
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('User_Notes:_New_or_Edit');
- }
+ /**
+ * The edit form.
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 2.5
+ */
+ protected $form;
+
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 2.5
+ */
+ protected $item;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 2.5
+ */
+ protected $state;
+
+ /**
+ * Override the display method for the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 2.5
+ * @throws \Exception
+ */
+ public function display($tpl = null)
+ {
+ // Initialise view variables.
+ $this->state = $this->get('State');
+ $this->item = $this->get('Item');
+ $this->form = $this->get('Form');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ parent::display($tpl);
+ $this->addToolbar();
+ }
+
+ /**
+ * Display the toolbar.
+ *
+ * @return void
+ *
+ * @since 2.5
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ $input = Factory::getApplication()->input;
+ $input->set('hidemainmenu', 1);
+
+ $user = $this->getCurrentUser();
+ $isNew = ($this->item->id == 0);
+ $checkedOut = !(is_null($this->item->checked_out) || $this->item->checked_out == $user->get('id'));
+
+ // Since we don't track these assets at the item level, use the category id.
+ $canDo = ContentHelper::getActions('com_users', 'category', $this->item->catid);
+
+ ToolbarHelper::title(Text::_('COM_USERS_NOTES'), 'users user');
+
+ $toolbarButtons = [];
+
+ // If not checked out, can save the item.
+ if (!$checkedOut && ($canDo->get('core.edit') || count($user->getAuthorisedCategories('com_users', 'core.create')))) {
+ ToolbarHelper::apply('note.apply');
+ $toolbarButtons[] = ['save', 'note.save'];
+ }
+
+ if (!$checkedOut && count($user->getAuthorisedCategories('com_users', 'core.create'))) {
+ $toolbarButtons[] = ['save2new', 'note.save2new'];
+ }
+
+ // If an existing item, can save to a copy.
+ if (!$isNew && (count($user->getAuthorisedCategories('com_users', 'core.create')) > 0)) {
+ $toolbarButtons[] = ['save2copy', 'note.save2copy'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('note.cancel');
+ } else {
+ ToolbarHelper::cancel('note.cancel', 'JTOOLBAR_CLOSE');
+
+ if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $canDo->get('core.edit')) {
+ ToolbarHelper::versions('com_users.note', $this->item->id);
+ }
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('User_Notes:_New_or_Edit');
+ }
}
diff --git a/administrator/components/com_users/src/View/Notes/HtmlView.php b/administrator/components/com_users/src/View/Notes/HtmlView.php
index a3c7c490c721d..c0d640864e10a 100644
--- a/administrator/components/com_users/src/View/Notes/HtmlView.php
+++ b/administrator/components/com_users/src/View/Notes/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->user = $this->get('User');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState'))
- {
- $this->setLayout('emptystate');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Turn parameters into registry objects
- foreach ($this->items as $item)
- {
- $item->cparams = new Registry($item->category_params);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Display the toolbar.
- *
- * @return void
- *
- * @since 2.5
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions('com_users', 'category', $this->state->get('filter.category_id'));
-
- ToolbarHelper::title(Text::_('COM_USERS_VIEW_NOTES_TITLE'), 'users user');
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('note.add');
- }
-
- if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin')))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- if ($canDo->get('core.edit.state'))
- {
- $childBar->publish('notes.publish')->listCheck(true);
- $childBar->unpublish('notes.unpublish')->listCheck(true);
- $childBar->archive('notes.archive')->listCheck(true);
- $childBar->checkin('notes.checkin')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') != -2 && $canDo->get('core.edit.state'))
- {
- $childBar->trash('notes.trash');
- }
- }
-
- if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete'))
- {
- $toolbar->delete('notes.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_users');
- }
-
- $toolbar->help('User_Notes');
- }
+ /**
+ * A list of user note objects.
+ *
+ * @var array
+ * @since 2.5
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 2.5
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 2.5
+ */
+ protected $state;
+
+ /**
+ * The model state.
+ *
+ * @var User
+ * @since 2.5
+ */
+ protected $user;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * Is this view an Empty State
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $isEmptyState = false;
+
+ /**
+ * Override the display method for the view.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ public function display($tpl = null)
+ {
+ // Initialise view variables.
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->user = $this->get('User');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
+ $this->setLayout('emptystate');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Turn parameters into registry objects
+ foreach ($this->items as $item) {
+ $item->cparams = new Registry($item->category_params);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Display the toolbar.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions('com_users', 'category', $this->state->get('filter.category_id'));
+
+ ToolbarHelper::title(Text::_('COM_USERS_VIEW_NOTES_TITLE'), 'users user');
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('note.add');
+ }
+
+ if (!$this->isEmptyState && ($canDo->get('core.edit.state') || $canDo->get('core.admin'))) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ if ($canDo->get('core.edit.state')) {
+ $childBar->publish('notes.publish')->listCheck(true);
+ $childBar->unpublish('notes.unpublish')->listCheck(true);
+ $childBar->archive('notes.archive')->listCheck(true);
+ $childBar->checkin('notes.checkin')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') != -2 && $canDo->get('core.edit.state')) {
+ $childBar->trash('notes.trash');
+ }
+ }
+
+ if (!$this->isEmptyState && $this->state->get('filter.published') == -2 && $canDo->get('core.delete')) {
+ $toolbar->delete('notes.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_users');
+ }
+
+ $toolbar->help('User_Notes');
+ }
}
diff --git a/administrator/components/com_users/src/View/SiteTemplateTrait.php b/administrator/components/com_users/src/View/SiteTemplateTrait.php
index 0683a6ec9fede..cd915b2424438 100644
--- a/administrator/components/com_users/src/View/SiteTemplateTrait.php
+++ b/administrator/components/com_users/src/View/SiteTemplateTrait.php
@@ -1,4 +1,5 @@
get('captive_template', '');
-
- if (empty($templateStyle) || !$app->isClient('site'))
- {
- return;
- }
+ /**
+ * Set a specific site template style in the frontend application
+ *
+ * @return void
+ * @throws Exception
+ * @since 4.2.0
+ */
+ private function setSiteTemplateStyle(): void
+ {
+ $app = Factory::getApplication();
+ $templateStyle = (int) ComponentHelper::getParams('com_users')->get('captive_template', '');
- $itemId = $app->input->get('Itemid');
+ if (empty($templateStyle) || !$app->isClient('site')) {
+ return;
+ }
- if (!empty($itemId))
- {
- return;
- }
+ $itemId = $app->input->get('Itemid');
- $app->input->set('templateStyle', $templateStyle);
+ if (!empty($itemId)) {
+ return;
+ }
- try
- {
- $refApp = new ReflectionObject($app);
- $refTemplate = $refApp->getProperty('template');
- $refTemplate->setAccessible(true);
- $refTemplate->setValue($app, null);
- }
- catch (ReflectionException $e)
- {
- return;
- }
+ $app->input->set('templateStyle', $templateStyle);
- $template = $app->getTemplate(true);
+ try {
+ $refApp = new ReflectionObject($app);
+ $refTemplate = $refApp->getProperty('template');
+ $refTemplate->setAccessible(true);
+ $refTemplate->setValue($app, null);
+ } catch (ReflectionException $e) {
+ return;
+ }
- $app->set('theme', $template->template);
- $app->set('themeParams', $template->params);
- }
+ $template = $app->getTemplate(true);
+ $app->set('theme', $template->template);
+ $app->set('themeParams', $template->params);
+ }
}
diff --git a/administrator/components/com_users/src/View/User/HtmlView.php b/administrator/components/com_users/src/View/User/HtmlView.php
index 38bc006953d2c..b2e57e893fe6c 100644
--- a/administrator/components/com_users/src/View/User/HtmlView.php
+++ b/administrator/components/com_users/src/View/User/HtmlView.php
@@ -1,4 +1,5 @@
item = $this->get('Item'))
- {
- $app = Factory::getApplication();
- $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_NOT_EXIST'), 'error');
- $app->redirect('index.php?option=com_users&view=users');
- }
-
- $this->form = $this->get('Form');
- $this->state = $this->get('State');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- // Prevent user from modifying own group(s)
- $user = Factory::getApplication()->getIdentity();
-
- if ((int) $user->id != (int) $this->item->id || $user->authorise('core.admin'))
- {
- $this->grouplist = $this->get('Groups');
- $this->groups = $this->get('AssignedGroups');
- }
-
- $this->form->setValue('password', null);
- $this->form->setValue('password2', null);
-
- /** @var User $userBeingEdited */
- $userBeingEdited = Factory::getContainer()
- ->get(UserFactoryInterface::class)
- ->loadUserById($this->item->id);
-
- if ($this->item->id > 0 && (int) $userBeingEdited->id == (int) $this->item->id)
- {
- try
- {
- $this->mfaConfigurationUI = Mfa::canShowConfigurationInterface($userBeingEdited)
- ? Mfa::getConfigurationInterface($userBeingEdited)
- : '';
- }
- catch (\Exception $e)
- {
- // In case something goes really wrong with the plugins; prevents hard breaks.
- $this->mfaConfigurationUI = null;
- }
- }
-
- parent::display($tpl);
-
- $this->addToolbar();
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = Factory::getApplication()->getIdentity();
- $canDo = ContentHelper::getActions('com_users');
- $isNew = ($this->item->id == 0);
- $isProfile = $this->item->id == $user->id;
-
- ToolbarHelper::title(
- Text::_(
- $isNew ? 'COM_USERS_VIEW_NEW_USER_TITLE' : ($isProfile ? 'COM_USERS_VIEW_EDIT_PROFILE_TITLE' : 'COM_USERS_VIEW_EDIT_USER_TITLE')
- ),
- 'user ' . ($isNew ? 'user-add' : ($isProfile ? 'user-profile' : 'user-edit'))
- );
-
- $toolbarButtons = [];
-
- if ($canDo->get('core.edit') || $canDo->get('core.create') || $isProfile)
- {
- ToolbarHelper::apply('user.apply');
- $toolbarButtons[] = ['save', 'user.save'];
- }
-
- if ($canDo->get('core.create') && $canDo->get('core.manage'))
- {
- $toolbarButtons[] = ['save2new', 'user.save2new'];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- if (empty($this->item->id))
- {
- ToolbarHelper::cancel('user.cancel');
- }
- else
- {
- ToolbarHelper::cancel('user.cancel', 'JTOOLBAR_CLOSE');
- }
-
- ToolbarHelper::divider();
- ToolbarHelper::help('Users:_Edit_Profile');
- }
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * Gets the available groups
+ *
+ * @var array
+ */
+ protected $grouplist;
+
+ /**
+ * The groups this user is assigned to
+ *
+ * @var array
+ * @since 1.6
+ */
+ protected $groups;
+
+ /**
+ * The model state
+ *
+ * @var CMSObject
+ */
+ protected $state;
+
+ /**
+ * The Multi-factor Authentication configuration interface for the user.
+ *
+ * @var string|null
+ * @since 4.2.0
+ */
+ protected $mfaConfigurationUI;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 1.5
+ */
+ public function display($tpl = null)
+ {
+ // If no item found, dont show the edit screen, redirect with message
+ if (false === $this->item = $this->get('Item')) {
+ $app = Factory::getApplication();
+ $app->enqueueMessage(Text::_('JLIB_APPLICATION_ERROR_NOT_EXIST'), 'error');
+ $app->redirect('index.php?option=com_users&view=users');
+ }
+
+ $this->form = $this->get('Form');
+ $this->state = $this->get('State');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ // Prevent user from modifying own group(s)
+ $user = Factory::getApplication()->getIdentity();
+
+ if ((int) $user->id != (int) $this->item->id || $user->authorise('core.admin')) {
+ $this->grouplist = $this->get('Groups');
+ $this->groups = $this->get('AssignedGroups');
+ }
+
+ $this->form->setValue('password', null);
+ $this->form->setValue('password2', null);
+
+ /** @var User $userBeingEdited */
+ $userBeingEdited = Factory::getContainer()
+ ->get(UserFactoryInterface::class)
+ ->loadUserById($this->item->id);
+
+ if ($this->item->id > 0 && (int) $userBeingEdited->id == (int) $this->item->id) {
+ try {
+ $this->mfaConfigurationUI = Mfa::canShowConfigurationInterface($userBeingEdited)
+ ? Mfa::getConfigurationInterface($userBeingEdited)
+ : '';
+ } catch (\Exception $e) {
+ // In case something goes really wrong with the plugins; prevents hard breaks.
+ $this->mfaConfigurationUI = null;
+ }
+ }
+
+ parent::display($tpl);
+
+ $this->addToolbar();
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = Factory::getApplication()->getIdentity();
+ $canDo = ContentHelper::getActions('com_users');
+ $isNew = ($this->item->id == 0);
+ $isProfile = $this->item->id == $user->id;
+
+ ToolbarHelper::title(
+ Text::_(
+ $isNew ? 'COM_USERS_VIEW_NEW_USER_TITLE' : ($isProfile ? 'COM_USERS_VIEW_EDIT_PROFILE_TITLE' : 'COM_USERS_VIEW_EDIT_USER_TITLE')
+ ),
+ 'user ' . ($isNew ? 'user-add' : ($isProfile ? 'user-profile' : 'user-edit'))
+ );
+
+ $toolbarButtons = [];
+
+ if ($canDo->get('core.edit') || $canDo->get('core.create') || $isProfile) {
+ ToolbarHelper::apply('user.apply');
+ $toolbarButtons[] = ['save', 'user.save'];
+ }
+
+ if ($canDo->get('core.create') && $canDo->get('core.manage')) {
+ $toolbarButtons[] = ['save2new', 'user.save2new'];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ if (empty($this->item->id)) {
+ ToolbarHelper::cancel('user.cancel');
+ } else {
+ ToolbarHelper::cancel('user.cancel', 'JTOOLBAR_CLOSE');
+ }
+
+ ToolbarHelper::divider();
+ ToolbarHelper::help('Users:_Edit_Profile');
+ }
}
diff --git a/administrator/components/com_users/src/View/Users/HtmlView.php b/administrator/components/com_users/src/View/Users/HtmlView.php
index ee56bdc8a47c4..3887c808f39d3 100644
--- a/administrator/components/com_users/src/View/Users/HtmlView.php
+++ b/administrator/components/com_users/src/View/Users/HtmlView.php
@@ -1,4 +1,5 @@
items = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->state = $this->get('State');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->canDo = ContentHelper::getActions('com_users');
- $this->db = Factory::getDbo();
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->addToolbar();
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 1.6
- */
- protected function addToolbar()
- {
- $canDo = $this->canDo;
- $user = $this->getCurrentUser();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_USERS_VIEW_USERS_TITLE'), 'users user');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('user.add');
- }
-
- if ($canDo->get('core.edit.state') || $canDo->get('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('users.activate', 'COM_USERS_TOOLBAR_ACTIVATE', true);
- $childBar->unpublish('users.block', 'COM_USERS_TOOLBAR_BLOCK', true);
- $childBar->standardButton('unblock')
- ->text('COM_USERS_TOOLBAR_UNBLOCK')
- ->task('users.unblock')
- ->listCheck(true);
-
- // Add a batch button
- if ($user->authorise('core.create', 'com_users')
- && $user->authorise('core.edit', 'com_users')
- && $user->authorise('core.edit.state', 'com_users'))
- {
- $childBar->popupButton('batch')
- ->text('JTOOLBAR_BATCH')
- ->selector('collapseModal')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.delete'))
- {
- $childBar->delete('users.delete')
- ->text('JTOOLBAR_DELETE')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences('com_users');
- }
-
- $toolbar->help('Users');
- }
+ /**
+ * The item data.
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $items;
+
+ /**
+ * The pagination object.
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 1.6
+ */
+ protected $pagination;
+
+ /**
+ * The model state.
+ *
+ * @var CMSObject
+ * @since 1.6
+ */
+ protected $state;
+
+ /**
+ * A Form instance with filter fields.
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.6.3
+ */
+ public $filterForm;
+
+ /**
+ * An array with active filters.
+ *
+ * @var array
+ * @since 3.6.3
+ */
+ public $activeFilters;
+
+ /**
+ * An ACL object to verify user rights.
+ *
+ * @var CMSObject
+ * @since 3.6.3
+ */
+ protected $canDo;
+
+ /**
+ * An instance of DatabaseDriver.
+ *
+ * @var DatabaseDriver
+ * @since 3.6.3
+ *
+ * @deprecated 5.0 Will be removed without replacement
+ */
+ protected $db;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ */
+ public function display($tpl = null)
+ {
+ $this->items = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->state = $this->get('State');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->canDo = ContentHelper::getActions('com_users');
+ $this->db = Factory::getDbo();
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->addToolbar();
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 1.6
+ */
+ protected function addToolbar()
+ {
+ $canDo = $this->canDo;
+ $user = $this->getCurrentUser();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_USERS_VIEW_USERS_TITLE'), 'users user');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('user.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $canDo->get('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('users.activate', 'COM_USERS_TOOLBAR_ACTIVATE', true);
+ $childBar->unpublish('users.block', 'COM_USERS_TOOLBAR_BLOCK', true);
+ $childBar->standardButton('unblock')
+ ->text('COM_USERS_TOOLBAR_UNBLOCK')
+ ->task('users.unblock')
+ ->listCheck(true);
+
+ // Add a batch button
+ if (
+ $user->authorise('core.create', 'com_users')
+ && $user->authorise('core.edit', 'com_users')
+ && $user->authorise('core.edit.state', 'com_users')
+ ) {
+ $childBar->popupButton('batch')
+ ->text('JTOOLBAR_BATCH')
+ ->selector('collapseModal')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.delete')) {
+ $childBar->delete('users.delete')
+ ->text('JTOOLBAR_DELETE')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences('com_users');
+ }
+
+ $toolbar->help('Users');
+ }
}
diff --git a/administrator/components/com_users/tmpl/debuggroup/default.php b/administrator/components/com_users/tmpl/debuggroup/default.php
index ac658cdd6c8e4..b76b09280a550 100644
--- a/administrator/components/com_users/tmpl/debuggroup/default.php
+++ b/administrator/components/com_users/tmpl/debuggroup/default.php
@@ -1,4 +1,5 @@
escape($this->state->get('list.direction'));
?>
diff --git a/administrator/components/com_users/tmpl/debuguser/default.php b/administrator/components/com_users/tmpl/debuguser/default.php
index 75b108ef8b93b..159af56b5cbd4 100644
--- a/administrator/components/com_users/tmpl/debuguser/default.php
+++ b/administrator/components/com_users/tmpl/debuguser/default.php
@@ -1,4 +1,5 @@
actions as $action) :
- $name = $action[0];
- if (in_array($name, ['core.login.site', 'core.login.admin', 'core.login.offline', 'core.login.api', 'core.admin'])) :
- $loginActions[] = $action;
- else :
- $actions[] = $action;
- endif;
+ $name = $action[0];
+ if (in_array($name, ['core.login.site', 'core.login.admin', 'core.login.offline', 'core.login.api', 'core.admin'])) :
+ $loginActions[] = $action;
+ else :
+ $actions[] = $action;
+ endif;
endforeach;
?>
diff --git a/administrator/components/com_users/tmpl/group/edit.php b/administrator/components/com_users/tmpl/group/edit.php
index 2bcbb242f6092..d4c95a5542b23 100644
--- a/administrator/components/com_users/tmpl/group/edit.php
+++ b/administrator/components/com_users/tmpl/group/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$this->useCoreUI = true;
?>
diff --git a/administrator/components/com_users/tmpl/groups/default.php b/administrator/components/com_users/tmpl/groups/default.php
index def943cd56c4b..36d4d22e6e9cc 100644
--- a/administrator/components/com_users/tmpl/groups/default.php
+++ b/administrator/components/com_users/tmpl/groups/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('com_users.admin-users-groups')
- ->useScript('multiselect')
- ->useScript('table.columns');
+ ->useScript('multiselect')
+ ->useScript('table.columns');
?>
diff --git a/administrator/components/com_users/tmpl/level/edit.php b/administrator/components/com_users/tmpl/level/edit.php
index 389362f311ef2..e4985cc6a9043 100644
--- a/administrator/components/com_users/tmpl/level/edit.php
+++ b/administrator/components/com_users/tmpl/level/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_users/tmpl/levels/default.php b/administrator/components/com_users/tmpl/levels/default.php
index cefbc0692e3b2..83e260fb91351 100644
--- a/administrator/components/com_users/tmpl/levels/default.php
+++ b/administrator/components/com_users/tmpl/levels/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$saveOrder = $listOrder == 'a.ordering';
-if ($saveOrder && !empty($this->items))
-{
- $saveOrderingUrl = 'index.php?option=com_users&task=levels.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder && !empty($this->items)) {
+ $saveOrderingUrl = 'index.php?option=com_users&task=levels.saveOrderAjax&tmpl=component&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_users/tmpl/mail/default.php b/administrator/components/com_users/tmpl/mail/default.php
index 6a5eae8916997..209d5700f76db 100644
--- a/administrator/components/com_users/tmpl/mail/default.php
+++ b/administrator/components/com_users/tmpl/mail/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_users/tmpl/note/edit.php b/administrator/components/com_users/tmpl/note/edit.php
index ba80a12a1f4d8..8fd2b6d215227 100644
--- a/administrator/components/com_users/tmpl/note/edit.php
+++ b/administrator/components/com_users/tmpl/note/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
?>
diff --git a/administrator/components/com_users/tmpl/notes/default.php b/administrator/components/com_users/tmpl/notes/default.php
index 07586c547a7d5..b158e7cf1374f 100644
--- a/administrator/components/com_users/tmpl/notes/default.php
+++ b/administrator/components/com_users/tmpl/notes/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
@@ -26,103 +27,103 @@
?>
diff --git a/administrator/components/com_users/tmpl/notes/emptystate.php b/administrator/components/com_users/tmpl/notes/emptystate.php
index 2366086b4218b..0b963fa82243e 100644
--- a/administrator/components/com_users/tmpl/notes/emptystate.php
+++ b/administrator/components/com_users/tmpl/notes/emptystate.php
@@ -1,4 +1,5 @@
'COM_USERS_NOTES',
- 'formURL' => 'index.php?option=com_users&view=notes',
- 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:User_Notes',
- 'icon' => 'icon-users user',
+ 'textPrefix' => 'COM_USERS_NOTES',
+ 'formURL' => 'index.php?option=com_users&view=notes',
+ 'helpURL' => 'https://docs.joomla.org/Special:MyLanguage/Help40:User_Notes',
+ 'icon' => 'icon-users user',
];
-if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_users'))
-{
- $displayData['createURL'] = 'index.php?option=com_users&task=note.add';
+if (Factory::getApplication()->getIdentity()->authorise('core.create', 'com_users')) {
+ $displayData['createURL'] = 'index.php?option=com_users&task=note.add';
}
echo LayoutHelper::render('joomla.content.emptystate', $displayData);
diff --git a/administrator/components/com_users/tmpl/notes/modal.php b/administrator/components/com_users/tmpl/notes/modal.php
index deea2d14628d2..19b781d81d4da 100644
--- a/administrator/components/com_users/tmpl/notes/modal.php
+++ b/administrator/components/com_users/tmpl/notes/modal.php
@@ -1,4 +1,5 @@
-
user->name, $this->user->id); ?>
+
user->name, $this->user->id); ?>
items)) : ?>
-
+
-
- items as $item) : ?>
-
-
- subject) : ?>
-
id, $this->escape($item->subject)); ?>
-
- id, Text::_('COM_USERS_EMPTY_SUBJECT')); ?>
-
-
-
-
- created_time, Text::_('DATE_FORMAT_LC2')); ?>
-
-
- cparams->get('image'); ?>
-
- catid && isset($category_image)) : ?>
-
-
-
-
-
- escape($item->category_title); ?>
-
-
-
-
-
- body) ? HTMLHelper::_('content.prepare', $item->body) : ''); ?>
-
-
-
-
+
+ items as $item) : ?>
+
+
+ subject) : ?>
+
id, $this->escape($item->subject)); ?>
+
+ id, Text::_('COM_USERS_EMPTY_SUBJECT')); ?>
+
+
+
+
+ created_time, Text::_('DATE_FORMAT_LC2')); ?>
+
+
+ cparams->get('image'); ?>
+
+ catid && isset($category_image)) : ?>
+
+
+
+
+
+ escape($item->category_title); ?>
+
+
+
+
+
+ body) ? HTMLHelper::_('content.prepare', $item->body) : ''); ?>
+
+
+
+
diff --git a/administrator/components/com_users/tmpl/user/edit.php b/administrator/components/com_users/tmpl/user/edit.php
index 1df46ed313115..d408bbb6906bf 100644
--- a/administrator/components/com_users/tmpl/user/edit.php
+++ b/administrator/components/com_users/tmpl/user/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$input = Factory::getApplication()->input;
@@ -33,50 +34,50 @@
?>
diff --git a/administrator/components/com_users/tmpl/user/edit_groups.php b/administrator/components/com_users/tmpl/user/edit_groups.php
index d8d97b88cd771..fa5d093f04713 100644
--- a/administrator/components/com_users/tmpl/user/edit_groups.php
+++ b/administrator/components/com_users/tmpl/user/edit_groups.php
@@ -1,4 +1,5 @@
-groups, true); ?>
+groups, true);
diff --git a/administrator/components/com_users/tmpl/users/default_batch_body.php b/administrator/components/com_users/tmpl/users/default_batch_body.php
index b74fd0e867b5b..1c4b4b3ccc19e 100644
--- a/administrator/components/com_users/tmpl/users/default_batch_body.php
+++ b/administrator/components/com_users/tmpl/users/default_batch_body.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
@@ -13,16 +15,16 @@
// Create the copy/move options.
$options = array(
- HTMLHelper::_('select.option', 'add', Text::_('COM_USERS_BATCH_ADD')),
- HTMLHelper::_('select.option', 'del', Text::_('COM_USERS_BATCH_DELETE')),
- HTMLHelper::_('select.option', 'set', Text::_('COM_USERS_BATCH_SET'))
+ HTMLHelper::_('select.option', 'add', Text::_('COM_USERS_BATCH_ADD')),
+ HTMLHelper::_('select.option', 'del', Text::_('COM_USERS_BATCH_DELETE')),
+ HTMLHelper::_('select.option', 'set', Text::_('COM_USERS_BATCH_SET'))
);
// Create the reset password options.
$resetOptions = array(
- HTMLHelper::_('select.option', '', Text::_('COM_USERS_NO_ACTION')),
- HTMLHelper::_('select.option', 'yes', Text::_('JYES')),
- HTMLHelper::_('select.option', 'no', Text::_('JNO'))
+ HTMLHelper::_('select.option', '', Text::_('COM_USERS_NO_ACTION')),
+ HTMLHelper::_('select.option', 'yes', Text::_('JYES')),
+ HTMLHelper::_('select.option', 'no', Text::_('JNO'))
);
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
@@ -32,33 +34,33 @@
?>
diff --git a/administrator/components/com_users/tmpl/users/default_batch_footer.php b/administrator/components/com_users/tmpl/users/default_batch_footer.php
index 7f35695fde22f..e8db055384e61 100644
--- a/administrator/components/com_users/tmpl/users/default_batch_footer.php
+++ b/administrator/components/com_users/tmpl/users/default_batch_footer.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
+
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
?>
-
+
-
+
diff --git a/administrator/components/com_users/tmpl/users/modal.php b/administrator/components/com_users/tmpl/users/modal.php
index c1dae97df77ef..787156ab8c865 100644
--- a/administrator/components/com_users/tmpl/users/modal.php
+++ b/administrator/components/com_users/tmpl/users/modal.php
@@ -1,4 +1,5 @@
diff --git a/administrator/components/com_workflow/services/provider.php b/administrator/components/com_workflow/services/provider.php
index dbaf8e74cc1b8..81471b5803650 100644
--- a/administrator/components/com_workflow/services/provider.php
+++ b/administrator/components/com_workflow/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Workflow'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Workflow'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Workflow'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Workflow'));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_workflow/src/Controller/DisplayController.php b/administrator/components/com_workflow/src/Controller/DisplayController.php
index 7be435b39e32e..d48c96dfc149e 100644
--- a/administrator/components/com_workflow/src/Controller/DisplayController.php
+++ b/administrator/components/com_workflow/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
- }
-
- /**
- * Method to display a view.
- *
- * @param boolean $cachable If true, the view output will be cached
- * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
- *
- * @return BaseController|boolean This object to support chaining.
- *
- * @since 1.5
- */
- public function display($cachable = false, $urlparams = array())
- {
- $view = $this->input->get('view');
- $layout = $this->input->get('layout');
- $id = $this->input->getInt('id');
-
- // Check for edit form.
- if (in_array($view, ['workflow', 'stage', 'transition']) && $layout == 'edit' && !$this->checkEditId('com_workflow.edit.' . $view, $id))
- {
- // Somehow the person just went to the form - we don't allow that.
- if (!\count($this->app->getMessageQueue()))
- {
- $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
- }
-
- $url = 'index.php?option=com_workflow&view=' . Inflector::pluralize($view) . '&extension=' . $this->input->getCmd('extension');
-
- $this->setRedirect(Route::_($url, false));
-
- return false;
- }
-
- return parent::display();
- }
+ /**
+ * The default view.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $default_view = 'workflows';
+
+ /**
+ * The extension for which the workflow apply.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+ }
+
+ /**
+ * Method to display a view.
+ *
+ * @param boolean $cachable If true, the view output will be cached
+ * @param array $urlparams An array of safe URL parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
+ *
+ * @return BaseController|boolean This object to support chaining.
+ *
+ * @since 1.5
+ */
+ public function display($cachable = false, $urlparams = array())
+ {
+ $view = $this->input->get('view');
+ $layout = $this->input->get('layout');
+ $id = $this->input->getInt('id');
+
+ // Check for edit form.
+ if (in_array($view, ['workflow', 'stage', 'transition']) && $layout == 'edit' && !$this->checkEditId('com_workflow.edit.' . $view, $id)) {
+ // Somehow the person just went to the form - we don't allow that.
+ if (!\count($this->app->getMessageQueue())) {
+ $this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id), 'error');
+ }
+
+ $url = 'index.php?option=com_workflow&view=' . Inflector::pluralize($view) . '&extension=' . $this->input->getCmd('extension');
+
+ $this->setRedirect(Route::_($url, false));
+
+ return false;
+ }
+
+ return parent::display();
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/StageController.php b/administrator/components/com_workflow/src/Controller/StageController.php
index 63be67755ad4d..7cc76ce9b5795 100644
--- a/administrator/components/com_workflow/src/Controller/StageController.php
+++ b/administrator/components/com_workflow/src/Controller/StageController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -23,159 +23,151 @@
*/
class StageController extends FormController
{
- /**
- * The workflow in where the stage belongs to
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowId;
-
- /**
- * The extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension or workflow id is set
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If workflow id is not set try to get it from input or throw an exception
- if (empty($this->workflowId))
- {
- $this->workflowId = $this->input->getInt('workflow_id');
-
- if (empty($this->workflowId))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
- }
- }
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
- }
-
- /**
- * Method to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', $this->extension . '.workflow.' . (int) $this->workflowId);
- }
-
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record->id))
- {
- return false;
- }
-
- // Check "edit" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->extension . '.stage.' . $recordId))
- {
- return true;
- }
-
- // Check "edit own" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->extension . '.stage.' . $recordId))
- {
- return !empty($record) && $record->created_by == $user->id;
- }
-
- return false;
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
-
- $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
-
- return $append;
- }
+ /**
+ * The workflow in where the stage belongs to
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowId;
+
+ /**
+ * The extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension or workflow id is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If workflow id is not set try to get it from input or throw an exception
+ if (empty($this->workflowId)) {
+ $this->workflowId = $this->input->getInt('workflow_id');
+
+ if (empty($this->workflowId)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
+ }
+ }
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+ }
+
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', $this->extension . '.workflow.' . (int) $this->workflowId);
+ }
+
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record->id)) {
+ return false;
+ }
+
+ // Check "edit" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->extension . '.stage.' . $recordId)) {
+ return true;
+ }
+
+ // Check "edit own" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->extension . '.stage.' . $recordId)) {
+ return !empty($record) && $record->created_by == $user->id;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+
+ $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/StagesController.php b/administrator/components/com_workflow/src/Controller/StagesController.php
index 61be18cba531c..7f64bac3f9e13 100644
--- a/administrator/components/com_workflow/src/Controller/StagesController.php
+++ b/administrator/components/com_workflow/src/Controller/StagesController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -25,182 +25,170 @@
*/
class StagesController extends AdminController
{
- /**
- * The workflow in where the stage belongs to
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowId;
-
- /**
- * The extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * The prefix to use with controller messages.
- *
- * @var string
- * @since 4.0.0
- */
- protected $text_prefix = 'COM_WORKFLOW_STAGES';
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension or workflow id is set
- */
- public function __construct(array $config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If workflow id is not set try to get it from input or throw an exception
- if (empty($this->workflowId))
- {
- $this->workflowId = $this->input->getInt('workflow_id');
-
- if (empty($this->workflowId))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
- }
- }
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
-
- $this->registerTask('unsetDefault', 'setDefault');
- }
-
- /**
- * Proxy for getModel
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config The array of possible config values. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 4.0.0
- */
- public function getModel($name = 'Stage', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to set the home property for a list of items
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setDefault()
- {
- // Check for request forgeries
- $this->checkToken();
-
- // Get items to publish from the request.
- $cid = (array) $this->input->get('cid', array(), 'int');
- $data = array('setDefault' => 1, 'unsetDefault' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($data, $task, 0, 'int');
-
- if (!$value)
- {
- $this->setMessage(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'), 'warning');
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . '&extension=' . $this->extension, false
- )
- );
-
- return;
- }
-
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
-
- if (empty($cid))
- {
- $this->setMessage(Text::_('COM_WORKFLOW_NO_ITEM_SELECTED'), 'warning');
- }
- elseif (count($cid) > 1)
- {
- $this->setMessage(Text::_('COM_WORKFLOW_TOO_MANY_STAGES'), 'error');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Make sure the item ids are integers
- $id = reset($cid);
-
- // Publish the items.
- if (!$model->setDefault($id, $value))
- {
- $this->setMessage($model->getError(), 'warning');
- }
- else
- {
- $this->setMessage(Text::_('COM_WORKFLOW_STAGE_SET_DEFAULT'));
- }
- }
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . '&extension=' . $this->extension
- . '&workflow_id=' . $this->workflowId, false
- )
- );
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '') . '&workflow_id=' . $this->workflowId;
- }
+ /**
+ * The workflow in where the stage belongs to
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowId;
+
+ /**
+ * The extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $text_prefix = 'COM_WORKFLOW_STAGES';
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension or workflow id is set
+ */
+ public function __construct(array $config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If workflow id is not set try to get it from input or throw an exception
+ if (empty($this->workflowId)) {
+ $this->workflowId = $this->input->getInt('workflow_id');
+
+ if (empty($this->workflowId)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
+ }
+ }
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+
+ $this->registerTask('unsetDefault', 'setDefault');
+ }
+
+ /**
+ * Proxy for getModel
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 4.0.0
+ */
+ public function getModel($name = 'Stage', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to set the home property for a list of items
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setDefault()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get items to publish from the request.
+ $cid = (array) $this->input->get('cid', array(), 'int');
+ $data = array('setDefault' => 1, 'unsetDefault' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($data, $task, 0, 'int');
+
+ if (!$value) {
+ $this->setMessage(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'), 'warning');
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . '&extension=' . $this->extension,
+ false
+ )
+ );
+
+ return;
+ }
+
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
+
+ if (empty($cid)) {
+ $this->setMessage(Text::_('COM_WORKFLOW_NO_ITEM_SELECTED'), 'warning');
+ } elseif (count($cid) > 1) {
+ $this->setMessage(Text::_('COM_WORKFLOW_TOO_MANY_STAGES'), 'error');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Make sure the item ids are integers
+ $id = reset($cid);
+
+ // Publish the items.
+ if (!$model->setDefault($id, $value)) {
+ $this->setMessage($model->getError(), 'warning');
+ } else {
+ $this->setMessage(Text::_('COM_WORKFLOW_STAGE_SET_DEFAULT'));
+ }
+ }
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . '&extension=' . $this->extension
+ . '&workflow_id=' . $this->workflowId,
+ false
+ )
+ );
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '') . '&workflow_id=' . $this->workflowId;
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/TransitionController.php b/administrator/components/com_workflow/src/Controller/TransitionController.php
index fd7b19e89c586..f9fe617e1dca1 100644
--- a/administrator/components/com_workflow/src/Controller/TransitionController.php
+++ b/administrator/components/com_workflow/src/Controller/TransitionController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -23,160 +23,152 @@
*/
class TransitionController extends FormController
{
- /**
- * The workflow where the transition takes place
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowId;
-
- /**
- * The extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension or workflow id is set
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If workflow id is not set try to get it from input or throw an exception
- if (empty($this->workflowId))
- {
- $this->workflowId = $this->input->getInt('workflow_id');
-
- if (empty($this->workflowId))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
- }
- }
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
- }
-
- /**
- * Method to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', $this->extension . '.workflow.' . (int) $this->workflowId);
- }
-
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- $model = $this->getModel();
-
- $item = $model->getItem($recordId);
-
- if (empty($item->id))
- {
- return false;
- }
-
- // Check "edit" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->extension . '.transition.' . $recordId))
- {
- return true;
- }
-
- // Check "edit own" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->extension . '.transition.' . $recordId))
- {
- return !empty($item) && $item->created_by == $user->id;
- }
-
- return false;
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
- $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension;
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension;
-
- return $append;
- }
+ /**
+ * The workflow where the transition takes place
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowId;
+
+ /**
+ * The extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension or workflow id is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If workflow id is not set try to get it from input or throw an exception
+ if (empty($this->workflowId)) {
+ $this->workflowId = $this->input->getInt('workflow_id');
+
+ if (empty($this->workflowId)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
+ }
+ }
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+ }
+
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', $this->extension . '.workflow.' . (int) $this->workflowId);
+ }
+
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ $model = $this->getModel();
+
+ $item = $model->getItem($recordId);
+
+ if (empty($item->id)) {
+ return false;
+ }
+
+ // Check "edit" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->extension . '.transition.' . $recordId)) {
+ return true;
+ }
+
+ // Check "edit own" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->extension . '.transition.' . $recordId)) {
+ return !empty($item) && $item->created_by == $user->id;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+ $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension;
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&workflow_id=' . $this->workflowId . '&extension=' . $this->extension;
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/TransitionsController.php b/administrator/components/com_workflow/src/Controller/TransitionsController.php
index 31670f514b4e3..c93467e64a289 100644
--- a/administrator/components/com_workflow/src/Controller/TransitionsController.php
+++ b/administrator/components/com_workflow/src/Controller/TransitionsController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -23,115 +23,110 @@
*/
class TransitionsController extends AdminController
{
- /**
- * The workflow where the transition takes place
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowId;
-
- /**
- * The extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * The prefix to use with controller messages.
- *
- * @var string
- * @since 4.0.0
- */
- protected $text_prefix = 'COM_WORKFLOW_TRANSITIONS';
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension or workflow id is set
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If workflow id is not set try to get it from input or throw an exception
- if (empty($this->workflowId))
- {
- $this->workflowId = $this->input->getInt('workflow_id');
-
- if (empty($this->workflowId))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
- }
- }
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
- }
-
- /**
- * Proxy for getModel
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config The array of possible config values. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 4.0.0
- */
- public function getModel($name = 'Transition', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
-
- $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '')
- . '&workflow_id=' . $this->workflowId;
-
- return $append;
- }
+ /**
+ * The workflow where the transition takes place
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowId;
+
+ /**
+ * The extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * The prefix to use with controller messages.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $text_prefix = 'COM_WORKFLOW_TRANSITIONS';
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension or workflow id is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If workflow id is not set try to get it from input or throw an exception
+ if (empty($this->workflowId)) {
+ $this->workflowId = $this->input->getInt('workflow_id');
+
+ if (empty($this->workflowId)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_WORKFLOW_ID_NOT_SET'));
+ }
+ }
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+ }
+
+ /**
+ * Proxy for getModel
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 4.0.0
+ */
+ public function getModel($name = 'Transition', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+
+ $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '')
+ . '&workflow_id=' . $this->workflowId;
+
+ return $append;
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/WorkflowController.php b/administrator/components/com_workflow/src/Controller/WorkflowController.php
index 9d4208b24310b..5ecc903ab74dd 100644
--- a/administrator/components/com_workflow/src/Controller/WorkflowController.php
+++ b/administrator/components/com_workflow/src/Controller/WorkflowController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -25,223 +25,214 @@
*/
class WorkflowController extends FormController
{
- /**
- * The extension for which the workflows apply.
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension is set
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
- }
-
- /**
- * Method to check if you can add a new record.
- *
- * @param array $data An array of input data.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowAdd($data = array())
- {
- return $this->app->getIdentity()->authorise('core.create', $this->extension);
- }
-
- /**
- * Method to check if you can edit a record.
- *
- * @param array $data An array of input data.
- * @param string $key The name of the key for the primary key.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- protected function allowEdit($data = array(), $key = 'id')
- {
- $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
- $user = $this->app->getIdentity();
-
- $record = $this->getModel()->getItem($recordId);
-
- if (empty($record->id))
- {
- return false;
- }
-
- // Check "edit" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit', $this->extension . '.workflow.' . $recordId))
- {
- return true;
- }
-
- // Check "edit own" permission on record asset (explicit or inherited)
- if ($user->authorise('core.edit.own', $this->extension . '.workflow.' . $recordId))
- {
- return !empty($record) && $record->created_by == $user->id;
- }
-
- return false;
- }
-
- /**
- * Gets the URL arguments to append to an item redirect.
- *
- * @param integer $recordId The primary key id for the item.
- * @param string $urlVar The name of the URL variable for the id.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
- {
- $append = parent::getRedirectToItemAppend($recordId);
- $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
-
- return $append;
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- $append = parent::getRedirectToListAppend();
- $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
-
- return $append;
- }
-
- /**
- * Function that allows child controller access to model data
- * after the data has been saved.
- *
- * @param BaseDatabaseModel $model The data model object.
- * @param array $validData The validated data.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function postSaveHook(BaseDatabaseModel $model, $validData = array())
- {
- $task = $this->getTask();
-
- // The save2copy task needs to be handled slightly differently.
- if ($task === 'save2copy')
- {
- $table = $model->getTable();
-
- $key = $table->getKeyName();
-
- $recordId = (int) $this->input->getInt($key);
-
- // @todo Moves queries out of the controller.
- $db = $model->getDbo();
- $query = $db->getQuery(true);
-
- $query->select('*')
- ->from($db->quoteName('#__workflow_stages'))
- ->where($db->quoteName('workflow_id') . ' = :id')
- ->bind(':id', $recordId, ParameterType::INTEGER);
-
- $statuses = $db->setQuery($query)->loadAssocList();
-
- $smodel = $this->getModel('Stage');
-
- $workflowID = (int) $model->getState($model->getName() . '.id');
-
- $mapping = [];
-
- foreach ($statuses as $status)
- {
- $table = $smodel->getTable();
-
- $oldID = $status['id'];
-
- $status['workflow_id'] = $workflowID;
- $status['id'] = 0;
-
- unset($status['asset_id']);
-
- $table->save($status);
-
- $mapping[$oldID] = (int) $table->id;
- }
-
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName('#__workflow_transitions'))
- ->where($db->quoteName('workflow_id') . ' = :id')
- ->bind(':id', $recordId, ParameterType::INTEGER);
-
- $transitions = $db->setQuery($query)->loadAssocList();
-
- $tmodel = $this->getModel('Transition');
-
- foreach ($transitions as $transition)
- {
- $table = $tmodel->getTable();
-
- $transition['from_stage_id'] = $transition['from_stage_id'] != -1 ? $mapping[$transition['from_stage_id']] : -1;
- $transition['to_stage_id'] = $mapping[$transition['to_stage_id']];
+ /**
+ * The extension for which the workflows apply.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+ }
+
+ /**
+ * Method to check if you can add a new record.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowAdd($data = array())
+ {
+ return $this->app->getIdentity()->authorise('core.create', $this->extension);
+ }
+
+ /**
+ * Method to check if you can edit a record.
+ *
+ * @param array $data An array of input data.
+ * @param string $key The name of the key for the primary key.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ protected function allowEdit($data = array(), $key = 'id')
+ {
+ $recordId = isset($data[$key]) ? (int) $data[$key] : 0;
+ $user = $this->app->getIdentity();
+
+ $record = $this->getModel()->getItem($recordId);
+
+ if (empty($record->id)) {
+ return false;
+ }
+
+ // Check "edit" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit', $this->extension . '.workflow.' . $recordId)) {
+ return true;
+ }
+
+ // Check "edit own" permission on record asset (explicit or inherited)
+ if ($user->authorise('core.edit.own', $this->extension . '.workflow.' . $recordId)) {
+ return !empty($record) && $record->created_by == $user->id;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the URL arguments to append to an item redirect.
+ *
+ * @param integer $recordId The primary key id for the item.
+ * @param string $urlVar The name of the URL variable for the id.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
+ {
+ $append = parent::getRedirectToItemAppend($recordId);
+ $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
+
+ return $append;
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ $append = parent::getRedirectToListAppend();
+ $append .= '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
+
+ return $append;
+ }
+
+ /**
+ * Function that allows child controller access to model data
+ * after the data has been saved.
+ *
+ * @param BaseDatabaseModel $model The data model object.
+ * @param array $validData The validated data.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function postSaveHook(BaseDatabaseModel $model, $validData = array())
+ {
+ $task = $this->getTask();
+
+ // The save2copy task needs to be handled slightly differently.
+ if ($task === 'save2copy') {
+ $table = $model->getTable();
+
+ $key = $table->getKeyName();
+
+ $recordId = (int) $this->input->getInt($key);
+
+ // @todo Moves queries out of the controller.
+ $db = $model->getDbo();
+ $query = $db->getQuery(true);
+
+ $query->select('*')
+ ->from($db->quoteName('#__workflow_stages'))
+ ->where($db->quoteName('workflow_id') . ' = :id')
+ ->bind(':id', $recordId, ParameterType::INTEGER);
+
+ $statuses = $db->setQuery($query)->loadAssocList();
+
+ $smodel = $this->getModel('Stage');
+
+ $workflowID = (int) $model->getState($model->getName() . '.id');
+
+ $mapping = [];
+
+ foreach ($statuses as $status) {
+ $table = $smodel->getTable();
+
+ $oldID = $status['id'];
+
+ $status['workflow_id'] = $workflowID;
+ $status['id'] = 0;
+
+ unset($status['asset_id']);
+
+ $table->save($status);
+
+ $mapping[$oldID] = (int) $table->id;
+ }
+
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__workflow_transitions'))
+ ->where($db->quoteName('workflow_id') . ' = :id')
+ ->bind(':id', $recordId, ParameterType::INTEGER);
+
+ $transitions = $db->setQuery($query)->loadAssocList();
+
+ $tmodel = $this->getModel('Transition');
+
+ foreach ($transitions as $transition) {
+ $table = $tmodel->getTable();
+
+ $transition['from_stage_id'] = $transition['from_stage_id'] != -1 ? $mapping[$transition['from_stage_id']] : -1;
+ $transition['to_stage_id'] = $mapping[$transition['to_stage_id']];
- $transition['workflow_id'] = $workflowID;
- $transition['id'] = 0;
+ $transition['workflow_id'] = $workflowID;
+ $transition['id'] = 0;
- unset($transition['asset_id']);
+ unset($transition['asset_id']);
- $table->save($transition);
- }
- }
- }
+ $table->save($transition);
+ }
+ }
+ }
}
diff --git a/administrator/components/com_workflow/src/Controller/WorkflowsController.php b/administrator/components/com_workflow/src/Controller/WorkflowsController.php
index 82ea1104490f6..1145466c77020 100644
--- a/administrator/components/com_workflow/src/Controller/WorkflowsController.php
+++ b/administrator/components/com_workflow/src/Controller/WorkflowsController.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Controller;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Controller;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
@@ -25,163 +25,150 @@
*/
class WorkflowsController extends AdminController
{
- /**
- * The extension for which the workflows apply.
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Constructor.
- *
- * @param array $config An optional associative array of configuration settings.
- * @param MVCFactoryInterface $factory The factory.
- * @param CMSApplication $app The Application for the dispatcher
- * @param Input $input Input
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException when no extension is set
- */
- public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
- {
- parent::__construct($config, $factory, $app, $input);
-
- // If extension is not set try to get it from input or throw an exception
- if (empty($this->extension))
- {
- $extension = $this->input->getCmd('extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (empty($this->extension))
- {
- throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
- }
- }
-
- $this->registerTask('unsetDefault', 'setDefault');
- }
-
- /**
- * Proxy for getModel
- *
- * @param string $name The model name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config The array of possible config values. Optional.
- *
- * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
- *
- * @since 4.0.0
- */
- public function getModel($name = 'Workflow', $prefix = 'Administrator', $config = array('ignore_request' => true))
- {
- return parent::getModel($name, $prefix, $config);
- }
-
- /**
- * Method to set the home property for a list of items
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setDefault()
- {
- // Check for request forgeries
- $this->checkToken();
-
- // Get items to publish from the request.
- $cid = (array) $this->input->get('cid', array(), 'int');
- $data = array('setDefault' => 1, 'unsetDefault' => 0);
- $task = $this->getTask();
- $value = ArrayHelper::getValue($data, $task, 0, 'int');
-
- if (!$value)
- {
- $this->setMessage(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'), 'warning');
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''), false
- )
- );
-
- return;
- }
-
- // Remove zero values resulting from input filter
- $cid = array_filter($cid);
-
- if (empty($cid))
- {
- $this->setMessage(Text::_('COM_WORKFLOW_NO_ITEM_SELECTED'), 'warning');
- }
- elseif (count($cid) > 1)
- {
- $this->setMessage(Text::_('COM_WORKFLOW_TOO_MANY_WORKFLOWS'), 'error');
- }
- else
- {
- // Get the model.
- $model = $this->getModel();
-
- // Make sure the item ids are integers
- $id = reset($cid);
-
- // Publish the items.
- if (!$model->setDefault($id, $value))
- {
- $this->setMessage($model->getError(), 'warning');
- }
- else
- {
- if ($value === 1)
- {
- $ntext = 'COM_WORKFLOW_SET_DEFAULT';
- }
- else
- {
- $ntext = 'COM_WORKFLOW_ITEM_UNSET_DEFAULT';
- }
-
- $this->setMessage(Text::_($ntext, count($cid)));
- }
- }
-
- $this->setRedirect(
- Route::_(
- 'index.php?option=' . $this->option . '&view=' . $this->view_list
- . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''), false
- )
- );
- }
-
- /**
- * Gets the URL arguments to append to a list redirect.
- *
- * @return string The arguments to append to the redirect URL.
- *
- * @since 4.0.0
- */
- protected function getRedirectToListAppend()
- {
- return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
- }
+ /**
+ * The extension for which the workflows apply.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ * @param MVCFactoryInterface $factory The factory.
+ * @param CMSApplication $app The Application for the dispatcher
+ * @param Input $input Input
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException when no extension is set
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // If extension is not set try to get it from input or throw an exception
+ if (empty($this->extension)) {
+ $extension = $this->input->getCmd('extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (empty($this->extension)) {
+ throw new \InvalidArgumentException(Text::_('COM_WORKFLOW_ERROR_EXTENSION_NOT_SET'));
+ }
+ }
+
+ $this->registerTask('unsetDefault', 'setDefault');
+ }
+
+ /**
+ * Proxy for getModel
+ *
+ * @param string $name The model name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config The array of possible config values. Optional.
+ *
+ * @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
+ *
+ * @since 4.0.0
+ */
+ public function getModel($name = 'Workflow', $prefix = 'Administrator', $config = array('ignore_request' => true))
+ {
+ return parent::getModel($name, $prefix, $config);
+ }
+
+ /**
+ * Method to set the home property for a list of items
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setDefault()
+ {
+ // Check for request forgeries
+ $this->checkToken();
+
+ // Get items to publish from the request.
+ $cid = (array) $this->input->get('cid', array(), 'int');
+ $data = array('setDefault' => 1, 'unsetDefault' => 0);
+ $task = $this->getTask();
+ $value = ArrayHelper::getValue($data, $task, 0, 'int');
+
+ if (!$value) {
+ $this->setMessage(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'), 'warning');
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''),
+ false
+ )
+ );
+
+ return;
+ }
+
+ // Remove zero values resulting from input filter
+ $cid = array_filter($cid);
+
+ if (empty($cid)) {
+ $this->setMessage(Text::_('COM_WORKFLOW_NO_ITEM_SELECTED'), 'warning');
+ } elseif (count($cid) > 1) {
+ $this->setMessage(Text::_('COM_WORKFLOW_TOO_MANY_WORKFLOWS'), 'error');
+ } else {
+ // Get the model.
+ $model = $this->getModel();
+
+ // Make sure the item ids are integers
+ $id = reset($cid);
+
+ // Publish the items.
+ if (!$model->setDefault($id, $value)) {
+ $this->setMessage($model->getError(), 'warning');
+ } else {
+ if ($value === 1) {
+ $ntext = 'COM_WORKFLOW_SET_DEFAULT';
+ } else {
+ $ntext = 'COM_WORKFLOW_ITEM_UNSET_DEFAULT';
+ }
+
+ $this->setMessage(Text::_($ntext, count($cid)));
+ }
+ }
+
+ $this->setRedirect(
+ Route::_(
+ 'index.php?option=' . $this->option . '&view=' . $this->view_list
+ . '&extension=' . $this->extension . ($this->section ? '.' . $this->section : ''),
+ false
+ )
+ );
+ }
+
+ /**
+ * Gets the URL arguments to append to a list redirect.
+ *
+ * @return string The arguments to append to the redirect URL.
+ *
+ * @since 4.0.0
+ */
+ protected function getRedirectToListAppend()
+ {
+ return '&extension=' . $this->extension . ($this->section ? '.' . $this->section : '');
+ }
}
diff --git a/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php b/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php
index e34a303e7291e..077aba76c9452 100644
--- a/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php
+++ b/administrator/components/com_workflow/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
getApplication()->input->getCmd('extension');
+ /**
+ * Workflows have to check for extension permission
+ *
+ * @return void
+ */
+ protected function checkAccess()
+ {
+ $extension = $this->getApplication()->input->getCmd('extension');
- $parts = explode('.', $extension);
+ $parts = explode('.', $extension);
- // Check the user has permission to access this component if in the backend
- if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage.workflow', $parts[0]))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ // Check the user has permission to access this component if in the backend
+ if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage.workflow', $parts[0])) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/administrator/components/com_workflow/src/Field/ComponentsWorkflowField.php b/administrator/components/com_workflow/src/Field/ComponentsWorkflowField.php
index 681abc8f7b090..9d49792b07517 100644
--- a/administrator/components/com_workflow/src/Field/ComponentsWorkflowField.php
+++ b/administrator/components/com_workflow/src/Field/ComponentsWorkflowField.php
@@ -1,4 +1,5 @@
getDatabase();
-
- $query = $db->getQuery(true)
- ->select('DISTINCT a.name AS text, a.element AS value')
- ->from('#__extensions as a')
- ->where('a.enabled >= 1')
- ->where('a.type =' . $db->quote('component'));
-
- $items = $db->setQuery($query)->loadObjectList();
-
- $options = [];
-
- if (count($items))
- {
- $lang = Factory::getLanguage();
-
- $components = [];
-
- // Search for components supporting Fieldgroups - suppose that these components support fields as well
- foreach ($items as &$item)
- {
- $availableActions = Access::getActionsFromFile(
- JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
- "/access/section[@name='workflow']/"
- );
-
- if (!empty($availableActions))
- {
- // Load language
- $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
- $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
- || $lang->load($item->value . 'sys', $source);
-
- // Translate component name
- $item->text = Text::_($item->text);
-
- $components[] = $item;
- }
- }
-
- if (empty($components))
- {
- return [];
- }
-
- foreach ($components as $component)
- {
- // Search for different contexts
- $c = Factory::getApplication()->bootComponent($component->value);
-
- if ($c instanceof WorkflowServiceInterface)
- {
- $contexts = $c->getContexts();
-
- foreach ($contexts as $context)
- {
- $newOption = new \stdClass;
- $newOption->value = strtolower($component->value . '.' . $context);
- $newOption->text = $component->text . ' - ' . Text::_($context);
- $options[] = $newOption;
- }
- }
- else
- {
- $options[] = $component;
- }
- }
-
- // Sort by name
- $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $items);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $type = 'ComponentsWorkflow';
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ // Initialise variable.
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select('DISTINCT a.name AS text, a.element AS value')
+ ->from('#__extensions as a')
+ ->where('a.enabled >= 1')
+ ->where('a.type =' . $db->quote('component'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ $options = [];
+
+ if (count($items)) {
+ $lang = Factory::getLanguage();
+
+ $components = [];
+
+ // Search for components supporting Fieldgroups - suppose that these components support fields as well
+ foreach ($items as &$item) {
+ $availableActions = Access::getActionsFromFile(
+ JPATH_ADMINISTRATOR . '/components/' . $item->value . '/access.xml',
+ "/access/section[@name='workflow']/"
+ );
+
+ if (!empty($availableActions)) {
+ // Load language
+ $source = JPATH_ADMINISTRATOR . '/components/' . $item->value;
+ $lang->load($item->value . 'sys', JPATH_ADMINISTRATOR)
+ || $lang->load($item->value . 'sys', $source);
+
+ // Translate component name
+ $item->text = Text::_($item->text);
+
+ $components[] = $item;
+ }
+ }
+
+ if (empty($components)) {
+ return [];
+ }
+
+ foreach ($components as $component) {
+ // Search for different contexts
+ $c = Factory::getApplication()->bootComponent($component->value);
+
+ if ($c instanceof WorkflowServiceInterface) {
+ $contexts = $c->getContexts();
+
+ foreach ($contexts as $context) {
+ $newOption = new \stdClass();
+ $newOption->value = strtolower($component->value . '.' . $context);
+ $newOption->text = $component->text . ' - ' . Text::_($context);
+ $options[] = $newOption;
+ }
+ } else {
+ $options[] = $component;
+ }
+ }
+
+ // Sort by name
+ $items = ArrayHelper::sortObjects($options, 'text', 1, true, true);
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $items);
+
+ return $options;
+ }
}
diff --git a/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php b/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php
index 08752825cdfd0..e4407da0c6617 100644
--- a/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php
+++ b/administrator/components/com_workflow/src/Field/WorkflowcontextsField.php
@@ -1,4 +1,5 @@
getOptions()) < 2)
- {
- $this->layout = 'joomla.form.field.hidden';
- }
+ /**
+ * Method to get the field input markup for a generic list.
+ * Use the multiple attribute to enable multiselect.
+ *
+ * @return string The field input markup.
+ *
+ * @since 4.0.0
+ */
+ protected function getInput()
+ {
+ if (count($this->getOptions()) < 2) {
+ $this->layout = 'joomla.form.field.hidden';
+ }
- return parent::getInput();
- }
+ return parent::getInput();
+ }
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 4.0.0
- */
- protected function getOptions()
- {
- $parts = explode('.', $this->value);
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 4.0.0
+ */
+ protected function getOptions()
+ {
+ $parts = explode('.', $this->value);
- $component = Factory::getApplication()->bootComponent($parts[0]);
+ $component = Factory::getApplication()->bootComponent($parts[0]);
- if ($component instanceof WorkflowServiceInterface)
- {
- return $component->getWorkflowContexts();
- }
+ if ($component instanceof WorkflowServiceInterface) {
+ return $component->getWorkflowContexts();
+ }
- return [];
- }
+ return [];
+ }
}
diff --git a/administrator/components/com_workflow/src/Helper/StageHelper.php b/administrator/components/com_workflow/src/Helper/StageHelper.php
index 3348c32aa4ff9..a0ed5ed266170 100644
--- a/administrator/components/com_workflow/src/Helper/StageHelper.php
+++ b/administrator/components/com_workflow/src/Helper/StageHelper.php
@@ -1,4 +1,5 @@
option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
-
- $this->setState('filter.extension', $extension);
- }
-
- /**
- * Method to change the title
- *
- * @param integer $categoryId The id of the category.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 4.0.0
- */
- protected function generateNewTitle($categoryId, $alias, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('title' => $title)))
- {
- $title = StringHelper::increment($title);
- }
-
- return array($title, $alias);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function save($data)
- {
- $table = $this->getTable();
- $context = $this->option . '.' . $this->name;
- $app = Factory::getApplication();
- $user = $app->getIdentity();
- $input = $app->input;
- $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
-
- if (empty($data['workflow_id']))
- {
- $data['workflow_id'] = $workflowID;
- }
-
- $workflow = $this->getTable('Workflow');
-
- $workflow->load($data['workflow_id']);
-
- $parts = explode('.', $workflow->extension);
-
- if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0]))
- {
- unset($data['rules']);
- }
-
- // Make sure we use the correct extension when editing an existing workflow
- $key = $table->getKeyName();
- $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
-
- if ($pk > 0)
- {
- $table->load($pk);
-
- if ((int) $table->workflow_id)
- {
- $data['workflow_id'] = (int) $table->workflow_id;
- }
- }
-
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
-
- // Alter the title for save as copy
- if ($origTable->load(['title' => $data['title']]))
- {
- list($title) = $this->generateNewTitle(0, '', $data['title']);
- $data['title'] = $title;
- }
-
- $data['published'] = 0;
- $data['default'] = 0;
- }
-
- return parent::save($data);
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
- *
- * @since 4.0.0
- */
- protected function canDelete($record)
- {
- $table = $this->getTable('Workflow', 'Administrator');
-
- $table->load($record->workflow_id);
-
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- $app = Factory::getApplication();
- $extension = $app->getUserStateFromRequest('com_workflow.stage.filter.extension', 'extension', null, 'cmd');
-
- $parts = explode('.', $extension);
-
- $component = reset($parts);
-
- if (!Factory::getUser()->authorise('core.delete', $component . '.state.' . (int) $record->id) || $record->default)
- {
- $this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 4.0.0
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
- $app = Factory::getApplication();
- $context = $this->option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
-
- if (!\property_exists($record, 'workflow_id'))
- {
- $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
- $record->workflow_id = $workflowID;
- }
-
- // Check for existing workflow.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $extension . '.state.' . (int) $record->id);
- }
-
- // Default to component settings if workflow isn't known.
- return $user->authorise('core.edit.state', $extension);
- }
-
- /**
- * Abstract method for getting the form from the model.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|boolean A Form object on success, false on failure
- *
- * @since 4.0.0
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm(
- 'com_workflow.state',
- 'stage',
- array(
- 'control' => 'jform',
- 'load_data' => $loadData
- )
- );
-
- if (empty($form))
- {
- return false;
- }
-
- $id = $data['id'] ?? $form->getValue('id');
-
- $item = $this->getItem($id);
-
- $canEditState = $this->canEditState((object) $item);
-
- // Modify the form based on access controls.
- if (!$canEditState || !empty($item->default))
- {
- if (!$canEditState)
- {
- $form->setFieldAttribute('published', 'disabled', 'true');
- $form->setFieldAttribute('published', 'required', 'false');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- $form->setFieldAttribute('default', 'disabled', 'true');
- $form->setFieldAttribute('default', 'required', 'false');
- $form->setFieldAttribute('default', 'filter', 'unset');
- }
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 4.0.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState(
- 'com_workflow.edit.state.data',
- array()
- );
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- return $data;
- }
-
- /**
- * Method to change the home state of one or more items.
- *
- * @param array $pk A list of the primary keys to change.
- * @param integer $value The value of the home state.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setDefault($pk, $value = 1)
- {
- $table = $this->getTable();
-
- if ($table->load($pk))
- {
- if (!$table->published)
- {
- $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
-
- return false;
- }
- }
-
- if (empty($table->id) || !$this->canEditState($table))
- {
- Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- if ($value)
- {
- // Verify that the home page for this language is unique per client id
- if ($table->load(array('default' => '1', 'workflow_id' => $table->workflow_id)))
- {
- $table->default = 0;
- $table->store();
- }
- }
-
- if ($table->load($pk))
- {
- $table->default = $value;
- $table->store();
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array &$pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function publish(&$pks, $value = 1)
- {
- $table = $this->getTable();
- $pks = (array) $pks;
- $app = Factory::getApplication();
- $extension = $app->getUserStateFromRequest('com_workflow.state.filter.extension', 'extension', null, 'cmd');
-
- // Default item existence checks.
- if ($value != 1)
- {
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk) && $table->default)
- {
- // Prune items that you can't change.
- $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DISABLE_DEFAULT'), 'error');
-
- unset($pks[$i]);
- }
- }
- }
-
- return parent::publish($pks, $value);
- }
-
- /**
- * Method to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $extension = Factory::getApplication()->input->get('extension');
-
- $parts = explode('.', $extension);
-
- $extension = array_shift($parts);
-
- // Set the access control rules field component value.
- $form->setFieldAttribute('rules', 'component', $extension);
-
- parent::preprocessForm($form, $data, $group);
- }
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function populateState()
+ {
+ parent::populateState();
+
+ $app = Factory::getApplication();
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ }
+
+ /**
+ * Method to change the title
+ *
+ * @param integer $categoryId The id of the category.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 4.0.0
+ */
+ protected function generateNewTitle($categoryId, $alias, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('title' => $title))) {
+ $title = StringHelper::increment($title);
+ }
+
+ return array($title, $alias);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+ $context = $this->option . '.' . $this->name;
+ $app = Factory::getApplication();
+ $user = $app->getIdentity();
+ $input = $app->input;
+ $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
+
+ if (empty($data['workflow_id'])) {
+ $data['workflow_id'] = $workflowID;
+ }
+
+ $workflow = $this->getTable('Workflow');
+
+ $workflow->load($data['workflow_id']);
+
+ $parts = explode('.', $workflow->extension);
+
+ if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0])) {
+ unset($data['rules']);
+ }
+
+ // Make sure we use the correct extension when editing an existing workflow
+ $key = $table->getKeyName();
+ $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
+
+ if ($pk > 0) {
+ $table->load($pk);
+
+ if ((int) $table->workflow_id) {
+ $data['workflow_id'] = (int) $table->workflow_id;
+ }
+ }
+
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+
+ // Alter the title for save as copy
+ if ($origTable->load(['title' => $data['title']])) {
+ list($title) = $this->generateNewTitle(0, '', $data['title']);
+ $data['title'] = $title;
+ }
+
+ $data['published'] = 0;
+ $data['default'] = 0;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canDelete($record)
+ {
+ $table = $this->getTable('Workflow', 'Administrator');
+
+ $table->load($record->workflow_id);
+
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ $app = Factory::getApplication();
+ $extension = $app->getUserStateFromRequest('com_workflow.stage.filter.extension', 'extension', null, 'cmd');
+
+ $parts = explode('.', $extension);
+
+ $component = reset($parts);
+
+ if (!Factory::getUser()->authorise('core.delete', $component . '.state.' . (int) $record->id) || $record->default) {
+ $this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+ $app = Factory::getApplication();
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+
+ if (!\property_exists($record, 'workflow_id')) {
+ $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
+ $record->workflow_id = $workflowID;
+ }
+
+ // Check for existing workflow.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $extension . '.state.' . (int) $record->id);
+ }
+
+ // Default to component settings if workflow isn't known.
+ return $user->authorise('core.edit.state', $extension);
+ }
+
+ /**
+ * Abstract method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|boolean A Form object on success, false on failure
+ *
+ * @since 4.0.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_workflow.state',
+ 'stage',
+ array(
+ 'control' => 'jform',
+ 'load_data' => $loadData
+ )
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $id = $data['id'] ?? $form->getValue('id');
+
+ $item = $this->getItem($id);
+
+ $canEditState = $this->canEditState((object) $item);
+
+ // Modify the form based on access controls.
+ if (!$canEditState || !empty($item->default)) {
+ if (!$canEditState) {
+ $form->setFieldAttribute('published', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'required', 'false');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ $form->setFieldAttribute('default', 'disabled', 'true');
+ $form->setFieldAttribute('default', 'required', 'false');
+ $form->setFieldAttribute('default', 'filter', 'unset');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 4.0.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState(
+ 'com_workflow.edit.state.data',
+ array()
+ );
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Method to change the home state of one or more items.
+ *
+ * @param array $pk A list of the primary keys to change.
+ * @param integer $value The value of the home state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setDefault($pk, $value = 1)
+ {
+ $table = $this->getTable();
+
+ if ($table->load($pk)) {
+ if (!$table->published) {
+ $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
+
+ return false;
+ }
+ }
+
+ if (empty($table->id) || !$this->canEditState($table)) {
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ if ($value) {
+ // Verify that the home page for this language is unique per client id
+ if ($table->load(array('default' => '1', 'workflow_id' => $table->workflow_id))) {
+ $table->default = 0;
+ $table->store();
+ }
+ }
+
+ if ($table->load($pk)) {
+ $table->default = $value;
+ $table->store();
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array &$pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $table = $this->getTable();
+ $pks = (array) $pks;
+ $app = Factory::getApplication();
+ $extension = $app->getUserStateFromRequest('com_workflow.state.filter.extension', 'extension', null, 'cmd');
+
+ // Default item existence checks.
+ if ($value != 1) {
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk) && $table->default) {
+ // Prune items that you can't change.
+ $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DISABLE_DEFAULT'), 'error');
+
+ unset($pks[$i]);
+ }
+ }
+ }
+
+ return parent::publish($pks, $value);
+ }
+
+ /**
+ * Method to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $extension = Factory::getApplication()->input->get('extension');
+
+ $parts = explode('.', $extension);
+
+ $extension = array_shift($parts);
+
+ // Set the access control rules field component value.
+ $form->setFieldAttribute('rules', 'component', $extension);
+
+ parent::preprocessForm($form, $data, $group);
+ }
}
diff --git a/administrator/components/com_workflow/src/Model/StagesModel.php b/administrator/components/com_workflow/src/Model/StagesModel.php
index 53fb31fd4789b..74a2fa51379db 100644
--- a/administrator/components/com_workflow/src/Model/StagesModel.php
+++ b/administrator/components/com_workflow/src/Model/StagesModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int');
- $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
-
- if ($workflowID)
- {
- $table = $this->getTable('Workflow', 'Administrator');
-
- if ($table->load($workflowID))
- {
- $this->setState('active_workflow', $table->title);
- }
- }
-
- $this->setState('filter.workflow_id', $workflowID);
- $this->setState('filter.extension', $extension);
-
- parent::populateState($ordering, $direction);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 4.0.0
- */
- protected function getReorderConditions($table)
- {
- return [
- $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id,
- ];
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\Table\Table A Table object
- *
- * @since 4.0.0
- */
- public function getTable($type = 'Stage', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return string The query to database.
- *
- * @since 4.0.0
- */
- public function getListQuery()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query
- ->select(
- [
- $db->quoteName('s.id'),
- $db->quoteName('s.title'),
- $db->quoteName('s.ordering'),
- $db->quoteName('s.default'),
- $db->quoteName('s.published'),
- $db->quoteName('s.checked_out'),
- $db->quoteName('s.checked_out_time'),
- $db->quoteName('s.description'),
- $db->quoteName('uc.name', 'editor'),
- ]
- )
- ->from($db->quoteName('#__workflow_stages', 's'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('s.checked_out'));
-
- // Filter by extension
- if ($workflowID = (int) $this->getState('filter.workflow_id'))
- {
- $query->where($db->quoteName('s.workflow_id') . ' = :id')
- ->bind(':id', $workflowID, ParameterType::INTEGER);
- }
-
- $status = (string) $this->getState('filter.published');
-
- // Filter by publish state
- if (is_numeric($status))
- {
- $status = (int) $status;
- $query->where($db->quoteName('s.published') . ' = :status')
- ->bind(':status', $status, ParameterType::INTEGER);
- }
- elseif ($status === '')
- {
- $query->where($db->quoteName('s.published') . ' IN (0, 1)');
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName('s.title') . ' LIKE :search1 OR ' . $db->quoteName('s.description') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
-
- // Add the list ordering clause.
- $query->order($db->escape($this->getState('list.ordering', 's.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
-
- return $query;
- }
-
- /**
- * Returns a workflow object
- *
- * @return object The workflow
- *
- * @since 4.0.0
- */
- public function getWorkflow()
- {
- $table = $this->getTable('Workflow', 'Administrator');
-
- $workflowId = (int) $this->getState('filter.workflow_id');
-
- if ($workflowId > 0)
- {
- $table->load($workflowId);
- }
-
- return (object) $table->getProperties();
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @see JController
+ * @since 4.0.0
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 's.id',
+ 'title', 's.title',
+ 'ordering','s.ordering',
+ 'published', 's.published'
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function populateState($ordering = 's.ordering', $direction = 'ASC')
+ {
+ $app = Factory::getApplication();
+
+ $workflowID = $app->getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int');
+ $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
+
+ if ($workflowID) {
+ $table = $this->getTable('Workflow', 'Administrator');
+
+ if ($table->load($workflowID)) {
+ $this->setState('active_workflow', $table->title);
+ }
+ }
+
+ $this->setState('filter.workflow_id', $workflowID);
+ $this->setState('filter.extension', $extension);
+
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 4.0.0
+ */
+ protected function getReorderConditions($table)
+ {
+ return [
+ $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id,
+ ];
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\Table\Table A Table object
+ *
+ * @since 4.0.0
+ */
+ public function getTable($type = 'Stage', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return string The query to database.
+ *
+ * @since 4.0.0
+ */
+ public function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select(
+ [
+ $db->quoteName('s.id'),
+ $db->quoteName('s.title'),
+ $db->quoteName('s.ordering'),
+ $db->quoteName('s.default'),
+ $db->quoteName('s.published'),
+ $db->quoteName('s.checked_out'),
+ $db->quoteName('s.checked_out_time'),
+ $db->quoteName('s.description'),
+ $db->quoteName('uc.name', 'editor'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_stages', 's'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('s.checked_out'));
+
+ // Filter by extension
+ if ($workflowID = (int) $this->getState('filter.workflow_id')) {
+ $query->where($db->quoteName('s.workflow_id') . ' = :id')
+ ->bind(':id', $workflowID, ParameterType::INTEGER);
+ }
+
+ $status = (string) $this->getState('filter.published');
+
+ // Filter by publish state
+ if (is_numeric($status)) {
+ $status = (int) $status;
+ $query->where($db->quoteName('s.published') . ' = :status')
+ ->bind(':status', $status, ParameterType::INTEGER);
+ } elseif ($status === '') {
+ $query->where($db->quoteName('s.published') . ' IN (0, 1)');
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName('s.title') . ' LIKE :search1 OR ' . $db->quoteName('s.description') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+
+ // Add the list ordering clause.
+ $query->order($db->escape($this->getState('list.ordering', 's.ordering')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
+
+ return $query;
+ }
+
+ /**
+ * Returns a workflow object
+ *
+ * @return object The workflow
+ *
+ * @since 4.0.0
+ */
+ public function getWorkflow()
+ {
+ $table = $this->getTable('Workflow', 'Administrator');
+
+ $workflowId = (int) $this->getState('filter.workflow_id');
+
+ if ($workflowId > 0) {
+ $table->load($workflowId);
+ }
+
+ return (object) $table->getProperties();
+ }
}
diff --git a/administrator/components/com_workflow/src/Model/TransitionModel.php b/administrator/components/com_workflow/src/Model/TransitionModel.php
index 1e9708ccd5cc2..58df72b2879e3 100644
--- a/administrator/components/com_workflow/src/Model/TransitionModel.php
+++ b/administrator/components/com_workflow/src/Model/TransitionModel.php
@@ -1,4 +1,5 @@
option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
-
- $this->setState('filter.extension', $extension);
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
- *
- * @since 4.0.0
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- $app = Factory::getApplication();
- $extension = $app->getUserStateFromRequest('com_workflow.transition.filter.extension', 'extension', null, 'cmd');
-
- return Factory::getUser()->authorise('core.delete', $extension . '.transition.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 4.0.0
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
- $app = Factory::getApplication();
- $context = $this->option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
-
- if (!\property_exists($record, 'workflow_id'))
- {
- $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
- $record->workflow_id = $workflowID;
- }
-
- // Check for existing workflow.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $extension . '.transition.' . (int) $record->id);
- }
-
- // Default to component settings if workflow isn't known.
- return $user->authorise('core.edit.state', $extension);
- }
-
- /**
- * Method to get a single record.
- *
- * @param integer $pk The id of the primary key.
- *
- * @return \Joomla\CMS\Object\CMSObject|boolean Object on success, false on failure.
- *
- * @since 4.0.0
- */
- public function getItem($pk = null)
- {
- $item = parent::getItem($pk);
-
- if (property_exists($item, 'options'))
- {
- $registry = new Registry($item->options);
- $item->options = $registry->toArray();
- }
-
- return $item;
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function save($data)
- {
- $table = $this->getTable();
- $context = $this->option . '.' . $this->name;
- $app = Factory::getApplication();
- $user = $app->getIdentity();
- $input = $app->input;
-
- $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
-
- if (empty($data['workflow_id']))
- {
- $data['workflow_id'] = $workflowID;
- }
-
- $workflow = $this->getTable('Workflow');
-
- $workflow->load($data['workflow_id']);
-
- $parts = explode('.', $workflow->extension);
-
- if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0]))
- {
- unset($data['rules']);
- }
-
- // Make sure we use the correct workflow_id when editing an existing transition
- $key = $table->getKeyName();
- $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
-
- if ($pk > 0)
- {
- $table->load($pk);
-
- if ((int) $table->workflow_id)
- {
- $data['workflow_id'] = (int) $table->workflow_id;
- }
- }
-
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
-
- // Alter the title for save as copy
- if ($origTable->load(['title' => $data['title']]))
- {
- list($title) = $this->generateNewTitle(0, '', $data['title']);
- $data['title'] = $title;
- }
-
- $data['published'] = 0;
- }
-
- return parent::save($data);
- }
-
- /**
- * Method to change the title
- *
- * @param integer $categoryId The id of the category.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 4.0.0
- */
- protected function generateNewTitle($categoryId, $alias, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('title' => $title)))
- {
- $title = StringHelper::increment($title);
- }
-
- return array($title, $alias);
- }
-
- /**
- * Abstract method for getting the form from the model.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
- *
- * @since 4.0.0
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm(
- 'com_workflow.transition',
- 'transition',
- array(
- 'control' => 'jform',
- 'load_data' => $loadData
- )
- );
-
- if (empty($form))
- {
- return false;
- }
-
- $id = $data['id'] ?? $form->getValue('id');
-
- $item = $this->getItem($id);
-
- $canEditState = $this->canEditState((object) $item);
-
- // Modify the form based on access controls.
- if (!$canEditState)
- {
- $form->setFieldAttribute('published', 'disabled', 'true');
- $form->setFieldAttribute('published', 'required', 'false');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- if (!empty($item->workflow_id))
- {
- $data['workflow_id'] = (int) $item->workflow_id;
- }
-
- if (empty($data['workflow_id']))
- {
- $context = $this->option . '.' . $this->name;
-
- $data['workflow_id'] = (int) Factory::getApplication()->getUserStateFromRequest(
- $context . '.filter.workflow_id', 'workflow_id',
- 0,
- 'int'
- );
- }
-
- $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $data['workflow_id'];
- $where .= ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1';
-
- $form->setFieldAttribute('from_stage_id', 'sql_where', $where);
- $form->setFieldAttribute('to_stage_id', 'sql_where', $where);
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 4.0.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState(
- 'com_workflow.edit.transition.data',
- array()
- );
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- return $data;
- }
-
- public function getWorkflow()
- {
- $app = Factory::getApplication();
-
- $context = $this->option . '.' . $this->name;
-
- $workflow_id = (int) $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
-
- $workflow = $this->getTable('Workflow');
-
- $workflow->load($workflow_id);
-
- return (object) $workflow->getProperties();
- }
-
- /**
- * Trigger the form preparation for the workflow group
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @see FormField
- * @since 4.0.0
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $extension = Factory::getApplication()->input->get('extension');
-
- $parts = explode('.', $extension);
-
- $extension = array_shift($parts);
-
- // Set the access control rules field component value.
- $form->setFieldAttribute('rules', 'component', $extension);
-
- // Import the appropriate plugin group.
- PluginHelper::importPlugin('workflow');
-
- parent::preprocessForm($form, $data, $group);
- }
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function populateState()
+ {
+ parent::populateState();
+
+ $app = Factory::getApplication();
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ $app = Factory::getApplication();
+ $extension = $app->getUserStateFromRequest('com_workflow.transition.filter.extension', 'extension', null, 'cmd');
+
+ return Factory::getUser()->authorise('core.delete', $extension . '.transition.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+ $app = Factory::getApplication();
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+
+ if (!\property_exists($record, 'workflow_id')) {
+ $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
+ $record->workflow_id = $workflowID;
+ }
+
+ // Check for existing workflow.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $extension . '.transition.' . (int) $record->id);
+ }
+
+ // Default to component settings if workflow isn't known.
+ return $user->authorise('core.edit.state', $extension);
+ }
+
+ /**
+ * Method to get a single record.
+ *
+ * @param integer $pk The id of the primary key.
+ *
+ * @return \Joomla\CMS\Object\CMSObject|boolean Object on success, false on failure.
+ *
+ * @since 4.0.0
+ */
+ public function getItem($pk = null)
+ {
+ $item = parent::getItem($pk);
+
+ if (property_exists($item, 'options')) {
+ $registry = new Registry($item->options);
+ $item->options = $registry->toArray();
+ }
+
+ return $item;
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+ $context = $this->option . '.' . $this->name;
+ $app = Factory::getApplication();
+ $user = $app->getIdentity();
+ $input = $app->input;
+
+ $workflowID = $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
+
+ if (empty($data['workflow_id'])) {
+ $data['workflow_id'] = $workflowID;
+ }
+
+ $workflow = $this->getTable('Workflow');
+
+ $workflow->load($data['workflow_id']);
+
+ $parts = explode('.', $workflow->extension);
+
+ if (isset($data['rules']) && !$user->authorise('core.admin', $parts[0])) {
+ unset($data['rules']);
+ }
+
+ // Make sure we use the correct workflow_id when editing an existing transition
+ $key = $table->getKeyName();
+ $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
+
+ if ($pk > 0) {
+ $table->load($pk);
+
+ if ((int) $table->workflow_id) {
+ $data['workflow_id'] = (int) $table->workflow_id;
+ }
+ }
+
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+
+ // Alter the title for save as copy
+ if ($origTable->load(['title' => $data['title']])) {
+ list($title) = $this->generateNewTitle(0, '', $data['title']);
+ $data['title'] = $title;
+ }
+
+ $data['published'] = 0;
+ }
+
+ return parent::save($data);
+ }
+
+ /**
+ * Method to change the title
+ *
+ * @param integer $categoryId The id of the category.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 4.0.0
+ */
+ protected function generateNewTitle($categoryId, $alias, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('title' => $title))) {
+ $title = StringHelper::increment($title);
+ }
+
+ return array($title, $alias);
+ }
+
+ /**
+ * Abstract method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
+ *
+ * @since 4.0.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_workflow.transition',
+ 'transition',
+ array(
+ 'control' => 'jform',
+ 'load_data' => $loadData
+ )
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $id = $data['id'] ?? $form->getValue('id');
+
+ $item = $this->getItem($id);
+
+ $canEditState = $this->canEditState((object) $item);
+
+ // Modify the form based on access controls.
+ if (!$canEditState) {
+ $form->setFieldAttribute('published', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'required', 'false');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ if (!empty($item->workflow_id)) {
+ $data['workflow_id'] = (int) $item->workflow_id;
+ }
+
+ if (empty($data['workflow_id'])) {
+ $context = $this->option . '.' . $this->name;
+
+ $data['workflow_id'] = (int) Factory::getApplication()->getUserStateFromRequest(
+ $context . '.filter.workflow_id',
+ 'workflow_id',
+ 0,
+ 'int'
+ );
+ }
+
+ $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $data['workflow_id'];
+ $where .= ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1';
+
+ $form->setFieldAttribute('from_stage_id', 'sql_where', $where);
+ $form->setFieldAttribute('to_stage_id', 'sql_where', $where);
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 4.0.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState(
+ 'com_workflow.edit.transition.data',
+ array()
+ );
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ return $data;
+ }
+
+ public function getWorkflow()
+ {
+ $app = Factory::getApplication();
+
+ $context = $this->option . '.' . $this->name;
+
+ $workflow_id = (int) $app->getUserStateFromRequest($context . '.filter.workflow_id', 'workflow_id', 0, 'int');
+
+ $workflow = $this->getTable('Workflow');
+
+ $workflow->load($workflow_id);
+
+ return (object) $workflow->getProperties();
+ }
+
+ /**
+ * Trigger the form preparation for the workflow group
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @see FormField
+ * @since 4.0.0
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $extension = Factory::getApplication()->input->get('extension');
+
+ $parts = explode('.', $extension);
+
+ $extension = array_shift($parts);
+
+ // Set the access control rules field component value.
+ $form->setFieldAttribute('rules', 'component', $extension);
+
+ // Import the appropriate plugin group.
+ PluginHelper::importPlugin('workflow');
+
+ parent::preprocessForm($form, $data, $group);
+ }
}
diff --git a/administrator/components/com_workflow/src/Model/TransitionsModel.php b/administrator/components/com_workflow/src/Model/TransitionsModel.php
index 8ecf6afd2ea3e..53b178bc67dd6 100644
--- a/administrator/components/com_workflow/src/Model/TransitionsModel.php
+++ b/administrator/components/com_workflow/src/Model/TransitionsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int');
- $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
-
- if ($workflowID)
- {
- $table = $this->getTable('Workflow', 'Administrator');
-
- if ($table->load($workflowID))
- {
- $this->setState('active_workflow', $table->title);
- }
- }
-
- $this->setState('filter.workflow_id', $workflowID);
- $this->setState('filter.extension', $extension);
-
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\Table\Table A Table object
- *
- * @since 4.0.0
- */
- public function getTable($type = 'Transition', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 4.0.0
- */
- protected function getReorderConditions($table)
- {
- return [
- $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id,
- ];
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return string The query to database.
- *
- * @since 4.0.0
- */
- public function getListQuery()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query
- ->select(
- [
- $db->quoteName('t.id'),
- $db->quoteName('t.title'),
- $db->quoteName('t.from_stage_id'),
- $db->quoteName('t.to_stage_id'),
- $db->quoteName('t.published'),
- $db->quoteName('t.checked_out'),
- $db->quoteName('t.checked_out_time'),
- $db->quoteName('t.ordering'),
- $db->quoteName('t.description'),
- $db->quoteName('f_stage.title', 'from_stage'),
- $db->quoteName('t_stage.title', 'to_stage'),
- $db->quoteName('uc.name', 'editor'),
- ]
- )
- ->from($db->quoteName('#__workflow_transitions', 't'))
- ->join('LEFT', $db->quoteName('#__workflow_stages', 'f_stage'), $db->quoteName('f_stage.id') . ' = ' . $db->quoteName('t.from_stage_id'))
- ->join('LEFT', $db->quoteName('#__workflow_stages', 't_stage'), $db->quoteName('t_stage.id') . ' = ' . $db->quoteName('t.to_stage_id'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('t.checked_out'));
-
- // Filter by extension
- if ($workflowID = (int) $this->getState('filter.workflow_id'))
- {
- $query->where($db->quoteName('t.workflow_id') . ' = :id')
- ->bind(':id', $workflowID, ParameterType::INTEGER);
- }
-
- $status = (string) $this->getState('filter.published');
-
- // Filter by status
- if (is_numeric($status))
- {
- $status = (int) $status;
- $query->where($db->quoteName('t.published') . ' = :status')
- ->bind(':status', $status, ParameterType::INTEGER);
- }
- elseif ($status === '')
- {
- $query->where($db->quoteName('t.published') . ' IN (0, 1)');
- }
-
- // Filter by column from_stage_id
- if ($fromStage = (int) $this->getState('filter.from_stage'))
- {
- $query->where($db->quoteName('from_stage_id') . ' = :fromStage')
- ->bind(':fromStage', $fromStage, ParameterType::INTEGER);
- }
-
- // Filter by column to_stage_id
- if ($toStage = (int) $this->getState('filter.to_stage'))
- {
- $query->where($db->quoteName('to_stage_id') . ' = :toStage')
- ->bind(':toStage', $toStage, ParameterType::INTEGER);
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName('t.title') . ' LIKE :search1 OR ' . $db->quoteName('t.description') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 't.id');
- $orderDirn = strtoupper($this->state->get('list.direction', 'ASC'));
-
- $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC'));
-
- return $query;
- }
-
- /**
- * Get the filter form
- *
- * @param array $data data
- * @param boolean $loadData load current data
- *
- * @return \Joomla\CMS\Form\Form|boolean The Form object or false on error
- *
- * @since 4.0.0
- */
- public function getFilterForm($data = array(), $loadData = true)
- {
- $form = parent::getFilterForm($data, $loadData);
-
- $id = (int) $this->getState('filter.workflow_id');
-
- if ($form)
- {
- $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . $id . ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1';
-
- $form->setFieldAttribute('from_stage', 'sql_where', $where, 'filter');
- $form->setFieldAttribute('to_stage', 'sql_where', $where, 'filter');
- }
-
- return $form;
- }
-
- /**
- * Returns a workflow object
- *
- * @return object The workflow
- *
- * @since 4.0.0
- */
- public function getWorkflow()
- {
- $table = $this->getTable('Workflow', 'Administrator');
-
- $workflowId = (int) $this->getState('filter.workflow_id');
-
- if ($workflowId > 0)
- {
- $table->load($workflowId);
- }
-
- return (object) $table->getProperties();
- }
-
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @see JController
+ * @since 4.0.0
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 't.id',
+ 'published', 't.published',
+ 'ordering', 't.ordering',
+ 'title', 't.title',
+ 'from_stage', 't.from_stage_id',
+ 'to_stage', 't.to_stage_id'
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function populateState($ordering = 't.ordering', $direction = 'ASC')
+ {
+ $app = Factory::getApplication();
+ $workflowID = $app->getUserStateFromRequest($this->context . '.filter.workflow_id', 'workflow_id', 1, 'int');
+ $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
+
+ if ($workflowID) {
+ $table = $this->getTable('Workflow', 'Administrator');
+
+ if ($table->load($workflowID)) {
+ $this->setState('active_workflow', $table->title);
+ }
+ }
+
+ $this->setState('filter.workflow_id', $workflowID);
+ $this->setState('filter.extension', $extension);
+
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\Table\Table A Table object
+ *
+ * @since 4.0.0
+ */
+ public function getTable($type = 'Transition', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 4.0.0
+ */
+ protected function getReorderConditions($table)
+ {
+ return [
+ $this->getDatabase()->quoteName('workflow_id') . ' = ' . (int) $table->workflow_id,
+ ];
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return string The query to database.
+ *
+ * @since 4.0.0
+ */
+ public function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select(
+ [
+ $db->quoteName('t.id'),
+ $db->quoteName('t.title'),
+ $db->quoteName('t.from_stage_id'),
+ $db->quoteName('t.to_stage_id'),
+ $db->quoteName('t.published'),
+ $db->quoteName('t.checked_out'),
+ $db->quoteName('t.checked_out_time'),
+ $db->quoteName('t.ordering'),
+ $db->quoteName('t.description'),
+ $db->quoteName('f_stage.title', 'from_stage'),
+ $db->quoteName('t_stage.title', 'to_stage'),
+ $db->quoteName('uc.name', 'editor'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_transitions', 't'))
+ ->join('LEFT', $db->quoteName('#__workflow_stages', 'f_stage'), $db->quoteName('f_stage.id') . ' = ' . $db->quoteName('t.from_stage_id'))
+ ->join('LEFT', $db->quoteName('#__workflow_stages', 't_stage'), $db->quoteName('t_stage.id') . ' = ' . $db->quoteName('t.to_stage_id'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('t.checked_out'));
+
+ // Filter by extension
+ if ($workflowID = (int) $this->getState('filter.workflow_id')) {
+ $query->where($db->quoteName('t.workflow_id') . ' = :id')
+ ->bind(':id', $workflowID, ParameterType::INTEGER);
+ }
+
+ $status = (string) $this->getState('filter.published');
+
+ // Filter by status
+ if (is_numeric($status)) {
+ $status = (int) $status;
+ $query->where($db->quoteName('t.published') . ' = :status')
+ ->bind(':status', $status, ParameterType::INTEGER);
+ } elseif ($status === '') {
+ $query->where($db->quoteName('t.published') . ' IN (0, 1)');
+ }
+
+ // Filter by column from_stage_id
+ if ($fromStage = (int) $this->getState('filter.from_stage')) {
+ $query->where($db->quoteName('from_stage_id') . ' = :fromStage')
+ ->bind(':fromStage', $fromStage, ParameterType::INTEGER);
+ }
+
+ // Filter by column to_stage_id
+ if ($toStage = (int) $this->getState('filter.to_stage')) {
+ $query->where($db->quoteName('to_stage_id') . ' = :toStage')
+ ->bind(':toStage', $toStage, ParameterType::INTEGER);
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName('t.title') . ' LIKE :search1 OR ' . $db->quoteName('t.description') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 't.id');
+ $orderDirn = strtoupper($this->state->get('list.direction', 'ASC'));
+
+ $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC'));
+
+ return $query;
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return \Joomla\CMS\Form\Form|boolean The Form object or false on error
+ *
+ * @since 4.0.0
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ $id = (int) $this->getState('filter.workflow_id');
+
+ if ($form) {
+ $where = $this->getDatabase()->quoteName('workflow_id') . ' = ' . $id . ' AND ' . $this->getDatabase()->quoteName('published') . ' = 1';
+
+ $form->setFieldAttribute('from_stage', 'sql_where', $where, 'filter');
+ $form->setFieldAttribute('to_stage', 'sql_where', $where, 'filter');
+ }
+
+ return $form;
+ }
+
+ /**
+ * Returns a workflow object
+ *
+ * @return object The workflow
+ *
+ * @since 4.0.0
+ */
+ public function getWorkflow()
+ {
+ $table = $this->getTable('Workflow', 'Administrator');
+
+ $workflowId = (int) $this->getState('filter.workflow_id');
+
+ if ($workflowId > 0) {
+ $table->load($workflowId);
+ }
+
+ return (object) $table->getProperties();
+ }
}
diff --git a/administrator/components/com_workflow/src/Model/WorkflowModel.php b/administrator/components/com_workflow/src/Model/WorkflowModel.php
index 6e232db185091..e361ef837b576 100644
--- a/administrator/components/com_workflow/src/Model/WorkflowModel.php
+++ b/administrator/components/com_workflow/src/Model/WorkflowModel.php
@@ -1,4 +1,5 @@
option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
-
- $this->setState('filter.extension', $extension);
- }
-
- /**
- * Method to change the title
- *
- * @param integer $categoryId The id of the category.
- * @param string $alias The alias.
- * @param string $title The title.
- *
- * @return array Contains the modified title and alias.
- *
- * @since 4.0.0
- */
- protected function generateNewTitle($categoryId, $alias, $title)
- {
- // Alter the title & alias
- $table = $this->getTable();
-
- while ($table->load(array('title' => $title)))
- {
- $title = StringHelper::increment($title);
- }
-
- return array($title, $alias);
- }
-
- /**
- * Method to save the form data.
- *
- * @param array $data The form data.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function save($data)
- {
- $table = $this->getTable();
- $app = Factory::getApplication();
- $user = $app->getIdentity();
- $input = $app->input;
- $context = $this->option . '.' . $this->name;
- $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
- $data['extension'] = !empty($data['extension']) ? $data['extension'] : $extension;
-
- // Make sure we use the correct extension when editing an existing workflow
- $key = $table->getKeyName();
- $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
-
- if ($pk > 0)
- {
- $table->load($pk);
-
- $data['extension'] = $table->extension;
- }
-
- if (isset($data['rules']) && !$user->authorise('core.admin', $data['extension']))
- {
- unset($data['rules']);
- }
-
- if ($input->get('task') == 'save2copy')
- {
- $origTable = clone $this->getTable();
-
- // Alter the title for save as copy
- if ($origTable->load(['title' => $data['title']]))
- {
- list($title) = $this->generateNewTitle(0, '', $data['title']);
- $data['title'] = $title;
- }
-
- // Unpublish new copy
- $data['published'] = 0;
- $data['default'] = 0;
- }
-
- $result = parent::save($data);
-
- // Create default stage for new workflow
- if ($result && $input->getCmd('task') !== 'save2copy' && $this->getState($this->getName() . '.new'))
- {
- $workflow_id = (int) $this->getState($this->getName() . '.id');
-
- $table = $this->getTable('Stage');
-
- $table->id = 0;
- $table->title = 'COM_WORKFLOW_BASIC_STAGE';
- $table->description = '';
- $table->workflow_id = $workflow_id;
- $table->published = 1;
- $table->default = 1;
-
- $table->store();
- }
-
- return $result;
- }
-
- /**
- * Abstract method for getting the form from the model.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
- *
- * @since 4.0.0
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm(
- 'com_workflow.workflow',
- 'workflow',
- array(
- 'control' => 'jform',
- 'load_data' => $loadData
- )
- );
-
- if (empty($form))
- {
- return false;
- }
-
- $id = $data['id'] ?? $form->getValue('id');
-
- $item = $this->getItem($id);
-
- $canEditState = $this->canEditState((object) $item);
-
- // Modify the form based on access controls.
- if (!$canEditState || !empty($item->default))
- {
- if (!$canEditState)
- {
- $form->setFieldAttribute('published', 'disabled', 'true');
- $form->setFieldAttribute('published', 'required', 'false');
- $form->setFieldAttribute('published', 'filter', 'unset');
- }
-
- $form->setFieldAttribute('default', 'disabled', 'true');
- $form->setFieldAttribute('default', 'required', 'false');
- $form->setFieldAttribute('default', 'filter', 'unset');
- }
-
- $form->setFieldAttribute('created', 'default', Factory::getDate()->format('Y-m-d H:i:s'));
- $form->setFieldAttribute('modified', 'default', Factory::getDate()->format('Y-m-d H:i:s'));
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return mixed The data for the form.
- *
- * @since 4.0.0
- */
- protected function loadFormData()
- {
- // Check the session for previously entered form data.
- $data = Factory::getApplication()->getUserState(
- 'com_workflow.edit.workflow.data',
- array()
- );
-
- if (empty($data))
- {
- $data = $this->getItem();
- }
-
- return $data;
- }
-
- /**
- * Method to preprocess the form.
- *
- * @param Form $form Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $extension = Factory::getApplication()->input->get('extension');
-
- $parts = explode('.', $extension);
-
- $extension = array_shift($parts);
-
- // Set the access control rules field component value.
- $form->setFieldAttribute('rules', 'component', $extension);
-
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * A protected method to get a set of ordering conditions.
- *
- * @param object $table A record object.
- *
- * @return array An array of conditions to add to ordering queries.
- *
- * @since 4.0.0
- */
- protected function getReorderConditions($table)
- {
- $db = $this->getDatabase();
-
- return [
- $db->quoteName('extension') . ' = ' . $db->quote($table->extension),
- ];
- }
-
- /**
- * Method to change the default state of one item.
- *
- * @param array $pk A list of the primary keys to change.
- * @param integer $value The value of the home state.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setDefault($pk, $value = 1)
- {
- $table = $this->getTable();
-
- if ($table->load($pk))
- {
- if ($table->published !== 1)
- {
- $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
-
- return false;
- }
- }
-
- if (empty($table->id) || !$this->canEditState($table))
- {
- Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- $date = Factory::getDate()->toSql();
-
- if ($value)
- {
- // Unset other default item
- if ($table->load(
- [
- 'default' => '1',
- 'extension' => $table->get('extension')
- ]
- ))
- {
- $table->default = 0;
- $table->modified = $date;
- $table->store();
- }
- }
-
- if ($table->load($pk))
- {
- $table->modified = $date;
- $table->default = $value;
- $table->store();
- }
-
- // Clean the cache
- $this->cleanCache();
-
- return true;
- }
-
- /**
- * Method to test whether a record can be deleted.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
- *
- * @since 4.0.0
- */
- protected function canDelete($record)
- {
- if (empty($record->id) || $record->published != -2)
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.delete', $record->extension . '.workflow.' . (int) $record->id);
- }
-
- /**
- * Method to test whether a record can have its state changed.
- *
- * @param object $record A record object.
- *
- * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
- *
- * @since 4.0.0
- */
- protected function canEditState($record)
- {
- $user = Factory::getUser();
-
- // Check for existing workflow.
- if (!empty($record->id))
- {
- return $user->authorise('core.edit.state', $record->extension . '.workflow.' . (int) $record->id);
- }
-
- // Default to component settings if workflow isn't known.
- return $user->authorise('core.edit.state', $record->extension);
- }
-
- /**
- * Method to change the published state of one or more records.
- *
- * @param array &$pks A list of the primary keys to change.
- * @param integer $value The value of the published state.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function publish(&$pks, $value = 1)
- {
- $table = $this->getTable();
- $pks = (array) $pks;
-
- $date = Factory::getDate()->toSql();
-
- // Default workflow item check.
- foreach ($pks as $i => $pk)
- {
- if ($table->load($pk) && $value != 1 && $table->default)
- {
- // Prune items that you can't change.
- Factory::getApplication()->enqueueMessage(Text::_('COM_WORKFLOW_UNPUBLISH_DEFAULT_ERROR'), 'error');
- unset($pks[$i]);
- break;
- }
- }
-
- // Clean the cache.
- $this->cleanCache();
-
- // Ensure that previous checks don't empty the array.
- if (empty($pks))
- {
- return true;
- }
-
- $table->load($pk);
- $table->modified = $date;
- $table->store();
-
- return parent::publish($pks, $value);
- }
+ /**
+ * Auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function populateState()
+ {
+ parent::populateState();
+
+ $app = Factory::getApplication();
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ }
+
+ /**
+ * Method to change the title
+ *
+ * @param integer $categoryId The id of the category.
+ * @param string $alias The alias.
+ * @param string $title The title.
+ *
+ * @return array Contains the modified title and alias.
+ *
+ * @since 4.0.0
+ */
+ protected function generateNewTitle($categoryId, $alias, $title)
+ {
+ // Alter the title & alias
+ $table = $this->getTable();
+
+ while ($table->load(array('title' => $title))) {
+ $title = StringHelper::increment($title);
+ }
+
+ return array($title, $alias);
+ }
+
+ /**
+ * Method to save the form data.
+ *
+ * @param array $data The form data.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function save($data)
+ {
+ $table = $this->getTable();
+ $app = Factory::getApplication();
+ $user = $app->getIdentity();
+ $input = $app->input;
+ $context = $this->option . '.' . $this->name;
+ $extension = $app->getUserStateFromRequest($context . '.filter.extension', 'extension', null, 'cmd');
+ $data['extension'] = !empty($data['extension']) ? $data['extension'] : $extension;
+
+ // Make sure we use the correct extension when editing an existing workflow
+ $key = $table->getKeyName();
+ $pk = (isset($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
+
+ if ($pk > 0) {
+ $table->load($pk);
+
+ $data['extension'] = $table->extension;
+ }
+
+ if (isset($data['rules']) && !$user->authorise('core.admin', $data['extension'])) {
+ unset($data['rules']);
+ }
+
+ if ($input->get('task') == 'save2copy') {
+ $origTable = clone $this->getTable();
+
+ // Alter the title for save as copy
+ if ($origTable->load(['title' => $data['title']])) {
+ list($title) = $this->generateNewTitle(0, '', $data['title']);
+ $data['title'] = $title;
+ }
+
+ // Unpublish new copy
+ $data['published'] = 0;
+ $data['default'] = 0;
+ }
+
+ $result = parent::save($data);
+
+ // Create default stage for new workflow
+ if ($result && $input->getCmd('task') !== 'save2copy' && $this->getState($this->getName() . '.new')) {
+ $workflow_id = (int) $this->getState($this->getName() . '.id');
+
+ $table = $this->getTable('Stage');
+
+ $table->id = 0;
+ $table->title = 'COM_WORKFLOW_BASIC_STAGE';
+ $table->description = '';
+ $table->workflow_id = $workflow_id;
+ $table->published = 1;
+ $table->default = 1;
+
+ $table->store();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Abstract method for getting the form from the model.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return \Joomla\CMS\Form\Form|boolean A Form object on success, false on failure
+ *
+ * @since 4.0.0
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm(
+ 'com_workflow.workflow',
+ 'workflow',
+ array(
+ 'control' => 'jform',
+ 'load_data' => $loadData
+ )
+ );
+
+ if (empty($form)) {
+ return false;
+ }
+
+ $id = $data['id'] ?? $form->getValue('id');
+
+ $item = $this->getItem($id);
+
+ $canEditState = $this->canEditState((object) $item);
+
+ // Modify the form based on access controls.
+ if (!$canEditState || !empty($item->default)) {
+ if (!$canEditState) {
+ $form->setFieldAttribute('published', 'disabled', 'true');
+ $form->setFieldAttribute('published', 'required', 'false');
+ $form->setFieldAttribute('published', 'filter', 'unset');
+ }
+
+ $form->setFieldAttribute('default', 'disabled', 'true');
+ $form->setFieldAttribute('default', 'required', 'false');
+ $form->setFieldAttribute('default', 'filter', 'unset');
+ }
+
+ $form->setFieldAttribute('created', 'default', Factory::getDate()->format('Y-m-d H:i:s'));
+ $form->setFieldAttribute('modified', 'default', Factory::getDate()->format('Y-m-d H:i:s'));
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return mixed The data for the form.
+ *
+ * @since 4.0.0
+ */
+ protected function loadFormData()
+ {
+ // Check the session for previously entered form data.
+ $data = Factory::getApplication()->getUserState(
+ 'com_workflow.edit.workflow.data',
+ array()
+ );
+
+ if (empty($data)) {
+ $data = $this->getItem();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Method to preprocess the form.
+ *
+ * @param Form $form Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $extension = Factory::getApplication()->input->get('extension');
+
+ $parts = explode('.', $extension);
+
+ $extension = array_shift($parts);
+
+ // Set the access control rules field component value.
+ $form->setFieldAttribute('rules', 'component', $extension);
+
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * A protected method to get a set of ordering conditions.
+ *
+ * @param object $table A record object.
+ *
+ * @return array An array of conditions to add to ordering queries.
+ *
+ * @since 4.0.0
+ */
+ protected function getReorderConditions($table)
+ {
+ $db = $this->getDatabase();
+
+ return [
+ $db->quoteName('extension') . ' = ' . $db->quote($table->extension),
+ ];
+ }
+
+ /**
+ * Method to change the default state of one item.
+ *
+ * @param array $pk A list of the primary keys to change.
+ * @param integer $value The value of the home state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setDefault($pk, $value = 1)
+ {
+ $table = $this->getTable();
+
+ if ($table->load($pk)) {
+ if ($table->published !== 1) {
+ $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
+
+ return false;
+ }
+ }
+
+ if (empty($table->id) || !$this->canEditState($table)) {
+ Log::add(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ $date = Factory::getDate()->toSql();
+
+ if ($value) {
+ // Unset other default item
+ if (
+ $table->load(
+ [
+ 'default' => '1',
+ 'extension' => $table->get('extension')
+ ]
+ )
+ ) {
+ $table->default = 0;
+ $table->modified = $date;
+ $table->store();
+ }
+ }
+
+ if ($table->load($pk)) {
+ $table->modified = $date;
+ $table->default = $value;
+ $table->store();
+ }
+
+ // Clean the cache
+ $this->cleanCache();
+
+ return true;
+ }
+
+ /**
+ * Method to test whether a record can be deleted.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to delete the record. Defaults to the permission for the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canDelete($record)
+ {
+ if (empty($record->id) || $record->published != -2) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.delete', $record->extension . '.workflow.' . (int) $record->id);
+ }
+
+ /**
+ * Method to test whether a record can have its state changed.
+ *
+ * @param object $record A record object.
+ *
+ * @return boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
+ *
+ * @since 4.0.0
+ */
+ protected function canEditState($record)
+ {
+ $user = Factory::getUser();
+
+ // Check for existing workflow.
+ if (!empty($record->id)) {
+ return $user->authorise('core.edit.state', $record->extension . '.workflow.' . (int) $record->id);
+ }
+
+ // Default to component settings if workflow isn't known.
+ return $user->authorise('core.edit.state', $record->extension);
+ }
+
+ /**
+ * Method to change the published state of one or more records.
+ *
+ * @param array &$pks A list of the primary keys to change.
+ * @param integer $value The value of the published state.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function publish(&$pks, $value = 1)
+ {
+ $table = $this->getTable();
+ $pks = (array) $pks;
+
+ $date = Factory::getDate()->toSql();
+
+ // Default workflow item check.
+ foreach ($pks as $i => $pk) {
+ if ($table->load($pk) && $value != 1 && $table->default) {
+ // Prune items that you can't change.
+ Factory::getApplication()->enqueueMessage(Text::_('COM_WORKFLOW_UNPUBLISH_DEFAULT_ERROR'), 'error');
+ unset($pks[$i]);
+ break;
+ }
+ }
+
+ // Clean the cache.
+ $this->cleanCache();
+
+ // Ensure that previous checks don't empty the array.
+ if (empty($pks)) {
+ return true;
+ }
+
+ $table->load($pk);
+ $table->modified = $date;
+ $table->store();
+
+ return parent::publish($pks, $value);
+ }
}
diff --git a/administrator/components/com_workflow/src/Model/WorkflowsModel.php b/administrator/components/com_workflow/src/Model/WorkflowsModel.php
index 9fcd14fbc68af..b0f460e510e44 100644
--- a/administrator/components/com_workflow/src/Model/WorkflowsModel.php
+++ b/administrator/components/com_workflow/src/Model/WorkflowsModel.php
@@ -1,4 +1,5 @@
getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
-
- $this->setState('filter.extension', $extension);
- $parts = explode('.', $extension);
-
- // Extract the component name
- $this->setState('filter.component', $parts[0]);
-
- // Extract the optional section name
- $this->setState('filter.section', (count($parts) > 1) ? $parts[1] : null);
-
- parent::populateState($ordering, $direction);
- }
-
- /**
- * Method to get a table object, load it if necessary.
- *
- * @param string $type The table name. Optional.
- * @param string $prefix The class prefix. Optional.
- * @param array $config Configuration array for model. Optional.
- *
- * @return \Joomla\CMS\Table\Table A Table object
- *
- * @since 4.0.0
- */
- public function getTable($type = 'Workflow', $prefix = 'Administrator', $config = array())
- {
- return parent::getTable($type, $prefix, $config);
- }
-
- /**
- * Method to get an array of data items.
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 4.0.0
- */
- public function getItems()
- {
- $items = parent::getItems();
-
- if ($items)
- {
- $this->countItems($items);
- }
-
- return $items;
- }
-
- /**
- * Get the filter form
- *
- * @param array $data data
- * @param boolean $loadData load current data
- *
- * @return \Joomla\CMS\Form\Form|bool the Form object or false
- *
- * @since 4.0.0
- */
- public function getFilterForm($data = array(), $loadData = true)
- {
- $form = parent::getFilterForm($data, $loadData);
-
- if ($form)
- {
- $form->setValue('extension', null, $this->getState('filter.extension'));
- }
-
- return $form;
- }
-
- /**
- * Add the number of transitions and states to all workflow items
- *
- * @param array $items The workflow items
- *
- * @return mixed An array of data items on success, false on failure.
- *
- * @since 4.0.0
- */
- protected function countItems($items)
- {
- $db = $this->getDatabase();
-
- $ids = [0];
-
- foreach ($items as $item)
- {
- $ids[] = (int) $item->id;
-
- $item->count_states = 0;
- $item->count_transitions = 0;
- }
-
- $query = $db->getQuery(true);
-
- $query->select(
- [
- $db->quoteName('workflow_id'),
- 'COUNT(*) AS ' . $db->quoteName('count'),
- ]
- )
- ->from($db->quoteName('#__workflow_stages'))
- ->whereIn($db->quoteName('workflow_id'), $ids)
- ->where($db->quoteName('published') . ' >= 0')
- ->group($db->quoteName('workflow_id'));
-
- $status = $db->setQuery($query)->loadObjectList('workflow_id');
-
- $query = $db->getQuery(true);
-
- $query->select(
- [
- $db->quoteName('workflow_id'),
- 'COUNT(*) AS ' . $db->quoteName('count'),
- ]
- )
- ->from($db->quoteName('#__workflow_transitions'))
- ->whereIn($db->quoteName('workflow_id'), $ids)
- ->where($db->quoteName('published') . ' >= 0')
- ->group($db->quoteName('workflow_id'));
-
- $transitions = $db->setQuery($query)->loadObjectList('workflow_id');
-
- foreach ($items as $item)
- {
- if (isset($status[$item->id]))
- {
- $item->count_states = (int) $status[$item->id]->count;
- }
-
- if (isset($transitions[$item->id]))
- {
- $item->count_transitions = (int) $transitions[$item->id]->count;
- }
- }
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return string The query to database.
- *
- * @since 4.0.0
- */
- public function getListQuery()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->select(
- [
- $db->quoteName('w.id'),
- $db->quoteName('w.title'),
- $db->quoteName('w.created'),
- $db->quoteName('w.modified'),
- $db->quoteName('w.published'),
- $db->quoteName('w.checked_out'),
- $db->quoteName('w.checked_out_time'),
- $db->quoteName('w.ordering'),
- $db->quoteName('w.default'),
- $db->quoteName('w.created_by'),
- $db->quoteName('w.description'),
- $db->quoteName('u.name'),
- $db->quoteName('uc.name', 'editor'),
- ]
- )
- ->from($db->quoteName('#__workflows', 'w'))
- ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('w.created_by'))
- ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('w.checked_out'));
-
- // Filter by extension
- if ($extension = $this->getState('filter.extension'))
- {
- $query->where($db->quoteName('extension') . ' = :extension')
- ->bind(':extension', $extension);
- }
-
- $status = (string) $this->getState('filter.published');
-
- // Filter by status
- if (is_numeric($status))
- {
- $status = (int) $status;
- $query->where($db->quoteName('w.published') . ' = :published')
- ->bind(':published', $status, ParameterType::INTEGER);
- }
- elseif ($status === '')
- {
- $query->where($db->quoteName('w.published') . ' IN (0, 1)');
- }
-
- // Filter by search in title
- $search = $this->getState('filter.search');
-
- if (!empty($search))
- {
- $search = '%' . str_replace(' ', '%', trim($search)) . '%';
- $query->where('(' . $db->quoteName('w.title') . ' LIKE :search1 OR ' . $db->quoteName('w.description') . ' LIKE :search2)')
- ->bind([':search1', ':search2'], $search);
- }
-
- // Add the list ordering clause.
- $orderCol = $this->state->get('list.ordering', 'w.ordering');
- $orderDirn = strtoupper($this->state->get('list.direction', 'ASC'));
-
- $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC'));
-
- return $query;
- }
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @see JController
+ * @since 4.0.0
+ */
+ public function __construct($config = array())
+ {
+ if (empty($config['filter_fields'])) {
+ $config['filter_fields'] = array(
+ 'id', 'w.id',
+ 'title', 'w.title',
+ 'published', 'w.published',
+ 'created_by', 'w.created_by',
+ 'created', 'w.created',
+ 'ordering', 'w.ordering',
+ 'modified', 'w.modified',
+ 'description', 'w.description'
+ );
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Method to auto-populate the model state.
+ *
+ * This method should only be called once per instantiation and is designed
+ * to be called on the first call to the getState() method unless the model
+ * configuration flag to ignore the request is set.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @param string $ordering An optional ordering field.
+ * @param string $direction An optional direction (asc|desc).
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function populateState($ordering = 'w.ordering', $direction = 'asc')
+ {
+ $app = Factory::getApplication();
+ $extension = $app->getUserStateFromRequest($this->context . '.filter.extension', 'extension', null, 'cmd');
+
+ $this->setState('filter.extension', $extension);
+ $parts = explode('.', $extension);
+
+ // Extract the component name
+ $this->setState('filter.component', $parts[0]);
+
+ // Extract the optional section name
+ $this->setState('filter.section', (count($parts) > 1) ? $parts[1] : null);
+
+ parent::populateState($ordering, $direction);
+ }
+
+ /**
+ * Method to get a table object, load it if necessary.
+ *
+ * @param string $type The table name. Optional.
+ * @param string $prefix The class prefix. Optional.
+ * @param array $config Configuration array for model. Optional.
+ *
+ * @return \Joomla\CMS\Table\Table A Table object
+ *
+ * @since 4.0.0
+ */
+ public function getTable($type = 'Workflow', $prefix = 'Administrator', $config = array())
+ {
+ return parent::getTable($type, $prefix, $config);
+ }
+
+ /**
+ * Method to get an array of data items.
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 4.0.0
+ */
+ public function getItems()
+ {
+ $items = parent::getItems();
+
+ if ($items) {
+ $this->countItems($items);
+ }
+
+ return $items;
+ }
+
+ /**
+ * Get the filter form
+ *
+ * @param array $data data
+ * @param boolean $loadData load current data
+ *
+ * @return \Joomla\CMS\Form\Form|bool the Form object or false
+ *
+ * @since 4.0.0
+ */
+ public function getFilterForm($data = array(), $loadData = true)
+ {
+ $form = parent::getFilterForm($data, $loadData);
+
+ if ($form) {
+ $form->setValue('extension', null, $this->getState('filter.extension'));
+ }
+
+ return $form;
+ }
+
+ /**
+ * Add the number of transitions and states to all workflow items
+ *
+ * @param array $items The workflow items
+ *
+ * @return mixed An array of data items on success, false on failure.
+ *
+ * @since 4.0.0
+ */
+ protected function countItems($items)
+ {
+ $db = $this->getDatabase();
+
+ $ids = [0];
+
+ foreach ($items as $item) {
+ $ids[] = (int) $item->id;
+
+ $item->count_states = 0;
+ $item->count_transitions = 0;
+ }
+
+ $query = $db->getQuery(true);
+
+ $query->select(
+ [
+ $db->quoteName('workflow_id'),
+ 'COUNT(*) AS ' . $db->quoteName('count'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_stages'))
+ ->whereIn($db->quoteName('workflow_id'), $ids)
+ ->where($db->quoteName('published') . ' >= 0')
+ ->group($db->quoteName('workflow_id'));
+
+ $status = $db->setQuery($query)->loadObjectList('workflow_id');
+
+ $query = $db->getQuery(true);
+
+ $query->select(
+ [
+ $db->quoteName('workflow_id'),
+ 'COUNT(*) AS ' . $db->quoteName('count'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_transitions'))
+ ->whereIn($db->quoteName('workflow_id'), $ids)
+ ->where($db->quoteName('published') . ' >= 0')
+ ->group($db->quoteName('workflow_id'));
+
+ $transitions = $db->setQuery($query)->loadObjectList('workflow_id');
+
+ foreach ($items as $item) {
+ if (isset($status[$item->id])) {
+ $item->count_states = (int) $status[$item->id]->count;
+ }
+
+ if (isset($transitions[$item->id])) {
+ $item->count_transitions = (int) $transitions[$item->id]->count;
+ }
+ }
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return string The query to database.
+ *
+ * @since 4.0.0
+ */
+ public function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->select(
+ [
+ $db->quoteName('w.id'),
+ $db->quoteName('w.title'),
+ $db->quoteName('w.created'),
+ $db->quoteName('w.modified'),
+ $db->quoteName('w.published'),
+ $db->quoteName('w.checked_out'),
+ $db->quoteName('w.checked_out_time'),
+ $db->quoteName('w.ordering'),
+ $db->quoteName('w.default'),
+ $db->quoteName('w.created_by'),
+ $db->quoteName('w.description'),
+ $db->quoteName('u.name'),
+ $db->quoteName('uc.name', 'editor'),
+ ]
+ )
+ ->from($db->quoteName('#__workflows', 'w'))
+ ->join('LEFT', $db->quoteName('#__users', 'u'), $db->quoteName('u.id') . ' = ' . $db->quoteName('w.created_by'))
+ ->join('LEFT', $db->quoteName('#__users', 'uc'), $db->quoteName('uc.id') . ' = ' . $db->quoteName('w.checked_out'));
+
+ // Filter by extension
+ if ($extension = $this->getState('filter.extension')) {
+ $query->where($db->quoteName('extension') . ' = :extension')
+ ->bind(':extension', $extension);
+ }
+
+ $status = (string) $this->getState('filter.published');
+
+ // Filter by status
+ if (is_numeric($status)) {
+ $status = (int) $status;
+ $query->where($db->quoteName('w.published') . ' = :published')
+ ->bind(':published', $status, ParameterType::INTEGER);
+ } elseif ($status === '') {
+ $query->where($db->quoteName('w.published') . ' IN (0, 1)');
+ }
+
+ // Filter by search in title
+ $search = $this->getState('filter.search');
+
+ if (!empty($search)) {
+ $search = '%' . str_replace(' ', '%', trim($search)) . '%';
+ $query->where('(' . $db->quoteName('w.title') . ' LIKE :search1 OR ' . $db->quoteName('w.description') . ' LIKE :search2)')
+ ->bind([':search1', ':search2'], $search);
+ }
+
+ // Add the list ordering clause.
+ $orderCol = $this->state->get('list.ordering', 'w.ordering');
+ $orderDirn = strtoupper($this->state->get('list.direction', 'ASC'));
+
+ $query->order($db->escape($orderCol) . ' ' . ($orderDirn === 'DESC' ? 'DESC' : 'ASC'));
+
+ return $query;
+ }
}
diff --git a/administrator/components/com_workflow/src/Table/StageTable.php b/administrator/components/com_workflow/src/Table/StageTable.php
index 382e0422a0803..99a3fd5d3e6a1 100644
--- a/administrator/components/com_workflow/src/Table/StageTable.php
+++ b/administrator/components/com_workflow/src/Table/StageTable.php
@@ -1,4 +1,5 @@
getDbo();
- $app = Factory::getApplication();
- $pk = (int) $pk;
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('default'))
- ->from($db->quoteName('#__workflow_stages'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $pk, ParameterType::INTEGER);
-
- $isDefault = $db->setQuery($query)->loadResult();
-
- if ($isDefault)
- {
- $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DELETE_IS_DEFAULT'), 'error');
-
- return false;
- }
-
- try
- {
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__workflow_transitions'))
- ->where(
- [
- $db->quoteName('to_stage_id') . ' = :idTo',
- $db->quoteName('from_stage_id') . ' = :idFrom',
- ],
- 'OR'
- )
- ->bind([':idTo', ':idFrom'], $pk, ParameterType::INTEGER);
-
- $db->setQuery($query)->execute();
-
- return parent::delete($pk);
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage(Text::sprintf('COM_WORKFLOW_MSG_WORKFLOWS_DELETE_ERROR', $e->getMessage()), 'error');
- }
-
- return false;
- }
-
- /**
- * Overloaded check function
- *
- * @return boolean True on success
- *
- * @see Table::check()
- * @since 4.0.0
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (trim($this->title) === '')
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_STATE'));
-
- return false;
- }
-
- if (!empty($this->default))
- {
- if ((int) $this->published !== 1)
- {
- $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
-
- return false;
- }
- }
- else
- {
- $db = $this->getDbo();
- $query = $db->getQuery(true);
-
- $query
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__workflow_stages'))
- ->where(
- [
- $db->quoteName('workflow_id') . ' = :id',
- $db->quoteName('default') . ' = 1',
- ]
- )
- ->bind(':id', $this->workflow_id, ParameterType::INTEGER);
-
- $id = $db->setQuery($query)->loadResult();
-
- // If there is no default stage => set the current to default to recover
- if (empty($id))
- {
- $this->default = '1';
- }
- // This stage is the default, but someone has tried to disable it => not allowed
- elseif ($id === $this->id)
- {
- $this->setError(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'));
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Overloaded store function
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return mixed False on failure, positive integer on success.
- *
- * @see Table::store()
- * @since 4.0.0
- */
- public function store($updateNulls = true)
- {
- $table = new StageTable($this->getDbo());
-
- if ($this->default == '1')
- {
- // Verify that the default is unique for this workflow
- if ($table->load(array('default' => '1', 'workflow_id' => (int) $this->workflow_id)))
- {
- $table->default = 0;
- $table->store();
- }
- }
-
- return parent::store($updateNulls);
- }
-
- /**
- * Method to bind an associative array or object to the Table instance.
- * This method only binds properties that are publicly accessible and optionally
- * takes an array of properties to ignore when binding.
- *
- * @param array|object $src An associative array or object to bind to the Table instance.
- * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException
- */
- public function bind($src, $ignore = array())
- {
- // Bind the rules.
- if (isset($src['rules']) && \is_array($src['rules']))
- {
- $rules = new Rules($src['rules']);
- $this->setRules($rules);
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Method to compute the default name of the asset.
- * The default name is in the form table_name.id
- * where id is the value of the primary key of the table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetName()
- {
- $k = $this->_tbl_key;
- $workflow = new WorkflowTable($this->getDbo());
- $workflow->load($this->workflow_id);
-
- $parts = explode('.', $workflow->extension);
-
- $extension = array_shift($parts);
-
- return $extension . '.stage.' . (int) $this->$k;
- }
-
- /**
- * Method to return the title to use for the asset table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetTitle()
- {
- return $this->title;
- }
-
- /**
- * Get the parent asset id for the record
- *
- * @param Table|null $table A Table object for the asset parent.
- * @param integer|null $id The id for the asset
- *
- * @return integer The id of the asset's parent
- *
- * @since 4.0.0
- */
- protected function _getAssetParentId(Table $table = null, $id = null)
- {
- $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
-
- $workflow = new WorkflowTable($this->getDbo());
- $workflow->load($this->workflow_id);
-
- $parts = explode('.', $workflow->extension);
-
- $extension = array_shift($parts);
-
- $name = $extension . '.workflow.' . (int) $workflow->id;
-
- $asset->loadByName($name);
- $assetId = $asset->id;
-
- return !empty($assetId) ? $assetId : parent::_getAssetParentId($table, $id);
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ parent::__construct('#__workflow_stages', 'id', $db);
+ }
+
+ /**
+ * Deletes workflow with transition and stages.
+ *
+ * @param int $pk Extension ids to delete.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function delete($pk = null)
+ {
+ $db = $this->getDbo();
+ $app = Factory::getApplication();
+ $pk = (int) $pk;
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('default'))
+ ->from($db->quoteName('#__workflow_stages'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $pk, ParameterType::INTEGER);
+
+ $isDefault = $db->setQuery($query)->loadResult();
+
+ if ($isDefault) {
+ $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DELETE_IS_DEFAULT'), 'error');
+
+ return false;
+ }
+
+ try {
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__workflow_transitions'))
+ ->where(
+ [
+ $db->quoteName('to_stage_id') . ' = :idTo',
+ $db->quoteName('from_stage_id') . ' = :idFrom',
+ ],
+ 'OR'
+ )
+ ->bind([':idTo', ':idFrom'], $pk, ParameterType::INTEGER);
+
+ $db->setQuery($query)->execute();
+
+ return parent::delete($pk);
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage(Text::sprintf('COM_WORKFLOW_MSG_WORKFLOWS_DELETE_ERROR', $e->getMessage()), 'error');
+ }
+
+ return false;
+ }
+
+ /**
+ * Overloaded check function
+ *
+ * @return boolean True on success
+ *
+ * @see Table::check()
+ * @since 4.0.0
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (trim($this->title) === '') {
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_STATE'));
+
+ return false;
+ }
+
+ if (!empty($this->default)) {
+ if ((int) $this->published !== 1) {
+ $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
+
+ return false;
+ }
+ } else {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__workflow_stages'))
+ ->where(
+ [
+ $db->quoteName('workflow_id') . ' = :id',
+ $db->quoteName('default') . ' = 1',
+ ]
+ )
+ ->bind(':id', $this->workflow_id, ParameterType::INTEGER);
+
+ $id = $db->setQuery($query)->loadResult();
+
+ // If there is no default stage => set the current to default to recover
+ if (empty($id)) {
+ $this->default = '1';
+ } elseif ($id === $this->id) {
+ // This stage is the default, but someone has tried to disable it => not allowed
+ $this->setError(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'));
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded store function
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return mixed False on failure, positive integer on success.
+ *
+ * @see Table::store()
+ * @since 4.0.0
+ */
+ public function store($updateNulls = true)
+ {
+ $table = new StageTable($this->getDbo());
+
+ if ($this->default == '1') {
+ // Verify that the default is unique for this workflow
+ if ($table->load(array('default' => '1', 'workflow_id' => (int) $this->workflow_id))) {
+ $table->default = 0;
+ $table->store();
+ }
+ }
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to bind an associative array or object to the Table instance.
+ * This method only binds properties that are publicly accessible and optionally
+ * takes an array of properties to ignore when binding.
+ *
+ * @param array|object $src An associative array or object to bind to the Table instance.
+ * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ */
+ public function bind($src, $ignore = array())
+ {
+ // Bind the rules.
+ if (isset($src['rules']) && \is_array($src['rules'])) {
+ $rules = new Rules($src['rules']);
+ $this->setRules($rules);
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Method to compute the default name of the asset.
+ * The default name is in the form table_name.id
+ * where id is the value of the primary key of the table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetName()
+ {
+ $k = $this->_tbl_key;
+ $workflow = new WorkflowTable($this->getDbo());
+ $workflow->load($this->workflow_id);
+
+ $parts = explode('.', $workflow->extension);
+
+ $extension = array_shift($parts);
+
+ return $extension . '.stage.' . (int) $this->$k;
+ }
+
+ /**
+ * Method to return the title to use for the asset table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Get the parent asset id for the record
+ *
+ * @param Table|null $table A Table object for the asset parent.
+ * @param integer|null $id The id for the asset
+ *
+ * @return integer The id of the asset's parent
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetParentId(Table $table = null, $id = null)
+ {
+ $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
+
+ $workflow = new WorkflowTable($this->getDbo());
+ $workflow->load($this->workflow_id);
+
+ $parts = explode('.', $workflow->extension);
+
+ $extension = array_shift($parts);
+
+ $name = $extension . '.workflow.' . (int) $workflow->id;
+
+ $asset->loadByName($name);
+ $assetId = $asset->id;
+
+ return !empty($assetId) ? $assetId : parent::_getAssetParentId($table, $id);
+ }
}
diff --git a/administrator/components/com_workflow/src/Table/TransitionTable.php b/administrator/components/com_workflow/src/Table/TransitionTable.php
index 95b5b2de1ae8e..f78051957d928 100644
--- a/administrator/components/com_workflow/src/Table/TransitionTable.php
+++ b/administrator/components/com_workflow/src/Table/TransitionTable.php
@@ -1,4 +1,5 @@
setRules($rules);
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Method to compute the default name of the asset.
- * The default name is in the form table_name.id
- * where id is the value of the primary key of the table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetName()
- {
- $k = $this->_tbl_key;
- $workflow = new WorkflowTable($this->getDbo());
- $workflow->load($this->workflow_id);
-
- $parts = explode('.', $workflow->extension);
-
- $extension = array_shift($parts);
-
- return $extension . '.transition.' . (int) $this->$k;
- }
-
- /**
- * Method to return the title to use for the asset table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetTitle()
- {
- return $this->title;
- }
-
- /**
- * Get the parent asset id for the record
- *
- * @param Table $table A Table object for the asset parent.
- * @param integer $id The id for the asset
- *
- * @return integer The id of the asset's parent
- *
- * @since 4.0.0
- */
- protected function _getAssetParentId(Table $table = null, $id = null)
- {
- $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
-
- $workflow = new WorkflowTable($this->getDbo());
- $workflow->load($this->workflow_id);
-
- $parts = explode('.', $workflow->extension);
-
- $extension = array_shift($parts);
-
- $name = $extension . '.workflow.' . (int) $workflow->id;
-
- $asset->loadByName($name);
- $assetId = $asset->id;
-
- return !empty($assetId) ? $assetId : parent::_getAssetParentId($table, $id);
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * An array of key names to be json encoded in the bind function
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected $_jsonEncode = [
+ 'options'
+ ];
+
+ /**
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ parent::__construct('#__workflow_transitions', 'id', $db);
+ }
+
+ /**
+ * Method to bind an associative array or object to the Table instance.
+ * This method only binds properties that are publicly accessible and optionally
+ * takes an array of properties to ignore when binding.
+ *
+ * @param array|object $src An associative array or object to bind to the Table instance.
+ * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ */
+ public function bind($src, $ignore = array())
+ {
+ // Bind the rules.
+ if (isset($src['rules']) && \is_array($src['rules'])) {
+ $rules = new Rules($src['rules']);
+ $this->setRules($rules);
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Method to compute the default name of the asset.
+ * The default name is in the form table_name.id
+ * where id is the value of the primary key of the table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetName()
+ {
+ $k = $this->_tbl_key;
+ $workflow = new WorkflowTable($this->getDbo());
+ $workflow->load($this->workflow_id);
+
+ $parts = explode('.', $workflow->extension);
+
+ $extension = array_shift($parts);
+
+ return $extension . '.transition.' . (int) $this->$k;
+ }
+
+ /**
+ * Method to return the title to use for the asset table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Get the parent asset id for the record
+ *
+ * @param Table $table A Table object for the asset parent.
+ * @param integer $id The id for the asset
+ *
+ * @return integer The id of the asset's parent
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetParentId(Table $table = null, $id = null)
+ {
+ $asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
+
+ $workflow = new WorkflowTable($this->getDbo());
+ $workflow->load($this->workflow_id);
+
+ $parts = explode('.', $workflow->extension);
+
+ $extension = array_shift($parts);
+
+ $name = $extension . '.workflow.' . (int) $workflow->id;
+
+ $asset->loadByName($name);
+ $assetId = $asset->id;
+
+ return !empty($assetId) ? $assetId : parent::_getAssetParentId($table, $id);
+ }
}
diff --git a/administrator/components/com_workflow/src/Table/WorkflowTable.php b/administrator/components/com_workflow/src/Table/WorkflowTable.php
index a9f7267bb39b7..d0c444e278ad5 100644
--- a/administrator/components/com_workflow/src/Table/WorkflowTable.php
+++ b/administrator/components/com_workflow/src/Table/WorkflowTable.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\Table;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\Table;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Factory;
@@ -24,317 +24,290 @@
*/
class WorkflowTable extends Table
{
- /**
- * Indicates that columns fully support the NULL value in the database
- *
- * @var boolean
- *
- * @since 4.0.0
- */
- protected $_supportNullValue = true;
-
- /**
- * @param DatabaseDriver $db Database connector object
- *
- * @since 4.0.0
- */
- public function __construct(DatabaseDriver $db)
- {
- $this->typeAlias = '{extension}.workflow';
-
- parent::__construct('#__workflows', 'id', $db);
- }
-
- /**
- * Deletes workflow with transition and states.
- *
- * @param int $pk Extension ids to delete.
- *
- * @return boolean
- *
- * @since 4.0.0
- *
- * @throws \Exception on ACL error
- */
- public function delete($pk = null)
- {
- $db = $this->getDbo();
- $app = Factory::getApplication();
- $pk = (int) $pk;
-
- // Gets the workflow information that is going to be deleted.
- $query = $db->getQuery(true)
- ->select($db->quoteName('default'))
- ->from($db->quoteName('#__workflows'))
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $pk, ParameterType::INTEGER);
-
- $isDefault = $db->setQuery($query)->loadResult();
-
- if ($isDefault)
- {
- $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DELETE_DEFAULT'), 'error');
-
- return false;
- }
-
- // Delete the workflow states, then transitions from all tables.
- try
- {
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__workflow_stages'))
- ->where($db->quoteName('workflow_id') . ' = :id')
- ->bind(':id', $pk, ParameterType::INTEGER);
-
- $db->setQuery($query)->execute();
-
- $query = $db->getQuery(true)
- ->delete($db->quoteName('#__workflow_transitions'))
- ->where($db->quoteName('workflow_id') . ' = :id')
- ->bind(':id', $pk, ParameterType::INTEGER);
-
- $db->setQuery($query)->execute();
-
- return parent::delete($pk);
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage(Text::sprintf('COM_WORKFLOW_MSG_WORKFLOWS_DELETE_ERROR', $e->getMessage()), 'error');
-
- return false;
- }
- }
-
- /**
- * Overloaded check function
- *
- * @return boolean True on success
- *
- * @see Table::check()
- * @since 4.0.0
- */
- public function check()
- {
- try
- {
- parent::check();
- }
- catch (\Exception $e)
- {
- $this->setError($e->getMessage());
-
- return false;
- }
-
- if (trim($this->title) === '')
- {
- $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_WORKFLOW'));
-
- return false;
- }
-
- if (!empty($this->default))
- {
- if ((int) $this->published !== 1)
- {
- $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
-
- return false;
- }
- }
- else
- {
- $db = $this->getDbo();
- $query = $db->getQuery(true);
-
- $query
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__workflows'))
- ->where($db->quoteName('default') . ' = 1');
-
- $id = $db->setQuery($query)->loadResult();
-
- // If there is no default workflow => set the current to default to recover
- if (empty($id))
- {
- $this->default = '1';
- }
- // This workflow is the default, but someone has tried to disable it => not allowed
- elseif ($id === $this->id)
- {
- $this->setError(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'));
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Overloaded store function
- *
- * @param boolean $updateNulls True to update fields even if they are null.
- *
- * @return mixed False on failure, positive integer on success.
- *
- * @see Table::store()
- * @since 4.0.0
- */
- public function store($updateNulls = true)
- {
- $date = Factory::getDate();
- $user = Factory::getUser();
-
- $table = new WorkflowTable($this->getDbo());
-
- if ($this->id)
- {
- // Existing item
- $this->modified_by = $user->id;
- $this->modified = $date->toSql();
- }
- else
- {
- $this->modified_by = 0;
- }
-
- if (!(int) $this->created)
- {
- $this->created = $date->toSql();
- }
-
- if (empty($this->created_by))
- {
- $this->created_by = $user->id;
- }
-
- if (!(int) $this->modified)
- {
- $this->modified = $this->created;
- }
-
- if (empty($this->modified_by))
- {
- $this->modified_by = $this->created_by;
- }
-
- if ((int) $this->default === 1)
- {
- // Verify that the default is unique for this workflow
- if ($table->load(
- [
- 'default' => '1',
- 'extension' => $this->extension
- ]
- ))
- {
- $table->default = 0;
- $table->store();
- }
- }
-
- return parent::store($updateNulls);
- }
-
- /**
- * Method to bind an associative array or object to the Table instance.
- * This method only binds properties that are publicly accessible and optionally
- * takes an array of properties to ignore when binding.
- *
- * @param array|object $src An associative array or object to bind to the Table instance.
- * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException
- */
- public function bind($src, $ignore = array())
- {
- // Bind the rules.
- if (isset($src['rules']) && \is_array($src['rules']))
- {
- $rules = new Rules($src['rules']);
- $this->setRules($rules);
- }
-
- return parent::bind($src, $ignore);
- }
-
- /**
- * Method to compute the default name of the asset.
- * The default name is in the form table_name.id
- * where id is the value of the primary key of the table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetName()
- {
- $k = $this->_tbl_key;
-
- $parts = explode('.', $this->extension);
-
- $extension = array_shift($parts);
-
- return $extension . '.workflow.' . (int) $this->$k;
- }
-
- /**
- * Method to return the title to use for the asset table.
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function _getAssetTitle()
- {
- return $this->title;
- }
-
- /**
- * Get the parent asset id for the record
- *
- * @param Table $table A Table object for the asset parent.
- * @param integer $id The id for the asset
- *
- * @return integer The id of the asset's parent
- *
- * @since 4.0.0
- */
- protected function _getAssetParentId(Table $table = null, $id = null)
- {
- $assetId = null;
-
- $parts = explode('.', $this->extension);
-
- $extension = array_shift($parts);
-
- // Build the query to get the asset id for the parent category.
- $query = $this->getDbo()->getQuery(true)
- ->select($this->getDbo()->quoteName('id'))
- ->from($this->getDbo()->quoteName('#__assets'))
- ->where($this->getDbo()->quoteName('name') . ' = :extension')
- ->bind(':extension', $extension);
-
- // Get the asset id from the database.
- $this->getDbo()->setQuery($query);
-
- if ($result = $this->getDbo()->loadResult())
- {
- $assetId = (int) $result;
- }
-
- // Return the asset id.
- if ($assetId)
- {
- return $assetId;
- }
- else
- {
- return parent::_getAssetParentId($table, $id);
- }
- }
+ /**
+ * Indicates that columns fully support the NULL value in the database
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ protected $_supportNullValue = true;
+
+ /**
+ * @param DatabaseDriver $db Database connector object
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseDriver $db)
+ {
+ $this->typeAlias = '{extension}.workflow';
+
+ parent::__construct('#__workflows', 'id', $db);
+ }
+
+ /**
+ * Deletes workflow with transition and states.
+ *
+ * @param int $pk Extension ids to delete.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ *
+ * @throws \Exception on ACL error
+ */
+ public function delete($pk = null)
+ {
+ $db = $this->getDbo();
+ $app = Factory::getApplication();
+ $pk = (int) $pk;
+
+ // Gets the workflow information that is going to be deleted.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('default'))
+ ->from($db->quoteName('#__workflows'))
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $pk, ParameterType::INTEGER);
+
+ $isDefault = $db->setQuery($query)->loadResult();
+
+ if ($isDefault) {
+ $app->enqueueMessage(Text::_('COM_WORKFLOW_MSG_DELETE_DEFAULT'), 'error');
+
+ return false;
+ }
+
+ // Delete the workflow states, then transitions from all tables.
+ try {
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__workflow_stages'))
+ ->where($db->quoteName('workflow_id') . ' = :id')
+ ->bind(':id', $pk, ParameterType::INTEGER);
+
+ $db->setQuery($query)->execute();
+
+ $query = $db->getQuery(true)
+ ->delete($db->quoteName('#__workflow_transitions'))
+ ->where($db->quoteName('workflow_id') . ' = :id')
+ ->bind(':id', $pk, ParameterType::INTEGER);
+
+ $db->setQuery($query)->execute();
+
+ return parent::delete($pk);
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage(Text::sprintf('COM_WORKFLOW_MSG_WORKFLOWS_DELETE_ERROR', $e->getMessage()), 'error');
+
+ return false;
+ }
+ }
+
+ /**
+ * Overloaded check function
+ *
+ * @return boolean True on success
+ *
+ * @see Table::check()
+ * @since 4.0.0
+ */
+ public function check()
+ {
+ try {
+ parent::check();
+ } catch (\Exception $e) {
+ $this->setError($e->getMessage());
+
+ return false;
+ }
+
+ if (trim($this->title) === '') {
+ $this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_WORKFLOW'));
+
+ return false;
+ }
+
+ if (!empty($this->default)) {
+ if ((int) $this->published !== 1) {
+ $this->setError(Text::_('COM_WORKFLOW_ITEM_MUST_PUBLISHED'));
+
+ return false;
+ }
+ } else {
+ $db = $this->getDbo();
+ $query = $db->getQuery(true);
+
+ $query
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__workflows'))
+ ->where($db->quoteName('default') . ' = 1');
+
+ $id = $db->setQuery($query)->loadResult();
+
+ // If there is no default workflow => set the current to default to recover
+ if (empty($id)) {
+ $this->default = '1';
+ } elseif ($id === $this->id) {
+ // This workflow is the default, but someone has tried to disable it => not allowed
+ $this->setError(Text::_('COM_WORKFLOW_DISABLE_DEFAULT'));
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Overloaded store function
+ *
+ * @param boolean $updateNulls True to update fields even if they are null.
+ *
+ * @return mixed False on failure, positive integer on success.
+ *
+ * @see Table::store()
+ * @since 4.0.0
+ */
+ public function store($updateNulls = true)
+ {
+ $date = Factory::getDate();
+ $user = Factory::getUser();
+
+ $table = new WorkflowTable($this->getDbo());
+
+ if ($this->id) {
+ // Existing item
+ $this->modified_by = $user->id;
+ $this->modified = $date->toSql();
+ } else {
+ $this->modified_by = 0;
+ }
+
+ if (!(int) $this->created) {
+ $this->created = $date->toSql();
+ }
+
+ if (empty($this->created_by)) {
+ $this->created_by = $user->id;
+ }
+
+ if (!(int) $this->modified) {
+ $this->modified = $this->created;
+ }
+
+ if (empty($this->modified_by)) {
+ $this->modified_by = $this->created_by;
+ }
+
+ if ((int) $this->default === 1) {
+ // Verify that the default is unique for this workflow
+ if (
+ $table->load(
+ [
+ 'default' => '1',
+ 'extension' => $this->extension
+ ]
+ )
+ ) {
+ $table->default = 0;
+ $table->store();
+ }
+ }
+
+ return parent::store($updateNulls);
+ }
+
+ /**
+ * Method to bind an associative array or object to the Table instance.
+ * This method only binds properties that are publicly accessible and optionally
+ * takes an array of properties to ignore when binding.
+ *
+ * @param array|object $src An associative array or object to bind to the Table instance.
+ * @param array|string $ignore An optional array or space separated list of properties to ignore while binding.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ */
+ public function bind($src, $ignore = array())
+ {
+ // Bind the rules.
+ if (isset($src['rules']) && \is_array($src['rules'])) {
+ $rules = new Rules($src['rules']);
+ $this->setRules($rules);
+ }
+
+ return parent::bind($src, $ignore);
+ }
+
+ /**
+ * Method to compute the default name of the asset.
+ * The default name is in the form table_name.id
+ * where id is the value of the primary key of the table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetName()
+ {
+ $k = $this->_tbl_key;
+
+ $parts = explode('.', $this->extension);
+
+ $extension = array_shift($parts);
+
+ return $extension . '.workflow.' . (int) $this->$k;
+ }
+
+ /**
+ * Method to return the title to use for the asset table.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Get the parent asset id for the record
+ *
+ * @param Table $table A Table object for the asset parent.
+ * @param integer $id The id for the asset
+ *
+ * @return integer The id of the asset's parent
+ *
+ * @since 4.0.0
+ */
+ protected function _getAssetParentId(Table $table = null, $id = null)
+ {
+ $assetId = null;
+
+ $parts = explode('.', $this->extension);
+
+ $extension = array_shift($parts);
+
+ // Build the query to get the asset id for the parent category.
+ $query = $this->getDbo()->getQuery(true)
+ ->select($this->getDbo()->quoteName('id'))
+ ->from($this->getDbo()->quoteName('#__assets'))
+ ->where($this->getDbo()->quoteName('name') . ' = :extension')
+ ->bind(':extension', $extension);
+
+ // Get the asset id from the database.
+ $this->getDbo()->setQuery($query);
+
+ if ($result = $this->getDbo()->loadResult()) {
+ $assetId = (int) $result;
+ }
+
+ // Return the asset id.
+ if ($assetId) {
+ return $assetId;
+ } else {
+ return parent::_getAssetParentId($table, $id);
+ }
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Stage/HtmlView.php b/administrator/components/com_workflow/src/View/Stage/HtmlView.php
index 1c24c2cd7a3f1..0fabb6b6a8955 100644
--- a/administrator/components/com_workflow/src/View/Stage/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Stage/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Stage;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Stage;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
@@ -24,155 +24,147 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * The model state
- *
- * @var object
- * @since 4.0.0
- */
- protected $state;
-
- /**
- * From object to generate fields
- *
- * @var \Joomla\CMS\Form\Form
- *
- * @since 4.0.0
- */
- protected $form;
-
- /**
- * Items array
- *
- * @var object
- * @since 4.0.0
- */
- protected $item;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display item view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- // Get the Data
- $this->state = $this->get('State');
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $extension = $this->state->get('filter.extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- // Set the toolbar
- $this->addToolbar();
-
- // Display the template
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = empty($this->item->id);
-
- $canDo = StageHelper::getActions($this->extension, 'stage', $this->item->id);
-
- ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_STAGE_ADD') : Text::_('COM_WORKFLOW_STAGE_EDIT'), 'address');
-
- $toolbarButtons = [];
-
- if ($isNew)
- {
- // For new records, check the create permission.
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::apply('stage.apply');
- $toolbarButtons = [['save', 'stage.save'], ['save2new', 'stage.save2new']];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel(
- 'stage.cancel'
- );
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- if ($itemEditable)
- {
- ToolbarHelper::apply('stage.apply');
- $toolbarButtons = [['save', 'stage.save']];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'stage.save2new'];
- $toolbarButtons[] = ['save2copy', 'stage.save2copy'];
- }
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel(
- 'stage.cancel',
- 'JTOOLBAR_CLOSE'
- );
- }
-
- ToolbarHelper::divider();
- }
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $state;
+
+ /**
+ * From object to generate fields
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ protected $form;
+
+ /**
+ * Items array
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $item;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display item view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ // Get the Data
+ $this->state = $this->get('State');
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $extension = $this->state->get('filter.extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ // Set the toolbar
+ $this->addToolbar();
+
+ // Display the template
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = empty($this->item->id);
+
+ $canDo = StageHelper::getActions($this->extension, 'stage', $this->item->id);
+
+ ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_STAGE_ADD') : Text::_('COM_WORKFLOW_STAGE_EDIT'), 'address');
+
+ $toolbarButtons = [];
+
+ if ($isNew) {
+ // For new records, check the create permission.
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::apply('stage.apply');
+ $toolbarButtons = [['save', 'stage.save'], ['save2new', 'stage.save2new']];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel(
+ 'stage.cancel'
+ );
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ if ($itemEditable) {
+ ToolbarHelper::apply('stage.apply');
+ $toolbarButtons = [['save', 'stage.save']];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'stage.save2new'];
+ $toolbarButtons[] = ['save2copy', 'stage.save2copy'];
+ }
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel(
+ 'stage.cancel',
+ 'JTOOLBAR_CLOSE'
+ );
+ }
+
+ ToolbarHelper::divider();
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Stages/HtmlView.php b/administrator/components/com_workflow/src/View/Stages/HtmlView.php
index 294f44037113b..8919cf7958562 100644
--- a/administrator/components/com_workflow/src/View/Stages/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Stages/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Stages;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Stages;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
@@ -27,198 +27,190 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * An array of stages
- *
- * @var array
- * @since 4.0.0
- */
- protected $stages;
-
- /**
- * The model stage
- *
- * @var object
- * @since 4.0.0
- */
- protected $stage;
-
- /**
- * The HTML for displaying sidebar
- *
- * @var string
- * @since 4.0.0
- */
- protected $sidebar;
-
- /**
- * The pagination object
- *
- * @var \Joomla\CMS\Pagination\Pagination
- *
- * @since 4.0.0
- */
- protected $pagination;
-
- /**
- * Form object for search filters
- *
- * @var \Joomla\CMS\Form\Form
- *
- * @since 4.0.0
- */
- public $filterForm;
-
- /**
- * The active search filters
- *
- * @var array
- * @since 4.0.0
- */
- public $activeFilters;
-
- /**
- * The current workflow
- *
- * @var object
- * @since 4.0.0
- */
- protected $workflow;
-
- /**
- * The ID of current workflow
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowID;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display the view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- $this->state = $this->get('State');
- $this->stages = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->workflow = $this->get('Workflow');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->workflowID = $this->workflow->id;
-
- $parts = explode('.', $this->workflow->extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- if (!empty($this->stages))
- {
- $extension = Factory::getApplication()->input->getCmd('extension');
- $workflow = new Workflow($extension);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID);
-
- $user = $this->getCurrentUser();
-
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_STAGES_LIST', Text::_($this->state->get('active_workflow', ''))), 'address contact');
-
- $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
-
- ToolbarHelper::link(
- Route::_('index.php?option=com_workflow&view=workflows&extension=' . $this->escape($this->workflow->extension)),
- 'JTOOLBAR_BACK',
- $arrow
- );
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('stage.add');
- }
-
- if ($canDo->get('core.edit.state') || $user->authorise('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('stages.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
- $childBar->unpublish('stages.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
- $childBar->makeDefault('stages.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT');
-
- if ($canDo->get('core.admin'))
- {
- $childBar->checkin('stages.checkin')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') !== '-2')
- {
- $childBar->trash('stages.trash');
- }
- }
-
- if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete'))
- {
- $toolbar->delete('stages.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- $toolbar->help('Stages_List:_Basic_Workflow');
- }
+ /**
+ * An array of stages
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $stages;
+
+ /**
+ * The model stage
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $stage;
+
+ /**
+ * The HTML for displaying sidebar
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $sidebar;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 4.0.0
+ */
+ protected $pagination;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * The current workflow
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $workflow;
+
+ /**
+ * The ID of current workflow
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowID;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->stages = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->workflow = $this->get('Workflow');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->workflowID = $this->workflow->id;
+
+ $parts = explode('.', $this->workflow->extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ if (!empty($this->stages)) {
+ $extension = Factory::getApplication()->input->getCmd('extension');
+ $workflow = new Workflow($extension);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID);
+
+ $user = $this->getCurrentUser();
+
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_STAGES_LIST', Text::_($this->state->get('active_workflow', ''))), 'address contact');
+
+ $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
+
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_workflow&view=workflows&extension=' . $this->escape($this->workflow->extension)),
+ 'JTOOLBAR_BACK',
+ $arrow
+ );
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('stage.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('stages.publish', 'JTOOLBAR_ENABLE')->listCheck(true);
+ $childBar->unpublish('stages.unpublish', 'JTOOLBAR_DISABLE')->listCheck(true);
+ $childBar->makeDefault('stages.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT');
+
+ if ($canDo->get('core.admin')) {
+ $childBar->checkin('stages.checkin')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') !== '-2') {
+ $childBar->trash('stages.trash');
+ }
+ }
+
+ if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) {
+ $toolbar->delete('stages.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ $toolbar->help('Stages_List:_Basic_Workflow');
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Transition/HtmlView.php b/administrator/components/com_workflow/src/View/Transition/HtmlView.php
index 52d9112090361..dc3f2f7556a77 100644
--- a/administrator/components/com_workflow/src/View/Transition/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Transition/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Transition;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Transition;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
@@ -24,194 +24,183 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * The model state
- *
- * @var object
- * @since 4.0.0
- */
- protected $state;
-
- /**
- * Form object to generate fields
- *
- * @var \Joomla\CMS\Form\Form
- *
- * @since 4.0.0
- */
- protected $form;
-
- /**
- * Items array
- *
- * @var object
- * @since 4.0.0
- */
- protected $item;
-
- /**
- * That is object of Application
- *
- * @var \Joomla\CMS\Application\CMSApplication
- * @since 4.0.0
- */
- protected $app;
-
- /**
- * The application input object.
- *
- * @var \Joomla\CMS\Input\Input
- * @since 4.0.0
- */
- protected $input;
-
- /**
- * The ID of current workflow
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowID;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display item view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- $this->app = Factory::getApplication();
- $this->input = $this->app->input;
-
- // Get the Data
- $this->state = $this->get('State');
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $extension = $this->state->get('filter.extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- // Get the ID of workflow
- $this->workflowID = $this->input->getCmd("workflow_id");
-
- // Set the toolbar
- $this->addToolbar();
-
- // Display the template
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = empty($this->item->id);
-
- $canDo = StageHelper::getActions($this->extension, 'transition', $this->item->id);
-
- ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_TRANSITION_ADD') : Text::_('COM_WORKFLOW_TRANSITION_EDIT'), 'address');
-
- $toolbarButtons = [];
-
- $canCreate = $canDo->get('core.create');
-
- if ($isNew)
- {
- // For new records, check the create permission.
- if ($canCreate)
- {
- ToolbarHelper::apply('transition.apply');
- $toolbarButtons = [['save', 'transition.save'], ['save2new', 'transition.save2new']];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel(
- 'transition.cancel'
- );
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- if ($itemEditable)
- {
- ToolbarHelper::apply('transition.apply');
- $toolbarButtons[] = ['save', 'transition.save'];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canCreate)
- {
- $toolbarButtons[] = ['save2new', 'transition.save2new'];
- $toolbarButtons[] = ['save2copy', 'transition.save2copy'];
- }
- }
-
- if (count($toolbarButtons) > 1)
- {
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
- }
- else
- {
- ToolbarHelper::save('transition.save');
- }
-
- ToolbarHelper::cancel(
- 'transition.cancel',
- 'JTOOLBAR_CLOSE'
- );
- }
-
- ToolbarHelper::divider();
- }
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $state;
+
+ /**
+ * Form object to generate fields
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ protected $form;
+
+ /**
+ * Items array
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $item;
+
+ /**
+ * That is object of Application
+ *
+ * @var \Joomla\CMS\Application\CMSApplication
+ * @since 4.0.0
+ */
+ protected $app;
+
+ /**
+ * The application input object.
+ *
+ * @var \Joomla\CMS\Input\Input
+ * @since 4.0.0
+ */
+ protected $input;
+
+ /**
+ * The ID of current workflow
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowID;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display item view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ $this->app = Factory::getApplication();
+ $this->input = $this->app->input;
+
+ // Get the Data
+ $this->state = $this->get('State');
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $extension = $this->state->get('filter.extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ // Get the ID of workflow
+ $this->workflowID = $this->input->getCmd("workflow_id");
+
+ // Set the toolbar
+ $this->addToolbar();
+
+ // Display the template
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = empty($this->item->id);
+
+ $canDo = StageHelper::getActions($this->extension, 'transition', $this->item->id);
+
+ ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_TRANSITION_ADD') : Text::_('COM_WORKFLOW_TRANSITION_EDIT'), 'address');
+
+ $toolbarButtons = [];
+
+ $canCreate = $canDo->get('core.create');
+
+ if ($isNew) {
+ // For new records, check the create permission.
+ if ($canCreate) {
+ ToolbarHelper::apply('transition.apply');
+ $toolbarButtons = [['save', 'transition.save'], ['save2new', 'transition.save2new']];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel(
+ 'transition.cancel'
+ );
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ if ($itemEditable) {
+ ToolbarHelper::apply('transition.apply');
+ $toolbarButtons[] = ['save', 'transition.save'];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canCreate) {
+ $toolbarButtons[] = ['save2new', 'transition.save2new'];
+ $toolbarButtons[] = ['save2copy', 'transition.save2copy'];
+ }
+ }
+
+ if (count($toolbarButtons) > 1) {
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+ } else {
+ ToolbarHelper::save('transition.save');
+ }
+
+ ToolbarHelper::cancel(
+ 'transition.cancel',
+ 'JTOOLBAR_CLOSE'
+ );
+ }
+
+ ToolbarHelper::divider();
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Transitions/HtmlView.php b/administrator/components/com_workflow/src/View/Transitions/HtmlView.php
index 50653c395630e..01562949cc892 100644
--- a/administrator/components/com_workflow/src/View/Transitions/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Transitions/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Transitions;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Transitions;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
@@ -26,191 +26,184 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * An array of transitions
- *
- * @var array
- * @since 4.0.0
- */
- protected $transitions;
-
- /**
- * The model state
- *
- * @var object
- * @since 4.0.0
- */
- protected $state;
-
- /**
- * The HTML for displaying sidebar
- *
- * @var string
- * @since 4.0.0
- */
- protected $sidebar;
-
- /**
- * The pagination object
- *
- * @var \Joomla\CMS\Pagination\Pagination
- *
- * @since 4.0.0
- */
- protected $pagination;
-
- /**
- * Form object for search filters
- *
- * @var \Joomla\CMS\Form\Form
- *
- * @since 4.0.0
- */
- public $filterForm;
-
- /**
- * The active search filters
- *
- * @var array
- * @since 4.0.0
- */
- public $activeFilters;
-
- /**
- * The current workflow
- *
- * @var object
- * @since 4.0.0
- */
- protected $workflow;
-
- /**
- * The ID of current workflow
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowID;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display the view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- $this->state = $this->get('State');
- $this->transitions = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
- $this->workflow = $this->get('Workflow');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $this->workflowID = $this->workflow->id;
-
- $parts = explode('.', $this->workflow->extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID);
-
- $user = $this->getCurrentUser();
-
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_TRANSITIONS_LIST', Text::_($this->state->get('active_workflow'))), 'address contact');
-
- $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
-
- ToolbarHelper::link(
- Route::_('index.php?option=com_workflow&view=workflows&extension=' . $this->escape($this->workflow->extension)),
- 'JTOOLBAR_BACK',
- $arrow
- );
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('transition.add');
- }
-
- if ($canDo->get('core.edit.state') || $user->authorise('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('transitions.publish', 'JTOOLBAR_ENABLE');
- $childBar->unpublish('transitions.unpublish', 'JTOOLBAR_DISABLE');
-
- if ($canDo->get('core.admin'))
- {
- $childBar->checkin('transitions.checkin')->listCheck(true);
- }
-
- if ($this->state->get('filter.published') !== '-2')
- {
- $childBar->trash('transitions.trash');
- }
- }
-
- if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete'))
- {
- $toolbar->delete('transitions.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- $toolbar->help('Transitions_List:_Basic_Workflow');
- }
+ /**
+ * An array of transitions
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $transitions;
+
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $state;
+
+ /**
+ * The HTML for displaying sidebar
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $sidebar;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ *
+ * @since 4.0.0
+ */
+ protected $pagination;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * The current workflow
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $workflow;
+
+ /**
+ * The ID of current workflow
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowID;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->transitions = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+ $this->workflow = $this->get('Workflow');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $this->workflowID = $this->workflow->id;
+
+ $parts = explode('.', $this->workflow->extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions($this->extension, 'workflow', $this->workflowID);
+
+ $user = $this->getCurrentUser();
+
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::sprintf('COM_WORKFLOW_TRANSITIONS_LIST', Text::_($this->state->get('active_workflow'))), 'address contact');
+
+ $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
+
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_workflow&view=workflows&extension=' . $this->escape($this->workflow->extension)),
+ 'JTOOLBAR_BACK',
+ $arrow
+ );
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('transition.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('transitions.publish', 'JTOOLBAR_ENABLE');
+ $childBar->unpublish('transitions.unpublish', 'JTOOLBAR_DISABLE');
+
+ if ($canDo->get('core.admin')) {
+ $childBar->checkin('transitions.checkin')->listCheck(true);
+ }
+
+ if ($this->state->get('filter.published') !== '-2') {
+ $childBar->trash('transitions.trash');
+ }
+ }
+
+ if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) {
+ $toolbar->delete('transitions.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ $toolbar->help('Transitions_List:_Basic_Workflow');
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Workflow/HtmlView.php b/administrator/components/com_workflow/src/View/Workflow/HtmlView.php
index 0bc91d5cfe0cb..3a531ade83906 100644
--- a/administrator/components/com_workflow/src/View/Workflow/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Workflow/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Workflow;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Workflow;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
@@ -24,158 +24,150 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * The model state
- *
- * @var object
- * @since 4.0.0
- */
- protected $state;
-
- /**
- * The Form object
- *
- * @var \Joomla\CMS\Form\Form
- */
- protected $form;
-
- /**
- * The active item
- *
- * @var object
- */
- protected $item;
-
- /**
- * The ID of current workflow
- *
- * @var integer
- * @since 4.0.0
- */
- protected $workflowID;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display item view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- // Get the Data
- $this->state = $this->get('State');
- $this->form = $this->get('Form');
- $this->item = $this->get('Item');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $extension = $this->state->get('filter.extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- // Set the toolbar
- $this->addToolbar();
-
- // Display the template
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- Factory::getApplication()->input->set('hidemainmenu', true);
-
- $user = $this->getCurrentUser();
- $userId = $user->id;
- $isNew = empty($this->item->id);
-
- $canDo = WorkflowHelper::getActions($this->extension, 'workflow', $this->item->id);
-
- ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_WORKFLOWS_ADD') : Text::_('COM_WORKFLOW_WORKFLOWS_EDIT'), 'address');
-
- $toolbarButtons = [];
-
- if ($isNew)
- {
- // For new records, check the create permission.
- if ($canDo->get('core.create'))
- {
- ToolbarHelper::apply('workflow.apply');
- $toolbarButtons = [['save', 'workflow.save'], ['save2new', 'workflow.save2new']];
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel(
- 'workflow.cancel'
- );
- }
- else
- {
- // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
- $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
-
- if ($itemEditable)
- {
- ToolbarHelper::apply('workflow.apply');
- $toolbarButtons = [['save', 'workflow.save']];
-
- // We can save this record, but check the create permission to see if we can return to make a new one.
- if ($canDo->get('core.create'))
- {
- $toolbarButtons[] = ['save2new', 'workflow.save2new'];
- $toolbarButtons[] = ['save2copy', 'workflow.save2copy'];
- }
- }
-
- ToolbarHelper::saveGroup(
- $toolbarButtons,
- 'btn-success'
- );
-
- ToolbarHelper::cancel(
- 'workflow.cancel',
- 'JTOOLBAR_CLOSE'
- );
- }
- }
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $state;
+
+ /**
+ * The Form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ */
+ protected $form;
+
+ /**
+ * The active item
+ *
+ * @var object
+ */
+ protected $item;
+
+ /**
+ * The ID of current workflow
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $workflowID;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display item view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ // Get the Data
+ $this->state = $this->get('State');
+ $this->form = $this->get('Form');
+ $this->item = $this->get('Item');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $extension = $this->state->get('filter.extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ // Set the toolbar
+ $this->addToolbar();
+
+ // Display the template
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ Factory::getApplication()->input->set('hidemainmenu', true);
+
+ $user = $this->getCurrentUser();
+ $userId = $user->id;
+ $isNew = empty($this->item->id);
+
+ $canDo = WorkflowHelper::getActions($this->extension, 'workflow', $this->item->id);
+
+ ToolbarHelper::title(empty($this->item->id) ? Text::_('COM_WORKFLOW_WORKFLOWS_ADD') : Text::_('COM_WORKFLOW_WORKFLOWS_EDIT'), 'address');
+
+ $toolbarButtons = [];
+
+ if ($isNew) {
+ // For new records, check the create permission.
+ if ($canDo->get('core.create')) {
+ ToolbarHelper::apply('workflow.apply');
+ $toolbarButtons = [['save', 'workflow.save'], ['save2new', 'workflow.save2new']];
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel(
+ 'workflow.cancel'
+ );
+ } else {
+ // Since it's an existing record, check the edit permission, or fall back to edit own if the owner.
+ $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId);
+
+ if ($itemEditable) {
+ ToolbarHelper::apply('workflow.apply');
+ $toolbarButtons = [['save', 'workflow.save']];
+
+ // We can save this record, but check the create permission to see if we can return to make a new one.
+ if ($canDo->get('core.create')) {
+ $toolbarButtons[] = ['save2new', 'workflow.save2new'];
+ $toolbarButtons[] = ['save2copy', 'workflow.save2copy'];
+ }
+ }
+
+ ToolbarHelper::saveGroup(
+ $toolbarButtons,
+ 'btn-success'
+ );
+
+ ToolbarHelper::cancel(
+ 'workflow.cancel',
+ 'JTOOLBAR_CLOSE'
+ );
+ }
+ }
}
diff --git a/administrator/components/com_workflow/src/View/Workflows/HtmlView.php b/administrator/components/com_workflow/src/View/Workflows/HtmlView.php
index c7732cce4f66b..6202c3a1cff17 100644
--- a/administrator/components/com_workflow/src/View/Workflows/HtmlView.php
+++ b/administrator/components/com_workflow/src/View/Workflows/HtmlView.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\Component\Workflow\Administrator\View\Workflows;
-\defined('_JEXEC') or die;
+namespace Joomla\Component\Workflow\Administrator\View\Workflows;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
@@ -25,171 +25,163 @@
*/
class HtmlView extends BaseHtmlView
{
- /**
- * An array of workflows
- *
- * @var array
- * @since 4.0.0
- */
- protected $workflows;
-
- /**
- * The model state
- *
- * @var object
- * @since 4.0.0
- */
- protected $state;
-
- /**
- * The pagination object
- *
- * @var \Joomla\CMS\Pagination\Pagination
- * @since 4.0.0
- */
- protected $pagination;
-
- /**
- * The HTML for displaying sidebar
- *
- * @var string
- * @since 4.0.0
- */
- protected $sidebar;
-
- /**
- * Form object for search filters
- *
- * @var \Joomla\CMS\Form\Form
- * @since 4.0.0
- */
- public $filterForm;
-
- /**
- * The active search filters
- *
- * @var array
- * @since 4.0.0
- */
- public $activeFilters;
-
- /**
- * The name of current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $extension;
-
- /**
- * The section of the current extension
- *
- * @var string
- * @since 4.0.0
- */
- protected $section;
-
- /**
- * Display the view
- *
- * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function display($tpl = null)
- {
- $this->state = $this->get('State');
- $this->workflows = $this->get('Items');
- $this->pagination = $this->get('Pagination');
- $this->filterForm = $this->get('FilterForm');
- $this->activeFilters = $this->get('ActiveFilters');
-
- // Check for errors.
- if (count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- $extension = $this->state->get('filter.extension');
-
- $parts = explode('.', $extension);
-
- $this->extension = array_shift($parts);
-
- if (!empty($parts))
- {
- $this->section = array_shift($parts);
- }
-
- $this->addToolbar();
-
- parent::display($tpl);
- }
-
- /**
- * Add the page title and toolbar.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function addToolbar()
- {
- $canDo = ContentHelper::getActions($this->extension, $this->section);
-
- $user = Factory::getApplication()->getIdentity();
-
- // Get the toolbar object instance
- $toolbar = Toolbar::getInstance('toolbar');
-
- ToolbarHelper::title(Text::_('COM_WORKFLOW_WORKFLOWS_LIST'), 'file-alt contact');
-
- if ($canDo->get('core.create'))
- {
- $toolbar->addNew('workflow.add');
- }
-
- if ($canDo->get('core.edit.state') || $user->authorise('core.admin'))
- {
- $dropdown = $toolbar->dropdownButton('status-group')
- ->text('JTOOLBAR_CHANGE_STATUS')
- ->toggleSplit(false)
- ->icon('icon-ellipsis-h')
- ->buttonClass('btn btn-action')
- ->listCheck(true);
-
- $childBar = $dropdown->getChildToolbar();
-
- $childBar->publish('workflows.publish', 'JTOOLBAR_ENABLE');
- $childBar->unpublish('workflows.unpublish', 'JTOOLBAR_DISABLE');
- $childBar->makeDefault('workflows.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT');
-
- if ($canDo->get('core.admin'))
- {
- $childBar->checkin('workflows.checkin')->listCheck(true);
- }
-
- if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2)
- {
- $childBar->trash('workflows.trash');
- }
- }
-
- if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete'))
- {
- $toolbar->delete('workflows.delete')
- ->text('JTOOLBAR_EMPTY_TRASH')
- ->message('JGLOBAL_CONFIRM_DELETE')
- ->listCheck(true);
- }
-
- if ($canDo->get('core.admin') || $canDo->get('core.options'))
- {
- $toolbar->preferences($this->extension);
- }
-
- $toolbar->help('Workflows_List');
- }
+ /**
+ * An array of workflows
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $workflows;
+
+ /**
+ * The model state
+ *
+ * @var object
+ * @since 4.0.0
+ */
+ protected $state;
+
+ /**
+ * The pagination object
+ *
+ * @var \Joomla\CMS\Pagination\Pagination
+ * @since 4.0.0
+ */
+ protected $pagination;
+
+ /**
+ * The HTML for displaying sidebar
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $sidebar;
+
+ /**
+ * Form object for search filters
+ *
+ * @var \Joomla\CMS\Form\Form
+ * @since 4.0.0
+ */
+ public $filterForm;
+
+ /**
+ * The active search filters
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public $activeFilters;
+
+ /**
+ * The name of current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The section of the current extension
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $section;
+
+ /**
+ * Display the view
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function display($tpl = null)
+ {
+ $this->state = $this->get('State');
+ $this->workflows = $this->get('Items');
+ $this->pagination = $this->get('Pagination');
+ $this->filterForm = $this->get('FilterForm');
+ $this->activeFilters = $this->get('ActiveFilters');
+
+ // Check for errors.
+ if (count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ $extension = $this->state->get('filter.extension');
+
+ $parts = explode('.', $extension);
+
+ $this->extension = array_shift($parts);
+
+ if (!empty($parts)) {
+ $this->section = array_shift($parts);
+ }
+
+ $this->addToolbar();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Add the page title and toolbar.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function addToolbar()
+ {
+ $canDo = ContentHelper::getActions($this->extension, $this->section);
+
+ $user = Factory::getApplication()->getIdentity();
+
+ // Get the toolbar object instance
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_WORKFLOW_WORKFLOWS_LIST'), 'file-alt contact');
+
+ if ($canDo->get('core.create')) {
+ $toolbar->addNew('workflow.add');
+ }
+
+ if ($canDo->get('core.edit.state') || $user->authorise('core.admin')) {
+ $dropdown = $toolbar->dropdownButton('status-group')
+ ->text('JTOOLBAR_CHANGE_STATUS')
+ ->toggleSplit(false)
+ ->icon('icon-ellipsis-h')
+ ->buttonClass('btn btn-action')
+ ->listCheck(true);
+
+ $childBar = $dropdown->getChildToolbar();
+
+ $childBar->publish('workflows.publish', 'JTOOLBAR_ENABLE');
+ $childBar->unpublish('workflows.unpublish', 'JTOOLBAR_DISABLE');
+ $childBar->makeDefault('workflows.setDefault', 'COM_WORKFLOW_TOOLBAR_DEFAULT');
+
+ if ($canDo->get('core.admin')) {
+ $childBar->checkin('workflows.checkin')->listCheck(true);
+ }
+
+ if ($canDo->get('core.edit.state') && $this->state->get('filter.published') != -2) {
+ $childBar->trash('workflows.trash');
+ }
+ }
+
+ if ($this->state->get('filter.published') === '-2' && $canDo->get('core.delete')) {
+ $toolbar->delete('workflows.delete')
+ ->text('JTOOLBAR_EMPTY_TRASH')
+ ->message('JGLOBAL_CONFIRM_DELETE')
+ ->listCheck(true);
+ }
+
+ if ($canDo->get('core.admin') || $canDo->get('core.options')) {
+ $toolbar->preferences($this->extension);
+ }
+
+ $toolbar->help('Workflows_List');
+ }
}
diff --git a/administrator/components/com_workflow/tmpl/stage/edit.php b/administrator/components/com_workflow/tmpl/stage/edit.php
index b6f53c27fbb9d..24a89fecbb458 100644
--- a/administrator/components/com_workflow/tmpl/stage/edit.php
+++ b/administrator/components/com_workflow/tmpl/stage/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$user = $app->getIdentity();
@@ -35,54 +36,54 @@
diff --git a/administrator/components/com_workflow/tmpl/stages/default.php b/administrator/components/com_workflow/tmpl/stages/default.php
index 48c4a7969a085..d7de9de7c323b 100644
--- a/administrator/components/com_workflow/tmpl/stages/default.php
+++ b/administrator/components/com_workflow/tmpl/stages/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$user = Factory::getUser();
$userId = $user->id;
@@ -30,128 +32,128 @@
$saveOrder = ($listOrder == 's.ordering');
-if ($saveOrder)
-{
- $saveOrderingUrl = 'index.php?option=com_workflow&task=stages.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder) {
+ $saveOrderingUrl = 'index.php?option=com_workflow&task=stages.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_workflow/tmpl/transition/edit.php b/administrator/components/com_workflow/tmpl/transition/edit.php
index aaefb2ff14182..ee1f49c0be8fe 100644
--- a/administrator/components/com_workflow/tmpl/transition/edit.php
+++ b/administrator/components/com_workflow/tmpl/transition/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$user = $app->getIdentity();
@@ -34,38 +35,37 @@
?>
diff --git a/administrator/components/com_workflow/tmpl/transitions/default.php b/administrator/components/com_workflow/tmpl/transitions/default.php
index 6cc6eee815d58..97dee3f5c4a84 100644
--- a/administrator/components/com_workflow/tmpl/transitions/default.php
+++ b/administrator/components/com_workflow/tmpl/transitions/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
-$user = Factory::getUser();
+$user = Factory::getUser();
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
@@ -29,136 +31,136 @@
$saveOrder = ($listOrder == 't.ordering');
-if ($saveOrder)
-{
- $saveOrderingUrl = 'index.php?option=com_workflow&task=transitions.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->workflow->extension) . '&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder) {
+ $saveOrderingUrl = 'index.php?option=com_workflow&task=transitions.saveOrderAjax&workflow_id=' . (int) $this->workflowID . '&extension=' . $this->escape($this->workflow->extension) . '&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
?>
diff --git a/administrator/components/com_workflow/tmpl/workflow/edit.php b/administrator/components/com_workflow/tmpl/workflow/edit.php
index 32725ab18acd8..055056043752a 100644
--- a/administrator/components/com_workflow/tmpl/workflow/edit.php
+++ b/administrator/components/com_workflow/tmpl/workflow/edit.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate');
+ ->useScript('form.validate');
$app = Factory::getApplication();
$user = $app->getIdentity();
@@ -34,57 +35,56 @@
diff --git a/administrator/components/com_workflow/tmpl/workflows/default.php b/administrator/components/com_workflow/tmpl/workflows/default.php
index 449445fae312d..5da642bbe09a3 100644
--- a/administrator/components/com_workflow/tmpl/workflows/default.php
+++ b/administrator/components/com_workflow/tmpl/workflows/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('table.columns')
- ->useScript('multiselect');
+ ->useScript('multiselect');
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
@@ -29,15 +31,13 @@
$orderingColumn = 'created';
$saveOrderingUrl = '';
-if (strpos($listOrder, 'modified') !== false)
-{
- $orderingColumn = 'modified';
+if (strpos($listOrder, 'modified') !== false) {
+ $orderingColumn = 'modified';
}
-if ($saveOrder)
-{
- $saveOrderingUrl = 'index.php?option=com_workflow&task=workflows.saveOrderAjax&tmpl=component&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1';
- HTMLHelper::_('draggablelist.draggable');
+if ($saveOrder) {
+ $saveOrderingUrl = 'index.php?option=com_workflow&task=workflows.saveOrderAjax&tmpl=component&extension=' . $this->escape($this->extension) . '&' . Session::getFormToken() . '=1';
+ HTMLHelper::_('draggablelist.draggable');
}
$extension = $this->escape($this->state->get('filter.extension'));
@@ -46,144 +46,147 @@
$userId = $user->id;
?>
diff --git a/administrator/components/com_wrapper/services/provider.php b/administrator/components/com_wrapper/services/provider.php
index 0b52fecb1e1dc..cf2b51358aa72 100644
--- a/administrator/components/com_wrapper/services/provider.php
+++ b/administrator/components/com_wrapper/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Wrapper'));
- $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Wrapper'));
- $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Wrapper'));
- $container->set(
- ComponentInterface::class,
- function (Container $container)
- {
- $component = new WrapperComponent($container->get(ComponentDispatcherFactoryInterface::class));
- $component->setMVCFactory($container->get(MVCFactoryInterface::class));
- $component->setRouterFactory($container->get(RouterFactoryInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Wrapper'));
+ $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Wrapper'));
+ $container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Wrapper'));
+ $container->set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new WrapperComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ $component->setRouterFactory($container->get(RouterFactoryInterface::class));
- return $component;
- }
- );
- }
+ return $component;
+ }
+ );
+ }
};
diff --git a/administrator/components/com_wrapper/src/Extension/WrapperComponent.php b/administrator/components/com_wrapper/src/Extension/WrapperComponent.php
index 5479cbbf9daf2..a964a4ab6c8c6 100644
--- a/administrator/components/com_wrapper/src/Extension/WrapperComponent.php
+++ b/administrator/components/com_wrapper/src/Extension/WrapperComponent.php
@@ -1,4 +1,5 @@
alias('session.web', 'session.web.administrator')
- ->alias('session', 'session.web.administrator')
- ->alias('JSession', 'session.web.administrator')
- ->alias(\Joomla\CMS\Session\Session::class, 'session.web.administrator')
- ->alias(\Joomla\Session\Session::class, 'session.web.administrator')
- ->alias(\Joomla\Session\SessionInterface::class, 'session.web.administrator');
+ ->alias('session', 'session.web.administrator')
+ ->alias('JSession', 'session.web.administrator')
+ ->alias(\Joomla\CMS\Session\Session::class, 'session.web.administrator')
+ ->alias(\Joomla\Session\Session::class, 'session.web.administrator')
+ ->alias(\Joomla\Session\SessionInterface::class, 'session.web.administrator');
// Instantiate the application.
$app = $container->get(\Joomla\CMS\Application\AdministratorApplication::class);
diff --git a/administrator/includes/defines.php b/administrator/includes/defines.php
index e43728b056177..3e8a3fe5fb526 100644
--- a/administrator/includes/defines.php
+++ b/administrator/includes/defines.php
@@ -1,4 +1,5 @@
isInDevelopmentState())))
-{
- if (file_exists(JPATH_INSTALLATION . '/index.php'))
- {
- header('Location: ../installation/index.php');
-
- exit();
- }
- else
- {
- echo 'No configuration file found and no installation code available. Exiting...';
-
- exit;
- }
+if (
+ !file_exists(JPATH_CONFIGURATION . '/configuration.php')
+ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10)
+ || (file_exists(JPATH_INSTALLATION . '/index.php') && (false === (new Version())->isInDevelopmentState()))
+) {
+ if (file_exists(JPATH_INSTALLATION . '/index.php')) {
+ header('Location: ../installation/index.php');
+
+ exit();
+ } else {
+ echo 'No configuration file found and no installation code available. Exiting...';
+
+ exit;
+ }
}
// Pre-Load configuration. Don't remove the Output Buffering due to BOM issues, see JCode 26026
@@ -39,65 +38,59 @@
ob_end_clean();
// System configuration.
-$config = new JConfig;
+$config = new JConfig();
// Set the error_reporting, and adjust a global Error Handler
-switch ($config->error_reporting)
-{
- case 'default':
- case '-1':
-
- break;
+switch ($config->error_reporting) {
+ case 'default':
+ case '-1':
+ break;
- case 'none':
- case '0':
- error_reporting(0);
+ case 'none':
+ case '0':
+ error_reporting(0);
- break;
+ break;
- case 'simple':
- error_reporting(E_ERROR | E_WARNING | E_PARSE);
- ini_set('display_errors', 1);
+ case 'simple':
+ error_reporting(E_ERROR | E_WARNING | E_PARSE);
+ ini_set('display_errors', 1);
- break;
+ break;
- case 'maximum':
- case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
- error_reporting(E_ALL);
- ini_set('display_errors', 1);
+ case 'maximum':
+ case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
+ error_reporting(E_ALL);
+ ini_set('display_errors', 1);
- break;
+ break;
- default:
- error_reporting($config->error_reporting);
- ini_set('display_errors', 1);
+ default:
+ error_reporting($config->error_reporting);
+ ini_set('display_errors', 1);
- break;
+ break;
}
define('JDEBUG', $config->debug);
// Check deprecation logging
-if (empty($config->log_deprecated))
-{
- // Reset handler for E_USER_DEPRECATED
- set_error_handler(null, E_USER_DEPRECATED);
-}
-else
-{
- // Make sure handler for E_USER_DEPRECATED is registered
- set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
+if (empty($config->log_deprecated)) {
+ // Reset handler for E_USER_DEPRECATED
+ set_error_handler(null, E_USER_DEPRECATED);
+} else {
+ // Make sure handler for E_USER_DEPRECATED is registered
+ set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
}
-if (JDEBUG || $config->error_reporting === 'maximum')
-{
- // Set new Exception handler with debug enabled
- $errorHandler->setExceptionHandler(
- [
- new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
- 'renderException'
- ]
- );
+if (JDEBUG || $config->error_reporting === 'maximum') {
+ // Set new Exception handler with debug enabled
+ $errorHandler->setExceptionHandler(
+ [
+ new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
+ 'renderException'
+ ]
+ );
}
/**
@@ -106,15 +99,12 @@
* We need to do this as high up the stack as we can, as the default in \Joomla\Utilities\IpHelper is to
* $allowIpOverride = true which is the wrong default for a generic site NOT behind a trusted proxy/load balancer.
*/
-if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1)
-{
- // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
- IpHelper::setAllowIpOverrides(true);
-}
-else
-{
- // We disable the allowing of IP overriding using headers by default.
- IpHelper::setAllowIpOverrides(false);
+if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1) {
+ // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
+ IpHelper::setAllowIpOverrides(true);
+} else {
+ // We disable the allowing of IP overriding using headers by default.
+ IpHelper::setAllowIpOverrides(false);
}
unset($config);
diff --git a/administrator/index.php b/administrator/index.php
index cfec264f131a8..aba7c599d4637 100644
--- a/administrator/index.php
+++ b/administrator/index.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+
+ * @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
+
+ * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
-defined('_JEXEC') or die;
+
/**
* en-GB localise class.
@@ -15,76 +20,71 @@
*/
abstract class En_GBLocalise
{
- /**
- * Returns the potential suffixes for a specific number of items
- *
- * @param integer $count The number of items.
- *
- * @return array An array of potential suffixes.
- *
- * @since 1.6
- */
- public static function getPluralSuffixes($count)
- {
- if ($count == 0)
- {
- return array('0');
- }
- elseif ($count == 1)
- {
- return array('ONE', '1');
- }
- else
- {
- return array('OTHER', 'MORE');
- }
- }
+ /**
+ * Returns the potential suffixes for a specific number of items
+ *
+ * @param integer $count The number of items.
+ *
+ * @return array An array of potential suffixes.
+ *
+ * @since 1.6
+ */
+ public static function getPluralSuffixes($count)
+ {
+ if ($count == 0) {
+ return array('0');
+ } elseif ($count == 1) {
+ return array('ONE', '1');
+ } else {
+ return array('OTHER', 'MORE');
+ }
+ }
- /**
- * Returns the ignored search words
- *
- * @return array An array of ignored search words.
- *
- * @since 1.6
- */
- public static function getIgnoredSearchWords()
- {
- return array('and', 'in', 'on');
- }
+ /**
+ * Returns the ignored search words
+ *
+ * @return array An array of ignored search words.
+ *
+ * @since 1.6
+ */
+ public static function getIgnoredSearchWords()
+ {
+ return array('and', 'in', 'on');
+ }
- /**
- * Returns the lower length limit of search words
- *
- * @return integer The lower length limit of search words.
- *
- * @since 1.6
- */
- public static function getLowerLimitSearchWord()
- {
- return 3;
- }
+ /**
+ * Returns the lower length limit of search words
+ *
+ * @return integer The lower length limit of search words.
+ *
+ * @since 1.6
+ */
+ public static function getLowerLimitSearchWord()
+ {
+ return 3;
+ }
- /**
- * Returns the upper length limit of search words
- *
- * @return integer The upper length limit of search words.
- *
- * @since 1.6
- */
- public static function getUpperLimitSearchWord()
- {
- return 20;
- }
+ /**
+ * Returns the upper length limit of search words
+ *
+ * @return integer The upper length limit of search words.
+ *
+ * @since 1.6
+ */
+ public static function getUpperLimitSearchWord()
+ {
+ return 20;
+ }
- /**
- * Returns the number of chars to display when searching
- *
- * @return integer The number of chars to display when searching.
- *
- * @since 1.6
- */
- public static function getSearchDisplayedCharactersNumber()
- {
- return 200;
- }
+ /**
+ * Returns the number of chars to display when searching
+ *
+ * @return integer The number of chars to display when searching.
+ *
+ * @since 1.6
+ */
+ public static function getSearchDisplayedCharactersNumber()
+ {
+ return 200;
+ }
}
diff --git a/administrator/modules/mod_custom/mod_custom.php b/administrator/modules/mod_custom/mod_custom.php
index 190a310df45e1..05476b1e6f05f 100644
--- a/administrator/modules/mod_custom/mod_custom.php
+++ b/administrator/modules/mod_custom/mod_custom.php
@@ -1,4 +1,5 @@
def('prepare_content', 1))
-{
- PluginHelper::importPlugin('content');
- $module->content = HTMLHelper::_('content.prepare', $module->content, '', 'mod_custom.content');
+if ($params->def('prepare_content', 1)) {
+ PluginHelper::importPlugin('content');
+ $module->content = HTMLHelper::_('content.prepare', $module->content, '', 'mod_custom.content');
}
// Replace 'images/' to '../images/' when using an image from /images in backend.
diff --git a/administrator/modules/mod_custom/tmpl/default.php b/administrator/modules/mod_custom/tmpl/default.php
index dea6f8526b9df..c9262884b21d2 100644
--- a/administrator/modules/mod_custom/tmpl/default.php
+++ b/administrator/modules/mod_custom/tmpl/default.php
@@ -1,4 +1,5 @@
- content; ?>
+ content; ?>
diff --git a/administrator/modules/mod_feed/mod_feed.php b/administrator/modules/mod_feed/mod_feed.php
index 7c5175edb75d3..3a3c9d760d88b 100644
--- a/administrator/modules/mod_feed/mod_feed.php
+++ b/administrator/modules/mod_feed/mod_feed.php
@@ -1,4 +1,5 @@
get('rssurl', '');
+ /**
+ * Method to load a feed.
+ *
+ * @param \Joomla\Registry\Registry $params The parameters object.
+ *
+ * @return \Joomla\CMS\Feed\Feed|string Return a JFeedReader object or a string message if error.
+ *
+ * @since 1.5
+ */
+ public static function getFeed($params)
+ {
+ // Module params
+ $rssurl = $params->get('rssurl', '');
- // Get RSS parsed object
- try
- {
- $feed = new FeedFactory;
- $rssDoc = $feed->getFeed($rssurl);
- }
- catch (\Exception $e)
- {
- return Text::_('MOD_FEED_ERR_FEED_NOT_RETRIEVED');
- }
+ // Get RSS parsed object
+ try {
+ $feed = new FeedFactory();
+ $rssDoc = $feed->getFeed($rssurl);
+ } catch (\Exception $e) {
+ return Text::_('MOD_FEED_ERR_FEED_NOT_RETRIEVED');
+ }
- if (empty($rssDoc))
- {
- return Text::_('MOD_FEED_ERR_FEED_NOT_RETRIEVED');
- }
+ if (empty($rssDoc)) {
+ return Text::_('MOD_FEED_ERR_FEED_NOT_RETRIEVED');
+ }
- return $rssDoc;
- }
+ return $rssDoc;
+ }
}
diff --git a/administrator/modules/mod_feed/tmpl/default.php b/administrator/modules/mod_feed/tmpl/default.php
index 6adc4e1c62098..4846fceb8fef6 100644
--- a/administrator/modules/mod_feed/tmpl/default.php
+++ b/administrator/modules/mod_feed/tmpl/default.php
@@ -1,4 +1,5 @@
' . Text::_('MOD_FEED_ERR_NO_URL') . '';
-
- return;
-}
+if (empty($rssurl)) {
+ echo '' . Text::_('MOD_FEED_ERR_NO_URL') . '
';
-if (!empty($feed) && is_string($feed))
-{
- echo $feed;
+ return;
}
-else
-{
- $lang = $app->getLanguage();
- $myrtl = $params->get('rssrtl', 0);
- $direction = ' ';
- if ($lang->isRtl() && $myrtl == 0)
- {
- $direction = ' redirect-rtl';
- }
- // Feed description
- elseif ($lang->isRtl() && $myrtl == 1)
- {
- $direction = ' redirect-ltr';
- }
- elseif ($lang->isRtl() && $myrtl == 2)
- {
- $direction = ' redirect-rtl';
- }
- elseif ($myrtl == 0)
- {
- $direction = ' redirect-ltr';
- }
- elseif ($myrtl == 1)
- {
- $direction = ' redirect-ltr';
- }
- elseif ($myrtl == 2)
- {
- $direction = ' redirect-rtl';
- }
+if (!empty($feed) && is_string($feed)) {
+ echo $feed;
+} else {
+ $lang = $app->getLanguage();
+ $myrtl = $params->get('rssrtl', 0);
+ $direction = ' ';
- if ($feed != false) :
- ?>
-
- isRtl() && $myrtl == 0) {
+ $direction = ' redirect-rtl';
+ } elseif ($lang->isRtl() && $myrtl == 1) {
+ // Feed description
+ $direction = ' redirect-ltr';
+ } elseif ($lang->isRtl() && $myrtl == 2) {
+ $direction = ' redirect-rtl';
+ } elseif ($myrtl == 0) {
+ $direction = ' redirect-ltr';
+ } elseif ($myrtl == 1) {
+ $direction = ' redirect-ltr';
+ } elseif ($myrtl == 2) {
+ $direction = ' redirect-rtl';
+ }
- // Feed title
- if (!is_null($feed->title) && $params->get('rsstitle', 1)) : ?>
-
- get('rssdate', 1)) : ?>
-
- publishedDate, Text::_('DATE_FORMAT_LC3')); ?>
-
-
+ if ($feed != false) :
+ ?>
+
+
- get('rssdesc', 1)) : ?>
- description; ?>
-
+ // Feed title
+ if (!is_null($feed->title) && $params->get('rsstitle', 1)) : ?>
+
+ get('rssdate', 1)) : ?>
+
+ publishedDate, Text::_('DATE_FORMAT_LC3')); ?>
+
+
-
- get('rssimage', 1) && $feed->image) : ?>
-
-
+
+ get('rssdesc', 1)) : ?>
+ description; ?>
+
+
+ get('rssimage', 1) && $feed->image) : ?>
+
+
-
-
-
- get('rssitems', 3); $i++) :
- if (!$feed->offsetExists($i)) :
- break;
- endif;
- $uri = $feed[$i]->uri || !$feed[$i]->isPermaLink ? trim($feed[$i]->uri) : trim($feed[$i]->guid);
- $uri = !$uri || stripos($uri, 'http') !== 0 ? $rssurl : $uri;
- $text = $feed[$i]->content !== '' ? trim($feed[$i]->content) : '';
- ?>
-
-
-
-
- title); ?>
-
+
+
+
+ get('rssitems', 3); $i++) :
+ if (!$feed->offsetExists($i)) :
+ break;
+ endif;
+ $uri = $feed[$i]->uri || !$feed[$i]->isPermaLink ? trim($feed[$i]->uri) : trim($feed[$i]->guid);
+ $uri = !$uri || stripos($uri, 'http') !== 0 ? $rssurl : $uri;
+ $text = $feed[$i]->content !== '' ? trim($feed[$i]->content) : '';
+ ?>
+
+
+
+
+ title); ?>
+
- get('rssitemdate', 0)) : ?>
-
- publishedDate, Text::_('DATE_FORMAT_LC3')); ?>
-
-
+ get('rssitemdate', 0)) : ?>
+
+ publishedDate, Text::_('DATE_FORMAT_LC3')); ?>
+
+
- get('rssitemdesc', 1) && $text !== '') : ?>
-
- get('word_count', 0), true, false);
- echo str_replace(''', "'", $text);
- ?>
-
-
-
-
-
-
-
- get('rssitemdesc', 1) && $text !== '') : ?>
+
+ get('word_count', 0), true, false);
+ echo str_replace(''', "'", $text);
+ ?>
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_latest/mod_latest.php b/administrator/modules/mod_latest/mod_latest.php
index 28ed0c2486aa2..287f2e7bee024 100644
--- a/administrator/modules/mod_latest/mod_latest.php
+++ b/administrator/modules/mod_latest/mod_latest.php
@@ -1,4 +1,5 @@
get('workflow_enabled');
-if ($workflow_enabled)
-{
- $app->getLanguage()->load('com_workflow');
+if ($workflow_enabled) {
+ $app->getLanguage()->load('com_workflow');
}
-if ($params->get('automatic_title', 0))
-{
- $module->title = LatestHelper::getTitle($params);
+if ($params->get('automatic_title', 0)) {
+ $module->title = LatestHelper::getTitle($params);
}
-if (count($list))
-{
- require ModuleHelper::getLayoutPath('mod_latest', $params->get('layout', 'default'));
-}
-else
-{
- $app->getLanguage()->load('com_content');
-
- echo LayoutHelper::render('joomla.content.emptystate_module', [
- 'textPrefix' => 'COM_CONTENT',
- 'icon' => 'icon-copy',
- ]
- );
+if (count($list)) {
+ require ModuleHelper::getLayoutPath('mod_latest', $params->get('layout', 'default'));
+} else {
+ $app->getLanguage()->load('com_content');
+
+ echo LayoutHelper::render('joomla.content.emptystate_module', [
+ 'textPrefix' => 'COM_CONTENT',
+ 'icon' => 'icon-copy',
+ ]);
}
diff --git a/administrator/modules/mod_latest/src/Helper/LatestHelper.php b/administrator/modules/mod_latest/src/Helper/LatestHelper.php
index 5353913a538db..18aefeb615097 100644
--- a/administrator/modules/mod_latest/src/Helper/LatestHelper.php
+++ b/administrator/modules/mod_latest/src/Helper/LatestHelper.php
@@ -1,4 +1,5 @@
setState('list.select', 'a.id, a.title, a.checked_out, a.checked_out_time, ' .
- ' a.access, a.created, a.created_by, a.created_by_alias, a.featured, a.state, a.publish_up, a.publish_down'
- );
-
- // Set Ordering filter
- switch ($params->get('ordering', 'c_dsc'))
- {
- case 'm_dsc':
- $model->setState('list.ordering', 'a.modified DESC, a.created');
- $model->setState('list.direction', 'DESC');
- break;
-
- case 'c_dsc':
- default:
- $model->setState('list.ordering', 'a.created');
- $model->setState('list.direction', 'DESC');
- break;
- }
-
- // Set Category Filter
- $categoryId = $params->get('catid', null);
-
- if (is_numeric($categoryId))
- {
- $model->setState('filter.category_id', $categoryId);
- }
-
- // Set User Filter.
- $userId = $user->get('id');
-
- switch ($params->get('user_id', '0'))
- {
- case 'by_me':
- $model->setState('filter.author_id', $userId);
- break;
-
- case 'not_me':
- $model->setState('filter.author_id', $userId);
- $model->setState('filter.author_id.include', false);
- break;
- }
-
- // Set the Start and Limit
- $model->setState('list.start', 0);
- $model->setState('list.limit', $params->get('count', 5));
-
- $items = $model->getItems();
-
- if ($error = $model->getError())
- {
- throw new \Exception($error, 500);
- }
-
- // Set the links
- foreach ($items as &$item)
- {
- $item->link = '';
-
- if ($user->authorise('core.edit', 'com_content.article.' . $item->id)
- || ($user->authorise('core.edit.own', 'com_content.article.' . $item->id) && ($userId === $item->created_by)))
- {
- $item->link = Route::_('index.php?option=com_content&task=article.edit&id=' . $item->id);
- }
- }
-
- return $items;
- }
-
- /**
- * Get the alternate title for the module.
- *
- * @param \Joomla\Registry\Registry $params The module parameters.
- *
- * @return string The alternate title for the module.
- */
- public static function getTitle($params)
- {
- $who = $params->get('user_id', 0);
- $catid = (int) $params->get('catid', null);
- $type = $params->get('ordering') === 'c_dsc' ? '_CREATED' : '_MODIFIED';
- $title = '';
-
- if ($catid)
- {
- $category = Categories::getInstance('Content')->get($catid);
- $title = Text::_('MOD_POPULAR_UNEXISTING');
-
- if ($category)
- {
- $title = $category->title;
- }
- }
-
- return Text::plural(
- 'MOD_LATEST_TITLE' . $type . ($catid ? '_CATEGORY' : '') . ($who != '0' ? "_$who" : ''),
- (int) $params->get('count', 5),
- $title
- );
- }
+ /**
+ * Get a list of articles.
+ *
+ * @param Registry &$params The module parameters.
+ * @param ArticlesModel $model The model.
+ *
+ * @return mixed An array of articles, or false on error.
+ */
+ public static function getList(Registry &$params, ArticlesModel $model)
+ {
+ $user = Factory::getUser();
+
+ // Set List SELECT
+ $model->setState('list.select', 'a.id, a.title, a.checked_out, a.checked_out_time, ' .
+ ' a.access, a.created, a.created_by, a.created_by_alias, a.featured, a.state, a.publish_up, a.publish_down');
+
+ // Set Ordering filter
+ switch ($params->get('ordering', 'c_dsc')) {
+ case 'm_dsc':
+ $model->setState('list.ordering', 'a.modified DESC, a.created');
+ $model->setState('list.direction', 'DESC');
+ break;
+
+ case 'c_dsc':
+ default:
+ $model->setState('list.ordering', 'a.created');
+ $model->setState('list.direction', 'DESC');
+ break;
+ }
+
+ // Set Category Filter
+ $categoryId = $params->get('catid', null);
+
+ if (is_numeric($categoryId)) {
+ $model->setState('filter.category_id', $categoryId);
+ }
+
+ // Set User Filter.
+ $userId = $user->get('id');
+
+ switch ($params->get('user_id', '0')) {
+ case 'by_me':
+ $model->setState('filter.author_id', $userId);
+ break;
+
+ case 'not_me':
+ $model->setState('filter.author_id', $userId);
+ $model->setState('filter.author_id.include', false);
+ break;
+ }
+
+ // Set the Start and Limit
+ $model->setState('list.start', 0);
+ $model->setState('list.limit', $params->get('count', 5));
+
+ $items = $model->getItems();
+
+ if ($error = $model->getError()) {
+ throw new \Exception($error, 500);
+ }
+
+ // Set the links
+ foreach ($items as &$item) {
+ $item->link = '';
+
+ if (
+ $user->authorise('core.edit', 'com_content.article.' . $item->id)
+ || ($user->authorise('core.edit.own', 'com_content.article.' . $item->id) && ($userId === $item->created_by))
+ ) {
+ $item->link = Route::_('index.php?option=com_content&task=article.edit&id=' . $item->id);
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Get the alternate title for the module.
+ *
+ * @param \Joomla\Registry\Registry $params The module parameters.
+ *
+ * @return string The alternate title for the module.
+ */
+ public static function getTitle($params)
+ {
+ $who = $params->get('user_id', 0);
+ $catid = (int) $params->get('catid', null);
+ $type = $params->get('ordering') === 'c_dsc' ? '_CREATED' : '_MODIFIED';
+ $title = '';
+
+ if ($catid) {
+ $category = Categories::getInstance('Content')->get($catid);
+ $title = Text::_('MOD_POPULAR_UNEXISTING');
+
+ if ($category) {
+ $title = $category->title;
+ }
+ }
+
+ return Text::plural(
+ 'MOD_LATEST_TITLE' . $type . ($catid ? '_CATEGORY' : '') . ($who != '0' ? "_$who" : ''),
+ (int) $params->get('count', 5),
+ $title
+ );
+ }
}
diff --git a/administrator/modules/mod_latest/tmpl/default.php b/administrator/modules/mod_latest/tmpl/default.php
index 0a317f748fb48..f77973ade2cd1 100644
--- a/administrator/modules/mod_latest/tmpl/default.php
+++ b/administrator/modules/mod_latest/tmpl/default.php
@@ -1,4 +1,5 @@
- title; ?>
-
-
-
-
-
-
-
-
-
-
-
-
- $item) : ?>
-
-
- checked_out) : ?>
- editor, $item->checked_out_time, $module->id); ?>
-
- link) : ?>
-
- title, ENT_QUOTES, 'UTF-8'); ?>
-
-
- title, ENT_QUOTES, 'UTF-8'); ?>
-
-
-
-
- stage_title); ?>
-
-
-
- author_name; ?>
-
-
- created, Text::_('DATE_FORMAT_LC4')); ?>
-
-
-
-
-
-
-
-
-
-
-
+ title; ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ $item) : ?>
+
+
+ checked_out) : ?>
+ editor, $item->checked_out_time, $module->id); ?>
+
+ link) : ?>
+
+ title, ENT_QUOTES, 'UTF-8'); ?>
+
+
+ title, ENT_QUOTES, 'UTF-8'); ?>
+
+
+
+
+ stage_title); ?>
+
+
+
+ author_name; ?>
+
+
+ created, Text::_('DATE_FORMAT_LC4')); ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_latestactions/mod_latestactions.php b/administrator/modules/mod_latestactions/mod_latestactions.php
index 3b60fff23fb17..91e0a7f37427a 100644
--- a/administrator/modules/mod_latestactions/mod_latestactions.php
+++ b/administrator/modules/mod_latestactions/mod_latestactions.php
@@ -1,4 +1,5 @@
getIdentity()->authorise('core.admin'))
-{
- return;
+if (!$app->getIdentity()->authorise('core.admin')) {
+ return;
}
$list = LatestActionsHelper::getList($params);
-if ($params->get('automatic_title', 0))
-{
- $module->title = LatestActionsHelper::getTitle($params);
+if ($params->get('automatic_title', 0)) {
+ $module->title = LatestActionsHelper::getTitle($params);
}
require ModuleHelper::getLayoutPath('mod_latestactions', $params->get('layout', 'default'));
diff --git a/administrator/modules/mod_latestactions/src/Helper/LatestActionsHelper.php b/administrator/modules/mod_latestactions/src/Helper/LatestActionsHelper.php
index 61aae97beef10..98cfc0dccce9d 100644
--- a/administrator/modules/mod_latestactions/src/Helper/LatestActionsHelper.php
+++ b/administrator/modules/mod_latestactions/src/Helper/LatestActionsHelper.php
@@ -1,4 +1,5 @@
bootComponent('com_actionlogs')->getMVCFactory()
- ->createModel('Actionlogs', 'Administrator', ['ignore_request' => true]);
+ /**
+ * Get a list of articles.
+ *
+ * @param Registry &$params The module parameters.
+ *
+ * @return mixed An array of action logs, or false on error.
+ *
+ * @since 3.9.1
+ *
+ * @throws \Exception
+ */
+ public static function getList(&$params)
+ {
+ /** @var \Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel $model */
+ $model = Factory::getApplication()->bootComponent('com_actionlogs')->getMVCFactory()
+ ->createModel('Actionlogs', 'Administrator', ['ignore_request' => true]);
- // Set the Start and Limit
- $model->setState('list.start', 0);
- $model->setState('list.limit', $params->get('count', 5));
- $model->setState('list.ordering', 'a.id');
- $model->setState('list.direction', 'DESC');
+ // Set the Start and Limit
+ $model->setState('list.start', 0);
+ $model->setState('list.limit', $params->get('count', 5));
+ $model->setState('list.ordering', 'a.id');
+ $model->setState('list.direction', 'DESC');
- $rows = $model->getItems();
+ $rows = $model->getItems();
- // Load all actionlog plugins language files
- ActionlogsHelper::loadActionLogPluginsLanguage();
+ // Load all actionlog plugins language files
+ ActionlogsHelper::loadActionLogPluginsLanguage();
- foreach ($rows as $row)
- {
- $row->message = ActionlogsHelper::getHumanReadableLogMessage($row);
- }
+ foreach ($rows as $row) {
+ $row->message = ActionlogsHelper::getHumanReadableLogMessage($row);
+ }
- return $rows;
- }
+ return $rows;
+ }
- /**
- * Get the alternate title for the module
- *
- * @param Registry $params The module parameters.
- *
- * @return string The alternate title for the module.
- *
- * @since 3.9.1
- */
- public static function getTitle($params)
- {
- return Text::plural('MOD_LATESTACTIONS_TITLE', $params->get('count', 5));
- }
+ /**
+ * Get the alternate title for the module
+ *
+ * @param Registry $params The module parameters.
+ *
+ * @return string The alternate title for the module.
+ *
+ * @since 3.9.1
+ */
+ public static function getTitle($params)
+ {
+ return Text::plural('MOD_LATESTACTIONS_TITLE', $params->get('count', 5));
+ }
}
diff --git a/administrator/modules/mod_latestactions/tmpl/default.php b/administrator/modules/mod_latestactions/tmpl/default.php
index 0d27c28500318..a36b6fc510125 100644
--- a/administrator/modules/mod_latestactions/tmpl/default.php
+++ b/administrator/modules/mod_latestactions/tmpl/default.php
@@ -1,4 +1,5 @@
- title; ?>
-
-
-
-
-
-
-
-
- $item) : ?>
-
-
- message; ?>
-
-
- log_date); ?>
-
-
-
-
-
-
-
-
-
-
-
+ title; ?>
+
+
+
+
+
+
+
+
+ $item) : ?>
+
+
+ message; ?>
+
+
+ log_date); ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_logged/mod_logged.php b/administrator/modules/mod_logged/mod_logged.php
index e646784599a89..27a210d8570f3 100644
--- a/administrator/modules/mod_logged/mod_logged.php
+++ b/administrator/modules/mod_logged/mod_logged.php
@@ -1,4 +1,5 @@
get('automatic_title', 0))
-{
- $module->title = LoggedHelper::getTitle($params);
+if ($params->get('automatic_title', 0)) {
+ $module->title = LoggedHelper::getTitle($params);
}
// Check if session metadata tracking is enabled
-if ($app->get('session_metadata', true))
-{
- $users = LoggedHelper::getList($params, $app, Factory::getContainer()->get(DatabaseInterface::class));
+if ($app->get('session_metadata', true)) {
+ $users = LoggedHelper::getList($params, $app, Factory::getContainer()->get(DatabaseInterface::class));
- require ModuleHelper::getLayoutPath('mod_logged', $params->get('layout', 'default'));
-}
-else
-{
- require ModuleHelper::getLayoutPath('mod_logged', 'disabled');
+ require ModuleHelper::getLayoutPath('mod_logged', $params->get('layout', 'default'));
+} else {
+ require ModuleHelper::getLayoutPath('mod_logged', 'disabled');
}
diff --git a/administrator/modules/mod_logged/src/Helper/LoggedHelper.php b/administrator/modules/mod_logged/src/Helper/LoggedHelper.php
index 490f2b5ea2bde..d7ba31fcfc73f 100644
--- a/administrator/modules/mod_logged/src/Helper/LoggedHelper.php
+++ b/administrator/modules/mod_logged/src/Helper/LoggedHelper.php
@@ -1,4 +1,5 @@
getIdentity();
- $query = $db->getQuery(true)
- ->select('s.time, s.client_id, u.id, u.name, u.username')
- ->from('#__session AS s')
- ->join('LEFT', '#__users AS u ON s.userid = u.id')
- ->where('s.guest = 0')
- ->setLimit($params->get('count', 5), 0);
+ /**
+ * Get a list of logged users.
+ *
+ * @param Registry $params The module parameters
+ * @param CMSApplication $app The application
+ * @param DatabaseInterface $db The database
+ *
+ * @return mixed An array of users, or false on error.
+ *
+ * @throws \RuntimeException
+ */
+ public static function getList(Registry $params, CMSApplication $app, DatabaseInterface $db)
+ {
+ $user = $app->getIdentity();
+ $query = $db->getQuery(true)
+ ->select('s.time, s.client_id, u.id, u.name, u.username')
+ ->from('#__session AS s')
+ ->join('LEFT', '#__users AS u ON s.userid = u.id')
+ ->where('s.guest = 0')
+ ->setLimit($params->get('count', 5), 0);
- $db->setQuery($query);
+ $db->setQuery($query);
- try
- {
- $results = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- throw $e;
- }
+ try {
+ $results = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ throw $e;
+ }
- foreach ($results as $k => $result)
- {
- $results[$k]->logoutLink = '';
+ foreach ($results as $k => $result) {
+ $results[$k]->logoutLink = '';
- if ($user->authorise('core.manage', 'com_users'))
- {
- $results[$k]->editLink = Route::_('index.php?option=com_users&task=user.edit&id=' . $result->id);
- $results[$k]->logoutLink = Route::_(
- 'index.php?option=com_login&task=logout&uid=' . $result->id . '&' . Session::getFormToken() . '=1'
- );
- }
+ if ($user->authorise('core.manage', 'com_users')) {
+ $results[$k]->editLink = Route::_('index.php?option=com_users&task=user.edit&id=' . $result->id);
+ $results[$k]->logoutLink = Route::_(
+ 'index.php?option=com_login&task=logout&uid=' . $result->id . '&' . Session::getFormToken() . '=1'
+ );
+ }
- if ($params->get('name', 1) == 0)
- {
- $results[$k]->name = $results[$k]->username;
- }
- }
+ if ($params->get('name', 1) == 0) {
+ $results[$k]->name = $results[$k]->username;
+ }
+ }
- return $results;
- }
+ return $results;
+ }
- /**
- * Get the alternate title for the module
- *
- * @param \Joomla\Registry\Registry $params The module parameters.
- *
- * @return string The alternate title for the module.
- */
- public static function getTitle($params)
- {
- return Text::plural('MOD_LOGGED_TITLE', $params->get('count', 5));
- }
+ /**
+ * Get the alternate title for the module
+ *
+ * @param \Joomla\Registry\Registry $params The module parameters.
+ *
+ * @return string The alternate title for the module.
+ */
+ public static function getTitle($params)
+ {
+ return Text::plural('MOD_LOGGED_TITLE', $params->get('count', 5));
+ }
}
diff --git a/administrator/modules/mod_logged/tmpl/default.php b/administrator/modules/mod_logged/tmpl/default.php
index 27726f16d8149..9e878efa45481 100644
--- a/administrator/modules/mod_logged/tmpl/default.php
+++ b/administrator/modules/mod_logged/tmpl/default.php
@@ -1,4 +1,5 @@
- title; ?>
-
-
-
- get('name', 1) == 0) : ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
- editLink)) : ?>
-
- name, ENT_QUOTES, 'UTF-8'); ?>
-
-
- name, ENT_QUOTES, 'UTF-8'); ?>
-
-
-
- client_id === null) : ?>
-
-
- client_id) : ?>
-
-
-
-
-
-
- time, Text::_('DATE_FORMAT_LC5')); ?>
-
-
-
-
+ title; ?>
+
+
+
+ get('name', 1) == 0) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ editLink)) : ?>
+
+ name, ENT_QUOTES, 'UTF-8'); ?>
+
+
+ name, ENT_QUOTES, 'UTF-8'); ?>
+
+
+
+ client_id === null) : ?>
+
+
+ client_id) : ?>
+
+
+
+
+
+
+ time, Text::_('DATE_FORMAT_LC5')); ?>
+
+
+
+
diff --git a/administrator/modules/mod_logged/tmpl/disabled.php b/administrator/modules/mod_logged/tmpl/disabled.php
index fade4218a5158..676696d68d817 100644
--- a/administrator/modules/mod_logged/tmpl/disabled.php
+++ b/administrator/modules/mod_logged/tmpl/disabled.php
@@ -1,4 +1,5 @@
diff --git a/administrator/modules/mod_login/mod_login.php b/administrator/modules/mod_login/mod_login.php
index 39759064dd82c..25e009c372ce2 100644
--- a/administrator/modules/mod_login/mod_login.php
+++ b/administrator/modules/mod_login/mod_login.php
@@ -1,4 +1,5 @@
getLanguage()->isRtl())
- {
- foreach ($languages as &$language)
- {
- $language['text'] = $language['text'] . '';
- }
- }
+ // Fix wrongly set parentheses in RTL languages
+ if (Factory::getApplication()->getLanguage()->isRtl()) {
+ foreach ($languages as &$language) {
+ $language['text'] = $language['text'] . '';
+ }
+ }
- array_unshift($languages, HTMLHelper::_('select.option', '', Text::_('JDEFAULTLANGUAGE')));
+ array_unshift($languages, HTMLHelper::_('select.option', '', Text::_('JDEFAULTLANGUAGE')));
- return HTMLHelper::_('select.genericlist', $languages, 'lang', 'class="form-select"', 'value', 'text', null);
- }
+ return HTMLHelper::_('select.genericlist', $languages, 'lang', 'class="form-select"', 'value', 'text', null);
+ }
- /**
- * Get the redirect URI after login.
- *
- * @return string
- */
- public static function getReturnUri()
- {
- $uri = Uri::getInstance();
- $return = 'index.php' . $uri->toString(array('query'));
+ /**
+ * Get the redirect URI after login.
+ *
+ * @return string
+ */
+ public static function getReturnUri()
+ {
+ $uri = Uri::getInstance();
+ $return = 'index.php' . $uri->toString(array('query'));
- if ($return != 'index.php?option=com_login')
- {
- return base64_encode($return);
- }
- else
- {
- return base64_encode('index.php');
- }
- }
+ if ($return != 'index.php?option=com_login') {
+ return base64_encode($return);
+ } else {
+ return base64_encode('index.php');
+ }
+ }
}
diff --git a/administrator/modules/mod_login/tmpl/default.php b/administrator/modules/mod_login/tmpl/default.php
index 9249ecbaf6439..7a9234d9736fc 100644
--- a/administrator/modules/mod_login/tmpl/default.php
+++ b/administrator/modules/mod_login/tmpl/default.php
@@ -1,4 +1,5 @@
getDocument()->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('field.passwordview')
- ->registerAndUseScript('mod_login.admin', 'mod_login/admin-login.min.js', [], ['defer' => true], ['core', 'form.validate']);
+ ->useScript('field.passwordview')
+ ->registerAndUseScript('mod_login.admin', 'mod_login/admin-login.min.js', [], ['defer' => true], ['core', 'form.validate']);
Text::script('JSHOWPASSWORD');
Text::script('JHIDEPASSWORD');
?>
-
- '_blank',
- 'rel' => 'noopener nofollow',
- 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGIN_CREDENTIALS'))
- ]
- ); ?>
-
+
+ '_blank',
+ 'rel' => 'noopener nofollow',
+ 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGIN_CREDENTIALS'))
+ ]
+ ); ?>
+
diff --git a/administrator/modules/mod_loginsupport/mod_loginsupport.php b/administrator/modules/mod_loginsupport/mod_loginsupport.php
index 8bd2a0b2f5b73..77be815970ec0 100644
--- a/administrator/modules/mod_loginsupport/mod_loginsupport.php
+++ b/administrator/modules/mod_loginsupport/mod_loginsupport.php
@@ -1,4 +1,5 @@
get('automatic_title'))
-{
- $module->title = Text::_('MOD_LOGINSUPPORT_TITLE');
+if ($params->get('automatic_title')) {
+ $module->title = Text::_('MOD_LOGINSUPPORT_TITLE');
}
require ModuleHelper::getLayoutPath('mod_loginsupport', $params->get('layout', 'default'));
diff --git a/administrator/modules/mod_loginsupport/tmpl/default.php b/administrator/modules/mod_loginsupport/tmpl/default.php
index a01598dba85b9..78443fedee070 100644
--- a/administrator/modules/mod_loginsupport/tmpl/default.php
+++ b/administrator/modules/mod_loginsupport/tmpl/default.php
@@ -1,4 +1,5 @@
-
-
-
- get('forum_url'),
- Text::_('MOD_LOGINSUPPORT_FORUM'),
- [
- 'target' => '_blank',
- 'rel' => 'nofollow noopener',
- 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_FORUM'))
- ]
- ); ?>
-
-
- get('documentation_url'),
- Text::_('MOD_LOGINSUPPORT_DOCUMENTATION'),
- [
- 'target' => '_blank',
- 'rel' => 'nofollow noopener',
- 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_DOCUMENTATION'))
- ]
- ); ?>
-
-
- get('news_url'),
- Text::_('MOD_LOGINSUPPORT_NEWS'),
- [
- 'target' => '_blank',
- 'rel' => 'nofollow noopener',
- 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_NEWS'))
- ]
- ); ?>
-
-
+
+
+
+ get('forum_url'),
+ Text::_('MOD_LOGINSUPPORT_FORUM'),
+ [
+ 'target' => '_blank',
+ 'rel' => 'nofollow noopener',
+ 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_FORUM'))
+ ]
+ ); ?>
+
+
+ get('documentation_url'),
+ Text::_('MOD_LOGINSUPPORT_DOCUMENTATION'),
+ [
+ 'target' => '_blank',
+ 'rel' => 'nofollow noopener',
+ 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_DOCUMENTATION'))
+ ]
+ ); ?>
+
+
+ get('news_url'),
+ Text::_('MOD_LOGINSUPPORT_NEWS'),
+ [
+ 'target' => '_blank',
+ 'rel' => 'nofollow noopener',
+ 'title' => Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_('MOD_LOGINSUPPORT_NEWS'))
+ ]
+ ); ?>
+
+
diff --git a/administrator/modules/mod_menu/mod_menu.php b/administrator/modules/mod_menu/mod_menu.php
index 5c5dbbf18bb32..484dd7e24687e 100644
--- a/administrator/modules/mod_menu/mod_menu.php
+++ b/administrator/modules/mod_menu/mod_menu.php
@@ -1,4 +1,5 @@
application = $application;
- $this->root = new AdministratorMenuItem;
- }
-
- /**
- * Populate the menu items in the menu tree object
- *
- * @param Registry $params Menu configuration parameters
- * @param bool $enabled Whether the menu should be enabled or disabled
- *
- * @return AdministratorMenuItem Root node of the menu tree
- *
- * @since 3.7.0
- */
- public function load($params, $enabled)
- {
- $this->params = $params;
- $this->enabled = $enabled;
- $menutype = $this->params->get('menutype', '*');
-
- if ($menutype === '*')
- {
- $name = $this->params->get('preset', 'default');
- $this->root = MenusHelper::loadPreset($name);
- }
- else
- {
- $this->root = MenusHelper::getMenuItems($menutype, true);
-
- // Can we access everything important with this menu? Create a recovery menu!
- if ($this->enabled
- && $this->params->get('check', 1)
- && $this->check($this->root, $this->params))
- {
- $this->params->set('recovery', true);
-
- // In recovery mode, load the preset inside a special root node.
- $this->root = new AdministratorMenuItem(['level' => 0]);
- $heading = new AdministratorMenuItem(['title' => 'MOD_MENU_RECOVERY_MENU_ROOT', 'type' => 'heading']);
- $this->root->addChild($heading);
-
- MenusHelper::loadPreset('default', true, $heading);
-
- $this->preprocess($this->root);
-
- $this->root->addChild(new AdministratorMenuItem(['type' => 'separator']));
-
- // Add link to exit recovery mode
- $uri = clone Uri::getInstance();
- $uri->setVar('recover_menu', 0);
-
- $this->root->addChild(new AdministratorMenuItem(['title' => 'MOD_MENU_RECOVERY_EXIT', 'type' => 'url', 'link' => $uri->toString()]));
-
- return $this->root;
- }
- }
-
- $this->preprocess($this->root);
-
- return $this->root;
- }
-
- /**
- * Method to render a given level of a menu using provided layout file
- *
- * @param string $layoutFile The layout file to be used to render
- * @param AdministratorMenuItem $node Node to render the children of
- *
- * @return void
- *
- * @since 3.8.0
- */
- public function renderSubmenu($layoutFile, $node)
- {
- if (is_file($layoutFile))
- {
- $children = $node->getChildren();
-
- foreach ($children as $current)
- {
- $current->level = $node->level + 1;
-
- // This sets the scope to this object for the layout file and also isolates other `include`s
- require $layoutFile;
- }
- }
- }
-
- /**
- * Check the flat list of menu items for important links
- *
- * @param AdministratorMenuItem $node The menu items array
- * @param Registry $params Module options
- *
- * @return boolean Whether to show recovery menu
- *
- * @since 3.8.0
- */
- protected function check($node, Registry $params)
- {
- $me = $this->application->getIdentity();
- $authMenus = $me->authorise('core.manage', 'com_menus');
- $authModules = $me->authorise('core.manage', 'com_modules');
-
- if (!$authMenus && !$authModules)
- {
- return false;
- }
-
- $items = $node->getChildren(true);
- $types = array_column($items, 'type');
- $elements = array_column($items, 'element');
- $rMenu = $authMenus && !\in_array('com_menus', $elements);
- $rModule = $authModules && !\in_array('com_modules', $elements);
- $rContainer = !\in_array('container', $types);
-
- if ($rMenu || $rModule || $rContainer)
- {
- $recovery = $this->application->getUserStateFromRequest('mod_menu.recovery', 'recover_menu', 0, 'int');
-
- if ($recovery)
- {
- return true;
- }
-
- $missing = array();
-
- if ($rMenu)
- {
- $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_MENU_MANAGER');
- }
-
- if ($rModule)
- {
- $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_MODULE_MANAGER');
- }
-
- if ($rContainer)
- {
- $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_COMPONENTS_CONTAINER');
- }
-
- $uri = clone Uri::getInstance();
- $uri->setVar('recover_menu', 1);
-
- $table = Table::getInstance('MenuType');
- $menutype = $params->get('menutype');
-
- $table->load(array('menutype' => $menutype));
-
- $menutype = $table->get('title', $menutype);
- $message = Text::sprintf('MOD_MENU_IMPORTANT_ITEMS_INACCESSIBLE_LIST_WARNING', $menutype, implode(', ', $missing), $uri);
-
- $this->application->enqueueMessage($message, 'warning');
- }
-
- return false;
- }
-
- /**
- * Filter and perform other preparatory tasks for loaded menu items based on access rights and module configurations for display
- *
- * @param AdministratorMenuItem $parent A menu item to process
- *
- * @return array
- *
- * @since 3.8.0
- */
- protected function preprocess($parent)
- {
- $user = $this->application->getIdentity();
- $language = $this->application->getLanguage();
-
- $noSeparator = true;
- $children = $parent->getChildren();
-
- /**
- * Trigger onPreprocessMenuItems for the current level of backend menu items.
- * $children is an array of AdministratorMenuItem objects. A plugin can traverse the whole tree,
- * but new nodes will only be run through this method if their parents have not been processed yet.
- */
- $this->application->triggerEvent('onPreprocessMenuItems', array('com_menus.administrator.module', $children, $this->params, $this->enabled));
-
- foreach ($children as $item)
- {
- $itemParams = $item->getParams();
-
- // Exclude item with menu item option set to exclude from menu modules
- if ($itemParams->get('menu_show', 1) == 0)
- {
- $parent->removeChild($item);
- continue;
- }
-
- $item->scope = $item->scope ?? 'default';
- $item->icon = $item->icon ?? '';
-
- // Whether this scope can be displayed. Applies only to preset items. Db driven items should use un/published state.
- if (($item->scope === 'help' && $this->params->get('showhelp', 1) == 0) || ($item->scope === 'edit' && !$this->params->get('shownew', 1)))
- {
- $parent->removeChild($item);
- continue;
- }
-
- if (substr($item->link, 0, 8) === 'special:')
- {
- $special = substr($item->link, 8);
-
- if ($special === 'language-forum')
- {
- $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
- }
- elseif ($special === 'custom-forum')
- {
- $item->link = $this->params->get('forum_url');
- }
- }
-
- $uri = new Uri($item->link);
- $query = $uri->getQuery(true);
-
- /**
- * If component is passed in the link via option variable, we set $item->element to this value for further
- * processing. It is needed for links from menu items of third party extensions link to Joomla! core
- * components like com_categories, com_fields...
- */
- if ($option = $uri->getVar('option'))
- {
- $item->element = $option;
- }
-
- // Exclude item if is not enabled
- if ($item->element && !ComponentHelper::isEnabled($item->element))
- {
- $parent->removeChild($item);
- continue;
- }
-
- /*
- * Multilingual Associations if the site is not set as multilingual and/or Associations is not enabled in
- * the Language Filter plugin
- */
-
- if ($item->element === 'com_associations' && !Associations::isEnabled())
- {
- $parent->removeChild($item);
- continue;
- }
-
- // Exclude Mass Mail if disabled in global configuration
- if ($item->scope === 'massmail' && ($this->application->get('mailonline', 1) == 0 || $this->application->get('massmailoff', 0) == 1))
- {
- $parent->removeChild($item);
- continue;
- }
-
- // Exclude item if the component is not authorised
- $assetName = $item->element;
-
- if ($item->element === 'com_categories')
- {
- $assetName = $query['extension'] ?? 'com_content';
- }
- elseif ($item->element === 'com_fields')
- {
- // Only display Fields menus when enabled in the component
- $createFields = null;
-
- if (isset($query['context']))
- {
- $createFields = ComponentHelper::getParams(strstr($query['context'], '.', true))->get('custom_fields_enable', 1);
- }
-
- if (!$createFields)
- {
- $parent->removeChild($item);
- continue;
- }
-
- list($assetName) = isset($query['context']) ? explode('.', $query['context'], 2) : array('com_fields');
- }
- elseif ($item->element === 'com_cpanel' && $item->link === 'index.php')
- {
- continue;
- }
- elseif ($item->link === 'index.php?option=com_cpanel&view=help'
- || $item->link === 'index.php?option=com_cpanel&view=cpanel&dashboard=help')
- {
- if ($this->params->get('showhelp', 1))
- {
- continue;
- }
-
- // Exclude help menu item if set such in mod_menu
- $parent->removeChild($item);
- continue;
- }
- elseif ($item->element === 'com_workflow')
- {
- // Only display Workflow menus when enabled in the component
- $workflow = null;
-
- if (isset($query['extension']))
- {
- $parts = explode('.', $query['extension']);
-
- $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled') && $user->authorise('core.manage.workflow', $parts[0]);
- }
-
- if (!$workflow)
- {
- $parent->removeChild($item);
- continue;
- }
-
- list($assetName) = isset($query['extension']) ? explode('.', $query['extension'], 2) : array('com_workflow');
- }
- // Special case for components which only allow super user access
- elseif (\in_array($item->element, array('com_config', 'com_privacy', 'com_actionlogs'), true) && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- elseif ($item->element === 'com_joomlaupdate' && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- elseif (($item->link === 'index.php?option=com_installer&view=install' || $item->link === 'index.php?option=com_installer&view=languages')
- && !$user->authorise('core.admin'))
- {
- continue;
- }
- elseif ($item->element === 'com_admin')
- {
- if (isset($query['view']) && $query['view'] === 'sysinfo' && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- }
- elseif ($item->link === 'index.php?option=com_messages&view=messages' && !$user->authorise('core.manage', 'com_users'))
- {
- $parent->removeChild($item);
- continue;
- }
-
- if ($assetName && !$user->authorise(($item->scope === 'edit') ? 'core.create' : 'core.manage', $assetName))
- {
- $parent->removeChild($item);
- continue;
- }
-
- // Exclude if link is invalid
- if (is_null($item->link) || !\in_array($item->type, array('separator', 'heading', 'container')) && trim($item->link) === '')
- {
- $parent->removeChild($item);
- continue;
- }
-
- // Process any children if exists
- if ($item->hasChildren())
- {
- $this->preprocess($item);
- }
-
- // Populate automatic children for container items
- if ($item->type === 'container')
- {
- $exclude = (array) $itemParams->get('hideitems') ?: array();
- $components = MenusHelper::getMenuItems('main', false, $exclude);
-
- // We are adding the nodes first to preprocess them, then sort them and add them again.
- foreach ($components->getChildren() as $c)
- {
- $item->addChild($c);
- }
-
- $this->preprocess($item);
- $children = ArrayHelper::sortObjects($item->getChildren(), 'text', 1, false, true);
-
- foreach ($children as $c)
- {
- $item->addChild($c);
- }
- }
-
- // Exclude if there are no child items under heading or container
- if (\in_array($item->type, array('heading', 'container')) && !$item->hasChildren() && empty($item->components))
- {
- $parent->removeChild($item);
- continue;
- }
-
- // Remove repeated and edge positioned separators, It is important to put this check at the end of any logical filtering.
- if ($item->type === 'separator')
- {
- if ($noSeparator)
- {
- $parent->removeChild($item);
- continue;
- }
-
- $noSeparator = true;
- }
- else
- {
- $noSeparator = false;
- }
-
- // Ok we passed everything, load language at last only
- if ($item->element)
- {
- $language->load($item->element . '.sys', JPATH_ADMINISTRATOR) ||
- $language->load($item->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->element);
- }
-
- if ($item->type === 'separator' && $itemParams->get('text_separator') == 0)
- {
- $item->title = '';
- }
-
- $item->text = Text::_($item->title);
- }
-
- // If last one was a separator remove it too.
- $last = end($parent->getChildren());
-
- if ($last && $last->type === 'separator' && $last->getSibling(false) && $last->getSibling(false)->type === 'separator')
- {
- $parent->removeChild($last);
- }
- }
-
- /**
- * Method to get the CSS class name for an icon identifier or create one if
- * a custom image path is passed as the identifier
- *
- * @param AdministratorMenuItem $node Node to get icon data from
- *
- * @return string CSS class name
- *
- * @since 3.8.0
- */
- public function getIconClass($node)
- {
- $identifier = $node->class;
-
- // Top level is special
- if (trim($identifier) == '')
- {
- return null;
- }
-
- // We were passed a class name
- if (substr($identifier, 0, 6) == 'class:')
- {
- $class = substr($identifier, 6);
- }
- // We were passed background icon url. Build the CSS class for the icon
- else
- {
- if ($identifier == null)
- {
- return null;
- }
-
- $class = preg_replace('#\.[^.]*$#', '', basename($identifier));
- $class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#', '', $class);
- }
-
- $html = 'icon-' . $class . ' icon-fw';
-
- return $html;
- }
-
- /**
- * Create unique identifier
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getCounter()
- {
- $this->counter++;
-
- return $this->counter;
- }
+ /**
+ * The root of the menu
+ *
+ * @var AdministratorMenuItem
+ *
+ * @since 4.0.0
+ */
+ protected $root;
+
+ /**
+ * An array of AdministratorMenuItem nodes
+ *
+ * @var AdministratorMenuItem[]
+ *
+ * @since 4.0.0
+ */
+ protected $nodes = [];
+
+ /**
+ * The module options
+ *
+ * @var Registry
+ *
+ * @since 3.8.0
+ */
+ protected $params;
+
+ /**
+ * The menu bar state
+ *
+ * @var boolean
+ *
+ * @since 3.8.0
+ */
+ protected $enabled;
+
+ /**
+ * The application
+ *
+ * @var boolean
+ *
+ * @since 4.0.0
+ */
+ protected $application;
+
+ /**
+ * A counter for unique IDs
+ *
+ * @var integer
+ *
+ * @since 4.0.0
+ */
+ protected $counter = 0;
+
+ /**
+ * CssMenu constructor.
+ *
+ * @param CMSApplication $application The application
+ *
+ * @since 4.0.0
+ */
+ public function __construct(CMSApplication $application)
+ {
+ $this->application = $application;
+ $this->root = new AdministratorMenuItem();
+ }
+
+ /**
+ * Populate the menu items in the menu tree object
+ *
+ * @param Registry $params Menu configuration parameters
+ * @param bool $enabled Whether the menu should be enabled or disabled
+ *
+ * @return AdministratorMenuItem Root node of the menu tree
+ *
+ * @since 3.7.0
+ */
+ public function load($params, $enabled)
+ {
+ $this->params = $params;
+ $this->enabled = $enabled;
+ $menutype = $this->params->get('menutype', '*');
+
+ if ($menutype === '*') {
+ $name = $this->params->get('preset', 'default');
+ $this->root = MenusHelper::loadPreset($name);
+ } else {
+ $this->root = MenusHelper::getMenuItems($menutype, true);
+
+ // Can we access everything important with this menu? Create a recovery menu!
+ if (
+ $this->enabled
+ && $this->params->get('check', 1)
+ && $this->check($this->root, $this->params)
+ ) {
+ $this->params->set('recovery', true);
+
+ // In recovery mode, load the preset inside a special root node.
+ $this->root = new AdministratorMenuItem(['level' => 0]);
+ $heading = new AdministratorMenuItem(['title' => 'MOD_MENU_RECOVERY_MENU_ROOT', 'type' => 'heading']);
+ $this->root->addChild($heading);
+
+ MenusHelper::loadPreset('default', true, $heading);
+
+ $this->preprocess($this->root);
+
+ $this->root->addChild(new AdministratorMenuItem(['type' => 'separator']));
+
+ // Add link to exit recovery mode
+ $uri = clone Uri::getInstance();
+ $uri->setVar('recover_menu', 0);
+
+ $this->root->addChild(new AdministratorMenuItem(['title' => 'MOD_MENU_RECOVERY_EXIT', 'type' => 'url', 'link' => $uri->toString()]));
+
+ return $this->root;
+ }
+ }
+
+ $this->preprocess($this->root);
+
+ return $this->root;
+ }
+
+ /**
+ * Method to render a given level of a menu using provided layout file
+ *
+ * @param string $layoutFile The layout file to be used to render
+ * @param AdministratorMenuItem $node Node to render the children of
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ public function renderSubmenu($layoutFile, $node)
+ {
+ if (is_file($layoutFile)) {
+ $children = $node->getChildren();
+
+ foreach ($children as $current) {
+ $current->level = $node->level + 1;
+
+ // This sets the scope to this object for the layout file and also isolates other `include`s
+ require $layoutFile;
+ }
+ }
+ }
+
+ /**
+ * Check the flat list of menu items for important links
+ *
+ * @param AdministratorMenuItem $node The menu items array
+ * @param Registry $params Module options
+ *
+ * @return boolean Whether to show recovery menu
+ *
+ * @since 3.8.0
+ */
+ protected function check($node, Registry $params)
+ {
+ $me = $this->application->getIdentity();
+ $authMenus = $me->authorise('core.manage', 'com_menus');
+ $authModules = $me->authorise('core.manage', 'com_modules');
+
+ if (!$authMenus && !$authModules) {
+ return false;
+ }
+
+ $items = $node->getChildren(true);
+ $types = array_column($items, 'type');
+ $elements = array_column($items, 'element');
+ $rMenu = $authMenus && !\in_array('com_menus', $elements);
+ $rModule = $authModules && !\in_array('com_modules', $elements);
+ $rContainer = !\in_array('container', $types);
+
+ if ($rMenu || $rModule || $rContainer) {
+ $recovery = $this->application->getUserStateFromRequest('mod_menu.recovery', 'recover_menu', 0, 'int');
+
+ if ($recovery) {
+ return true;
+ }
+
+ $missing = array();
+
+ if ($rMenu) {
+ $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_MENU_MANAGER');
+ }
+
+ if ($rModule) {
+ $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_MODULE_MANAGER');
+ }
+
+ if ($rContainer) {
+ $missing[] = Text::_('MOD_MENU_IMPORTANT_ITEM_COMPONENTS_CONTAINER');
+ }
+
+ $uri = clone Uri::getInstance();
+ $uri->setVar('recover_menu', 1);
+
+ $table = Table::getInstance('MenuType');
+ $menutype = $params->get('menutype');
+
+ $table->load(array('menutype' => $menutype));
+
+ $menutype = $table->get('title', $menutype);
+ $message = Text::sprintf('MOD_MENU_IMPORTANT_ITEMS_INACCESSIBLE_LIST_WARNING', $menutype, implode(', ', $missing), $uri);
+
+ $this->application->enqueueMessage($message, 'warning');
+ }
+
+ return false;
+ }
+
+ /**
+ * Filter and perform other preparatory tasks for loaded menu items based on access rights and module configurations for display
+ *
+ * @param AdministratorMenuItem $parent A menu item to process
+ *
+ * @return array
+ *
+ * @since 3.8.0
+ */
+ protected function preprocess($parent)
+ {
+ $user = $this->application->getIdentity();
+ $language = $this->application->getLanguage();
+
+ $noSeparator = true;
+ $children = $parent->getChildren();
+
+ /**
+ * Trigger onPreprocessMenuItems for the current level of backend menu items.
+ * $children is an array of AdministratorMenuItem objects. A plugin can traverse the whole tree,
+ * but new nodes will only be run through this method if their parents have not been processed yet.
+ */
+ $this->application->triggerEvent('onPreprocessMenuItems', array('com_menus.administrator.module', $children, $this->params, $this->enabled));
+
+ foreach ($children as $item) {
+ $itemParams = $item->getParams();
+
+ // Exclude item with menu item option set to exclude from menu modules
+ if ($itemParams->get('menu_show', 1) == 0) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ $item->scope = $item->scope ?? 'default';
+ $item->icon = $item->icon ?? '';
+
+ // Whether this scope can be displayed. Applies only to preset items. Db driven items should use un/published state.
+ if (($item->scope === 'help' && $this->params->get('showhelp', 1) == 0) || ($item->scope === 'edit' && !$this->params->get('shownew', 1))) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ if (substr($item->link, 0, 8) === 'special:') {
+ $special = substr($item->link, 8);
+
+ if ($special === 'language-forum') {
+ $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
+ } elseif ($special === 'custom-forum') {
+ $item->link = $this->params->get('forum_url');
+ }
+ }
+
+ $uri = new Uri($item->link);
+ $query = $uri->getQuery(true);
+
+ /**
+ * If component is passed in the link via option variable, we set $item->element to this value for further
+ * processing. It is needed for links from menu items of third party extensions link to Joomla! core
+ * components like com_categories, com_fields...
+ */
+ if ($option = $uri->getVar('option')) {
+ $item->element = $option;
+ }
+
+ // Exclude item if is not enabled
+ if ($item->element && !ComponentHelper::isEnabled($item->element)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ /*
+ * Multilingual Associations if the site is not set as multilingual and/or Associations is not enabled in
+ * the Language Filter plugin
+ */
+
+ if ($item->element === 'com_associations' && !Associations::isEnabled()) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Exclude Mass Mail if disabled in global configuration
+ if ($item->scope === 'massmail' && ($this->application->get('mailonline', 1) == 0 || $this->application->get('massmailoff', 0) == 1)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Exclude item if the component is not authorised
+ $assetName = $item->element;
+
+ if ($item->element === 'com_categories') {
+ $assetName = $query['extension'] ?? 'com_content';
+ } elseif ($item->element === 'com_fields') {
+ // Only display Fields menus when enabled in the component
+ $createFields = null;
+
+ if (isset($query['context'])) {
+ $createFields = ComponentHelper::getParams(strstr($query['context'], '.', true))->get('custom_fields_enable', 1);
+ }
+
+ if (!$createFields) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ list($assetName) = isset($query['context']) ? explode('.', $query['context'], 2) : array('com_fields');
+ } elseif ($item->element === 'com_cpanel' && $item->link === 'index.php') {
+ continue;
+ } elseif (
+ $item->link === 'index.php?option=com_cpanel&view=help'
+ || $item->link === 'index.php?option=com_cpanel&view=cpanel&dashboard=help'
+ ) {
+ if ($this->params->get('showhelp', 1)) {
+ continue;
+ }
+
+ // Exclude help menu item if set such in mod_menu
+ $parent->removeChild($item);
+ continue;
+ } elseif ($item->element === 'com_workflow') {
+ // Only display Workflow menus when enabled in the component
+ $workflow = null;
+
+ if (isset($query['extension'])) {
+ $parts = explode('.', $query['extension']);
+
+ $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled') && $user->authorise('core.manage.workflow', $parts[0]);
+ }
+
+ if (!$workflow) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ list($assetName) = isset($query['extension']) ? explode('.', $query['extension'], 2) : array('com_workflow');
+ } elseif (\in_array($item->element, array('com_config', 'com_privacy', 'com_actionlogs'), true) && !$user->authorise('core.admin')) {
+ // Special case for components which only allow super user access
+ $parent->removeChild($item);
+ continue;
+ } elseif ($item->element === 'com_joomlaupdate' && !$user->authorise('core.admin')) {
+ $parent->removeChild($item);
+ continue;
+ } elseif (
+ ($item->link === 'index.php?option=com_installer&view=install' || $item->link === 'index.php?option=com_installer&view=languages')
+ && !$user->authorise('core.admin')
+ ) {
+ continue;
+ } elseif ($item->element === 'com_admin') {
+ if (isset($query['view']) && $query['view'] === 'sysinfo' && !$user->authorise('core.admin')) {
+ $parent->removeChild($item);
+ continue;
+ }
+ } elseif ($item->link === 'index.php?option=com_messages&view=messages' && !$user->authorise('core.manage', 'com_users')) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ if ($assetName && !$user->authorise(($item->scope === 'edit') ? 'core.create' : 'core.manage', $assetName)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Exclude if link is invalid
+ if (is_null($item->link) || !\in_array($item->type, array('separator', 'heading', 'container')) && trim($item->link) === '') {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Process any children if exists
+ if ($item->hasChildren()) {
+ $this->preprocess($item);
+ }
+
+ // Populate automatic children for container items
+ if ($item->type === 'container') {
+ $exclude = (array) $itemParams->get('hideitems') ?: array();
+ $components = MenusHelper::getMenuItems('main', false, $exclude);
+
+ // We are adding the nodes first to preprocess them, then sort them and add them again.
+ foreach ($components->getChildren() as $c) {
+ $item->addChild($c);
+ }
+
+ $this->preprocess($item);
+ $children = ArrayHelper::sortObjects($item->getChildren(), 'text', 1, false, true);
+
+ foreach ($children as $c) {
+ $item->addChild($c);
+ }
+ }
+
+ // Exclude if there are no child items under heading or container
+ if (\in_array($item->type, array('heading', 'container')) && !$item->hasChildren() && empty($item->components)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Remove repeated and edge positioned separators, It is important to put this check at the end of any logical filtering.
+ if ($item->type === 'separator') {
+ if ($noSeparator) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ $noSeparator = true;
+ } else {
+ $noSeparator = false;
+ }
+
+ // Ok we passed everything, load language at last only
+ if ($item->element) {
+ $language->load($item->element . '.sys', JPATH_ADMINISTRATOR) ||
+ $language->load($item->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->element);
+ }
+
+ if ($item->type === 'separator' && $itemParams->get('text_separator') == 0) {
+ $item->title = '';
+ }
+
+ $item->text = Text::_($item->title);
+ }
+
+ // If last one was a separator remove it too.
+ $last = end($parent->getChildren());
+
+ if ($last && $last->type === 'separator' && $last->getSibling(false) && $last->getSibling(false)->type === 'separator') {
+ $parent->removeChild($last);
+ }
+ }
+
+ /**
+ * Method to get the CSS class name for an icon identifier or create one if
+ * a custom image path is passed as the identifier
+ *
+ * @param AdministratorMenuItem $node Node to get icon data from
+ *
+ * @return string CSS class name
+ *
+ * @since 3.8.0
+ */
+ public function getIconClass($node)
+ {
+ $identifier = $node->class;
+
+ // Top level is special
+ if (trim($identifier) == '') {
+ return null;
+ }
+
+ // We were passed a class name
+ if (substr($identifier, 0, 6) == 'class:') {
+ $class = substr($identifier, 6);
+ } else {
+ // We were passed background icon url. Build the CSS class for the icon
+ if ($identifier == null) {
+ return null;
+ }
+
+ $class = preg_replace('#\.[^.]*$#', '', basename($identifier));
+ $class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#', '', $class);
+ }
+
+ $html = 'icon-' . $class . ' icon-fw';
+
+ return $html;
+ }
+
+ /**
+ * Create unique identifier
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getCounter()
+ {
+ $this->counter++;
+
+ return $this->counter;
+ }
}
diff --git a/administrator/modules/mod_menu/tmpl/default.php b/administrator/modules/mod_menu/tmpl/default.php
index 01f57febbe85b..581f4e9fb9e67 100644
--- a/administrator/modules/mod_menu/tmpl/default.php
+++ b/administrator/modules/mod_menu/tmpl/default.php
@@ -1,4 +1,5 @@
getWebAssetManager();
$wa->getRegistry()->addExtensionRegistryFile('com_cpanel');
$wa->useScript('metismenujs')
- ->registerAndUseScript('mod_menu.admin-menu', 'mod_menu/admin-menu.min.js', [], ['defer' => true], ['metismenujs'])
- ->useScript('com_cpanel.admin-system-loader');
+ ->registerAndUseScript('mod_menu.admin-menu', 'mod_menu/admin-menu.min.js', [], ['defer' => true], ['metismenujs'])
+ ->useScript('com_cpanel.admin-system-loader');
// Recurse through children of root node if they exist
-if ($root->hasChildren())
-{
- echo '';
- echo ' \n";
}
diff --git a/administrator/modules/mod_menu/tmpl/default_submenu.php b/administrator/modules/mod_menu/tmpl/default_submenu.php
index 566800a96aa78..13230279c5846 100644
--- a/administrator/modules/mod_menu/tmpl/default_submenu.php
+++ b/administrator/modules/mod_menu/tmpl/default_submenu.php
@@ -1,4 +1,5 @@
getParams();
// Build the CSS class suffix
-if (!$this->enabled)
-{
- $class .= ' disabled';
-}
-elseif ($current->type == 'separator')
-{
- $class = $current->title ? 'menuitem-group' : 'divider';
-}
-elseif ($current->hasChildren())
-{
- $class .= ' parent';
+if (!$this->enabled) {
+ $class .= ' disabled';
+} elseif ($current->type == 'separator') {
+ $class = $current->title ? 'menuitem-group' : 'divider';
+} elseif ($current->hasChildren()) {
+ $class .= ' parent';
}
-if ($current->level == 1)
-{
- $class .= ' item-level-1';
-}
-elseif ($current->level == 2)
-{
- $class .= ' item-level-2';
-}
-elseif ($current->level == 3)
-{
- $class .= ' item-level-3';
+if ($current->level == 1) {
+ $class .= ' item-level-1';
+} elseif ($current->level == 2) {
+ $class .= ' item-level-2';
+} elseif ($current->level == 3) {
+ $class .= ' item-level-3';
}
// Set the correct aria role and print the item
-if ($current->type == 'separator')
-{
- echo '';
-}
-else
-{
- echo ' ';
+if ($current->type == 'separator') {
+ echo ' ';
+} else {
+ echo ' ';
}
// Print a link if it exists
@@ -67,18 +55,14 @@
$itemIconClass = '';
$itemImage = '';
-if ($current->hasChildren())
-{
- $linkClass[] = 'has-arrow';
+if ($current->hasChildren()) {
+ $linkClass[] = 'has-arrow';
- if ($current->level > 2)
- {
- $dataToggle = ' data-bs-toggle="dropdown"';
- }
-}
-else
-{
- $linkClass[] = 'no-dropdown';
+ if ($current->level > 2) {
+ $dataToggle = ' data-bs-toggle="dropdown"';
+ }
+} else {
+ $linkClass[] = 'no-dropdown';
}
// Implode out $linkClass for rendering
@@ -100,110 +84,86 @@
$iconImage = $current->icon;
$homeImage = '';
-if ($iconClass === '' && $itemIconClass)
-{
- $iconClass = ' ';
+if ($iconClass === '' && $itemIconClass) {
+ $iconClass = ' ';
}
-if ($iconImage)
-{
- if (substr($iconImage, 0, 6) == 'class:' && substr($iconImage, 6) == 'icon-home')
- {
- $iconImage = ' ';
- $iconImage .= '' . Text::_('JDEFAULT') . ' ';
- }
- elseif (substr($iconImage, 0, 6) == 'image:')
- {
- $iconImage = ' ' . substr($iconImage, 6) . ' ';
- }
- else
- {
- $iconImage = '';
- }
+if ($iconImage) {
+ if (substr($iconImage, 0, 6) == 'class:' && substr($iconImage, 6) == 'icon-home') {
+ $iconImage = ' ';
+ $iconImage .= '' . Text::_('JDEFAULT') . ' ';
+ } elseif (substr($iconImage, 0, 6) == 'image:') {
+ $iconImage = ' ' . substr($iconImage, 6) . ' ';
+ } else {
+ $iconImage = '';
+ }
}
$itemImage = (empty($itemIconClass) && $itemImage) ? ' ' : '';
// If the item image is not set, the item title would not have margin. Here we add it.
-if ($icon == '' && $iconClass == '' && $current->level == 1 && $current->target == '')
-{
- $iconClass = ' ';
+if ($icon == '' && $iconClass == '' && $current->level == 1 && $current->target == '') {
+ $iconClass = ' ';
+}
+
+if ($link != '' && $current->target != '') {
+ echo ''
+ . $iconClass
+ . '' . $ajax . ' ';
+} elseif ($link != '' && $current->type !== 'separator') {
+ echo ''
+ . $iconClass
+ . '' . $iconImage . ' ';
+} elseif ($current->title != '' && $current->type !== 'separator') {
+ echo ''
+ . $iconClass
+ . '' . $ajax . ' ';
+} elseif ($current->title != '' && $current->type === 'separator') {
+ echo '' . $ajax;
+} else {
+ echo '' . Text::_($current->title) . ' ' . $ajax;
+}
+
+if ($currentParams->get('menu-quicktask') && (int) $this->params->get('shownew', 1) === 1) {
+ $params = $current->getParams();
+ $user = $this->application->getIdentity();
+ $link = $params->get('menu-quicktask');
+ $icon = $params->get('menu-quicktask-icon', 'plus');
+ $title = $params->get('menu-quicktask-title', 'MOD_MENU_QUICKTASK_NEW');
+ $permission = $params->get('menu-quicktask-permission');
+ $scope = $current->scope !== 'default' ? $current->scope : null;
+
+ if (!$permission || $user->authorise($permission, $scope)) {
+ echo '';
+ }
+}
+
+if (!empty($current->dashboard)) {
+ $titleDashboard = Text::sprintf('MOD_MENU_DASHBOARD_LINK', Text::_($current->title));
+ echo '';
}
-if ($link != '' && $current->target != '')
-{
- echo ''
- . $iconClass
- . '' . $ajax . ' ';
-}
-elseif ($link != '' && $current->type !== 'separator')
-{
- echo ''
- . $iconClass
- . '' . $iconImage . ' ';
-}
-elseif ($current->title != '' && $current->type !== 'separator')
-{
- echo ''
- . $iconClass
- . '' . $ajax . ' ';
-}
-elseif ($current->title != '' && $current->type === 'separator')
-{
- echo '' . $ajax;
-}
-else
-{
- echo '' . Text::_($current->title) . ' ' . $ajax;
-}
+// Recurse through children if they exist
+if ($this->enabled && $current->hasChildren()) {
+ if ($current->level > 1) {
+ $id = $current->id ? ' id="menu-' . strtolower($current->id) . '"' : '';
-if ($currentParams->get('menu-quicktask') && (int) $this->params->get('shownew', 1) === 1)
-{
- $params = $current->getParams();
- $user = $this->application->getIdentity();
- $link = $params->get('menu-quicktask');
- $icon = $params->get('menu-quicktask-icon', 'plus');
- $title = $params->get('menu-quicktask-title', 'MOD_MENU_QUICKTASK_NEW');
- $permission = $params->get('menu-quicktask-permission');
- $scope = $current->scope !== 'default' ? $current->scope : null;
-
- if (!$permission || $user->authorise($permission, $scope))
- {
- echo '';
- }
-}
+ echo '' . "\n";
+ } else {
+ echo '' . "\n";
+ }
-if (!empty($current->dashboard))
-{
- $titleDashboard = Text::sprintf('MOD_MENU_DASHBOARD_LINK', Text::_($current->title));
- echo '';
-}
+ // WARNING: Do not use direct 'include' or 'require' as it is important to isolate the scope for each call
+ $this->renderSubmenu(__FILE__, $current);
-// Recurse through children if they exist
-if ($this->enabled && $current->hasChildren())
-{
- if ($current->level > 1)
- {
- $id = $current->id ? ' id="menu-' . strtolower($current->id) . '"' : '';
-
- echo '' . "\n";
- }
- else
- {
- echo '' . "\n";
- }
-
- // WARNING: Do not use direct 'include' or 'require' as it is important to isolate the scope for each call
- $this->renderSubmenu(__FILE__, $current);
-
- echo " \n";
+ echo " \n";
}
echo " \n";
diff --git a/administrator/modules/mod_messages/mod_messages.php b/administrator/modules/mod_messages/mod_messages.php
index 296f4e1109a59..36ce8130708c9 100644
--- a/administrator/modules/mod_messages/mod_messages.php
+++ b/administrator/modules/mod_messages/mod_messages.php
@@ -1,4 +1,5 @@
getIdentity()->authorise('core.login.admin') || !$app->getIdentity()->authorise('core.manage', 'com_messages'))
-{
- return;
+if (!$app->getIdentity()->authorise('core.login.admin') || !$app->getIdentity()->authorise('core.manage', 'com_messages')) {
+ return;
}
// Try to get the items from the messages model
-try
-{
- /** @var \Joomla\Component\Messages\Administrator\Model\MessagesModel $messagesModel */
- $messagesModel = $app->bootComponent('com_messages')->getMVCFactory()
- ->createModel('Messages', 'Administrator', ['ignore_request' => true]);
- $messagesModel->setState('filter.state', 0);
- $messages = $messagesModel->getItems();
-}
-catch (RuntimeException $e)
-{
- $messages = [];
+try {
+ /** @var \Joomla\Component\Messages\Administrator\Model\MessagesModel $messagesModel */
+ $messagesModel = $app->bootComponent('com_messages')->getMVCFactory()
+ ->createModel('Messages', 'Administrator', ['ignore_request' => true]);
+ $messagesModel->setState('filter.state', 0);
+ $messages = $messagesModel->getItems();
+} catch (RuntimeException $e) {
+ $messages = [];
- // Still render the error message from the Exception object
- $app->enqueueMessage($e->getMessage(), 'error');
+ // Still render the error message from the Exception object
+ $app->enqueueMessage($e->getMessage(), 'error');
}
$countUnread = count($messages);
diff --git a/administrator/modules/mod_messages/tmpl/default.php b/administrator/modules/mod_messages/tmpl/default.php
index 43da51c9c8d8d..77e03cef5e79e 100644
--- a/administrator/modules/mod_messages/tmpl/default.php
+++ b/administrator/modules/mod_messages/tmpl/default.php
@@ -1,4 +1,5 @@
input->getBool('hidemainmenu');
-if ($hideLinks || $countUnread < 1)
-{
- return;
+if ($hideLinks || $countUnread < 1) {
+ return;
}
$route = 'index.php?option=com_messages&view=messages';
?>
diff --git a/administrator/modules/mod_multilangstatus/mod_multilangstatus.php b/administrator/modules/mod_multilangstatus/mod_multilangstatus.php
index 463f03f1b4774..959e1fb4fefe9 100644
--- a/administrator/modules/mod_multilangstatus/mod_multilangstatus.php
+++ b/administrator/modules/mod_multilangstatus/mod_multilangstatus.php
@@ -1,4 +1,5 @@
input->getBool('hidemainmenu');
-if (!$multilanguageEnabled || $hideLinks)
-{
- return;
+if (!$multilanguageEnabled || $hideLinks) {
+ return;
}
$modalHTML = HTMLHelper::_(
- 'bootstrap.renderModal',
- 'multiLangModal',
- array(
- 'title' => Text::_('MOD_MULTILANGSTATUS'),
- 'url' => Route::_('index.php?option=com_languages&view=multilangstatus&tmpl=component'),
- 'height' => '400px',
- 'width' => '800px',
- 'bodyHeight' => 70,
- 'modalWidth' => 80,
- 'footer' => '' . Text::_('JTOOLBAR_CLOSE') . ' ',
- )
+ 'bootstrap.renderModal',
+ 'multiLangModal',
+ array(
+ 'title' => Text::_('MOD_MULTILANGSTATUS'),
+ 'url' => Route::_('index.php?option=com_languages&view=multilangstatus&tmpl=component'),
+ 'height' => '400px',
+ 'width' => '800px',
+ 'bodyHeight' => 70,
+ 'modalWidth' => 80,
+ 'footer' => '' . Text::_('JTOOLBAR_CLOSE') . ' ',
+ )
);
$app->getDocument()->getWebAssetManager()
- ->registerAndUseScript('mod_multilangstatus.admin', 'mod_multilangstatus/admin-multilangstatus.min.js', [], ['type' => 'module', 'defer' => true]);
+ ->registerAndUseScript('mod_multilangstatus.admin', 'mod_multilangstatus/admin-multilangstatus.min.js', [], ['type' => 'module', 'defer' => true]);
?>
diff --git a/administrator/modules/mod_popular/mod_popular.php b/administrator/modules/mod_popular/mod_popular.php
index 9b6d859456671..6e283cfd5864c 100644
--- a/administrator/modules/mod_popular/mod_popular.php
+++ b/administrator/modules/mod_popular/mod_popular.php
@@ -1,4 +1,5 @@
get('automatic_title', 0))
-{
- $module->title = PopularHelper::getTitle($params);
+if ($params->get('automatic_title', 0)) {
+ $module->title = PopularHelper::getTitle($params);
}
// If recording of hits is disabled.
-if (!ComponentHelper::getParams('com_content')->get('record_hits', 1))
-{
- echo LayoutHelper::render('joomla.content.emptystate_module', [
- 'title' => 'JGLOBAL_RECORD_HITS_DISABLED',
- 'icon' => 'icon-minus-circle',
- ]
- );
-
- return;
+if (!ComponentHelper::getParams('com_content')->get('record_hits', 1)) {
+ echo LayoutHelper::render('joomla.content.emptystate_module', [
+ 'title' => 'JGLOBAL_RECORD_HITS_DISABLED',
+ 'icon' => 'icon-minus-circle',
+ ]);
+
+ return;
}
// If there are some articles to display.
-if (count($list))
-{
- require ModuleHelper::getLayoutPath('mod_popular', $params->get('layout', 'default'));
+if (count($list)) {
+ require ModuleHelper::getLayoutPath('mod_popular', $params->get('layout', 'default'));
- return;
+ return;
}
// If there are no articles to display, show empty state.
$app->getLanguage()->load('com_content');
echo LayoutHelper::render('joomla.content.emptystate_module', [
- 'textPrefix' => 'COM_CONTENT',
- 'icon' => 'icon-copy',
- ]
-);
+ 'textPrefix' => 'COM_CONTENT',
+ 'icon' => 'icon-copy',
+ ]);
diff --git a/administrator/modules/mod_popular/src/Helper/PopularHelper.php b/administrator/modules/mod_popular/src/Helper/PopularHelper.php
index 1572b9b821013..723ee72a9d8e6 100644
--- a/administrator/modules/mod_popular/src/Helper/PopularHelper.php
+++ b/administrator/modules/mod_popular/src/Helper/PopularHelper.php
@@ -1,4 +1,5 @@
setState('list.select', 'a.id, a.title, a.checked_out, a.checked_out_time, ' .
- ' a.publish_up, a.hits'
- );
-
- // Set Ordering filter
- $model->setState('list.ordering', 'a.hits');
- $model->setState('list.direction', 'DESC');
-
- // Set Category Filter
- $categoryId = $params->get('catid', null);
-
- if (is_numeric($categoryId))
- {
- $model->setState('filter.category_id', $categoryId);
- }
-
- // Set User Filter.
- $userId = $user->get('id');
-
- switch ($params->get('user_id', '0'))
- {
- case 'by_me':
- $model->setState('filter.author_id', $userId);
- break;
-
- case 'not_me':
- $model->setState('filter.author_id', $userId);
- $model->setState('filter.author_id.include', false);
- break;
- }
-
- // Set the Start and Limit
- $model->setState('list.start', 0);
- $model->setState('list.limit', $params->get('count', 5));
-
- $items = $model->getItems();
-
- if ($error = $model->getError())
- {
- throw new \Exception($error, 500);
- }
-
- // Set the links
- foreach ($items as &$item)
- {
- $item->link = '';
-
- if ($user->authorise('core.edit', 'com_content.article.' . $item->id)
- || ($user->authorise('core.edit.own', 'com_content.article.' . $item->id) && ($userId === $item->created_by)))
- {
- $item->link = Route::_('index.php?option=com_content&task=article.edit&id=' . $item->id);
- }
- }
-
- return $items;
- }
-
- /**
- * Get the alternate title for the module
- *
- * @param Registry $params The module parameters.
- *
- * @return string The alternate title for the module.
- */
- public static function getTitle($params)
- {
- $who = $params->get('user_id', 0);
- $catid = (int) $params->get('catid', null);
- $title = '';
-
- if ($catid)
- {
- $category = Categories::getInstance('Content')->get($catid);
- $title = Text::_('MOD_POPULAR_UNEXISTING');
-
- if ($category)
- {
- $title = $category->title;
- }
- }
-
- return Text::plural(
- 'MOD_POPULAR_TITLE' . ($catid ? '_CATEGORY' : '') . ($who != '0' ? "_$who" : ''),
- (int) $params->get('count', 5),
- $title
- );
- }
+ /**
+ * Get a list of the most popular articles.
+ *
+ * @param Registry &$params The module parameters.
+ * @param ArticlesModel $model The model.
+ *
+ * @return mixed An array of articles, or false on error.
+ *
+ * @throws \Exception
+ */
+ public static function getList(Registry &$params, ArticlesModel $model)
+ {
+ $user = Factory::getUser();
+
+ // Set List SELECT
+ $model->setState('list.select', 'a.id, a.title, a.checked_out, a.checked_out_time, ' .
+ ' a.publish_up, a.hits');
+
+ // Set Ordering filter
+ $model->setState('list.ordering', 'a.hits');
+ $model->setState('list.direction', 'DESC');
+
+ // Set Category Filter
+ $categoryId = $params->get('catid', null);
+
+ if (is_numeric($categoryId)) {
+ $model->setState('filter.category_id', $categoryId);
+ }
+
+ // Set User Filter.
+ $userId = $user->get('id');
+
+ switch ($params->get('user_id', '0')) {
+ case 'by_me':
+ $model->setState('filter.author_id', $userId);
+ break;
+
+ case 'not_me':
+ $model->setState('filter.author_id', $userId);
+ $model->setState('filter.author_id.include', false);
+ break;
+ }
+
+ // Set the Start and Limit
+ $model->setState('list.start', 0);
+ $model->setState('list.limit', $params->get('count', 5));
+
+ $items = $model->getItems();
+
+ if ($error = $model->getError()) {
+ throw new \Exception($error, 500);
+ }
+
+ // Set the links
+ foreach ($items as &$item) {
+ $item->link = '';
+
+ if (
+ $user->authorise('core.edit', 'com_content.article.' . $item->id)
+ || ($user->authorise('core.edit.own', 'com_content.article.' . $item->id) && ($userId === $item->created_by))
+ ) {
+ $item->link = Route::_('index.php?option=com_content&task=article.edit&id=' . $item->id);
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Get the alternate title for the module
+ *
+ * @param Registry $params The module parameters.
+ *
+ * @return string The alternate title for the module.
+ */
+ public static function getTitle($params)
+ {
+ $who = $params->get('user_id', 0);
+ $catid = (int) $params->get('catid', null);
+ $title = '';
+
+ if ($catid) {
+ $category = Categories::getInstance('Content')->get($catid);
+ $title = Text::_('MOD_POPULAR_UNEXISTING');
+
+ if ($category) {
+ $title = $category->title;
+ }
+ }
+
+ return Text::plural(
+ 'MOD_POPULAR_TITLE' . ($catid ? '_CATEGORY' : '') . ($who != '0' ? "_$who" : ''),
+ (int) $params->get('count', 5),
+ $title
+ );
+ }
}
diff --git a/administrator/modules/mod_popular/tmpl/default.php b/administrator/modules/mod_popular/tmpl/default.php
index 6992abc8befe8..4949093a8177e 100644
--- a/administrator/modules/mod_popular/tmpl/default.php
+++ b/administrator/modules/mod_popular/tmpl/default.php
@@ -1,4 +1,5 @@
- title; ?>
-
-
-
-
-
-
-
-
-
- $item) : ?>
-
- hits; ?>
- = 10000 ? 'danger' : ($hits >= 1000 ? 'warning' : ($hits >= 100 ? 'info' : 'secondary'))); ?>
-
-
- checked_out) : ?>
- editor, $item->checked_out_time); ?>
-
- link) : ?>
-
- title, ENT_QUOTES, 'UTF-8'); ?>
-
-
- title, ENT_QUOTES, 'UTF-8'); ?>
-
-
-
- hits; ?>
-
-
- publish_up, Text::_('DATE_FORMAT_LC4')); ?>
-
-
-
-
-
-
-
-
-
-
-
+ title; ?>
+
+
+
+
+
+
+
+
+
+ $item) : ?>
+
+ hits; ?>
+ = 10000 ? 'danger' : ($hits >= 1000 ? 'warning' : ($hits >= 100 ? 'info' : 'secondary'))); ?>
+
+
+ checked_out) : ?>
+ editor, $item->checked_out_time); ?>
+
+ link) : ?>
+
+ title, ENT_QUOTES, 'UTF-8'); ?>
+
+
+ title, ENT_QUOTES, 'UTF-8'); ?>
+
+
+
+ hits; ?>
+
+
+ publish_up, Text::_('DATE_FORMAT_LC4')); ?>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_post_installation_messages/mod_post_installation_messages.php b/administrator/modules/mod_post_installation_messages/mod_post_installation_messages.php
index 1812161ddf736..9f9268c4ccf59 100644
--- a/administrator/modules/mod_post_installation_messages/mod_post_installation_messages.php
+++ b/administrator/modules/mod_post_installation_messages/mod_post_installation_messages.php
@@ -1,4 +1,5 @@
bootComponent('com_postinstall')->getMVCFactory()
- ->createModel('Messages', 'Administrator', ['ignore_request' => true]);
- $messagesCount = $messagesModel->getItemsCount();
-}
-catch (RuntimeException $e)
-{
- $messagesCount = 0;
+try {
+ /** @var \Joomla\Component\Postinstall\Administrator\Model\MessagesModel $messagesModel */
+ $messagesModel = $app->bootComponent('com_postinstall')->getMVCFactory()
+ ->createModel('Messages', 'Administrator', ['ignore_request' => true]);
+ $messagesCount = $messagesModel->getItemsCount();
+} catch (RuntimeException $e) {
+ $messagesCount = 0;
- // Still render the error message from the Exception object
- $app->enqueueMessage($e->getMessage(), 'error');
+ // Still render the error message from the Exception object
+ $app->enqueueMessage($e->getMessage(), 'error');
}
$joomlaFilesExtensionId = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
diff --git a/administrator/modules/mod_post_installation_messages/tmpl/default.php b/administrator/modules/mod_post_installation_messages/tmpl/default.php
index 8efb6503ef488..f2b9cef548449 100644
--- a/administrator/modules/mod_post_installation_messages/tmpl/default.php
+++ b/administrator/modules/mod_post_installation_messages/tmpl/default.php
@@ -1,4 +1,5 @@
input->getBool('hidemainmenu');
-if ($hideLinks || $messagesCount < 1)
-{
- return;
+if ($hideLinks || $messagesCount < 1) {
+ return;
}
?>
getIdentity()->authorise('core.manage', 'com_postinstall')) : ?>
-
+
diff --git a/administrator/modules/mod_privacy_dashboard/mod_privacy_dashboard.php b/administrator/modules/mod_privacy_dashboard/mod_privacy_dashboard.php
index a58da39b7ee66..28c4bb5e75f88 100644
--- a/administrator/modules/mod_privacy_dashboard/mod_privacy_dashboard.php
+++ b/administrator/modules/mod_privacy_dashboard/mod_privacy_dashboard.php
@@ -1,4 +1,5 @@
getIdentity()->authorise('core.admin'))
-{
- return;
+if (!$app->getIdentity()->authorise('core.admin')) {
+ return;
}
// Boot component to ensure HTML helpers are loaded
@@ -25,19 +25,15 @@
// Load the privacy component language file.
$lang = $app->getLanguage();
$lang->load('com_privacy', JPATH_ADMINISTRATOR)
- || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
+ || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
$list = PrivacyDashboardHelper::getData();
-if (count($list))
-{
- require ModuleHelper::getLayoutPath('mod_privacy_dashboard', $params->get('layout', 'default'));
-}
-else
-{
- echo LayoutHelper::render('joomla.content.emptystate_module', [
- 'textPrefix' => 'COM_PRIVACY_REQUESTS',
- 'icon' => 'icon-lock',
- ]
- );
+if (count($list)) {
+ require ModuleHelper::getLayoutPath('mod_privacy_dashboard', $params->get('layout', 'default'));
+} else {
+ echo LayoutHelper::render('joomla.content.emptystate_module', [
+ 'textPrefix' => 'COM_PRIVACY_REQUESTS',
+ 'icon' => 'icon-lock',
+ ]);
}
diff --git a/administrator/modules/mod_privacy_dashboard/src/Helper/PrivacyDashboardHelper.php b/administrator/modules/mod_privacy_dashboard/src/Helper/PrivacyDashboardHelper.php
index 7a7088d69cc8e..06f358bdbd496 100644
--- a/administrator/modules/mod_privacy_dashboard/src/Helper/PrivacyDashboardHelper.php
+++ b/administrator/modules/mod_privacy_dashboard/src/Helper/PrivacyDashboardHelper.php
@@ -1,4 +1,5 @@
getQuery(true)
- ->select(
- [
- 'COUNT(*) AS count',
- $db->quoteName('status'),
- $db->quoteName('request_type'),
- ]
- )
- ->from($db->quoteName('#__privacy_requests'))
- ->group($db->quoteName('status'))
- ->group($db->quoteName('request_type'));
+ /**
+ * Method to retrieve information about the site privacy requests
+ *
+ * @return array Array containing site privacy requests
+ *
+ * @since 3.9.0
+ */
+ public static function getData()
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'COUNT(*) AS count',
+ $db->quoteName('status'),
+ $db->quoteName('request_type'),
+ ]
+ )
+ ->from($db->quoteName('#__privacy_requests'))
+ ->group($db->quoteName('status'))
+ ->group($db->quoteName('request_type'));
- $db->setQuery($query);
+ $db->setQuery($query);
- try
- {
- return $db->loadObjectList();
- }
- catch (ExecutionFailureException $e)
- {
- return [];
- }
- }
+ try {
+ return $db->loadObjectList();
+ } catch (ExecutionFailureException $e) {
+ return [];
+ }
+ }
}
diff --git a/administrator/modules/mod_privacy_dashboard/tmpl/default.php b/administrator/modules/mod_privacy_dashboard/tmpl/default.php
index 0450c0f4746ba..c82f8205ede0a 100644
--- a/administrator/modules/mod_privacy_dashboard/tmpl/default.php
+++ b/administrator/modules/mod_privacy_dashboard/tmpl/default.php
@@ -1,4 +1,5 @@
- title; ?>
-
-
-
-
-
-
-
-
-
- $item) : ?>
- status, array(0, 1))) : ?>
- count; ?>
-
- count; ?>
-
-
-
- request_type); ?>
-
-
-
- status); ?>
-
-
- count; ?>
-
-
-
-
-
-
-
-
-
-
-
+ title; ?>
+
+
+
+
+
+
+
+
+
+ $item) : ?>
+ status, array(0, 1))) : ?>
+ count; ?>
+
+ count; ?>
+
+
+
+ request_type); ?>
+
+
+
+ status); ?>
+
+
+ count; ?>
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/administrator/modules/mod_privacy_status/mod_privacy_status.php b/administrator/modules/mod_privacy_status/mod_privacy_status.php
index 964b15118d67d..afa43af91bbf1 100644
--- a/administrator/modules/mod_privacy_status/mod_privacy_status.php
+++ b/administrator/modules/mod_privacy_status/mod_privacy_status.php
@@ -1,4 +1,5 @@
getIdentity()->authorise('core.admin'))
-{
- return;
+if (!$app->getIdentity()->authorise('core.admin')) {
+ return;
}
// Boot component to ensure HTML helpers are loaded
@@ -27,7 +27,7 @@
// Load the privacy component language file.
$lang = $app->getLanguage();
$lang->load('com_privacy', JPATH_ADMINISTRATOR)
- || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
+ || $lang->load('com_privacy', JPATH_ADMINISTRATOR . '/components/com_privacy');
$privacyPolicyInfo = PrivacyStatusHelper::getPrivacyPolicyInfo();
$requestFormPublished = PrivacyStatusHelper::getRequestFormPublished();
diff --git a/administrator/modules/mod_privacy_status/src/Helper/PrivacyStatusHelper.php b/administrator/modules/mod_privacy_status/src/Helper/PrivacyStatusHelper.php
index 2bbeab6647f34..2d7bb9ea2a17a 100644
--- a/administrator/modules/mod_privacy_status/src/Helper/PrivacyStatusHelper.php
+++ b/administrator/modules/mod_privacy_status/src/Helper/PrivacyStatusHelper.php
@@ -1,4 +1,5 @@
false,
- 'articlePublished' => false,
- 'editLink' => '',
- ];
-
- /*
- * Prior to 3.9.0 it was common for a plugin such as the User - Profile plugin to define a privacy policy or
- * terms of service article, therefore we will also import the user plugin group to process this event.
- */
- PluginHelper::importPlugin('privacy');
- PluginHelper::importPlugin('user');
-
- Factory::getApplication()->triggerEvent('onPrivacyCheckPrivacyPolicyPublished', [&$policy]);
-
- return $policy;
- }
-
- /**
- * Check whether there is a menu item for the request form
- *
- * @return array Array containing a status of whether a menu is published for the request form and its current link
- *
- * @since 4.0.0
- */
- public static function getRequestFormPublished()
- {
- $status = [
- 'exists' => false,
- 'published' => false,
- 'link' => '',
- ];
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id'),
- $db->quoteName('published'),
- $db->quoteName('language'),
- ]
- )
- ->from($db->quoteName('#__menu'))
- ->where(
- [
- $db->quoteName('client_id') . ' = 0',
- $db->quoteName('link') . ' = ' . $db->quote('index.php?option=com_privacy&view=request'),
- ]
- )
- ->setLimit(1);
- $db->setQuery($query);
-
- $menuItem = $db->loadObject();
-
- // Check if the menu item exists in database
- if ($menuItem)
- {
- $status['exists'] = true;
-
- // Check if the menu item is published
- if ($menuItem->published == 1)
- {
- $status['published'] = true;
- }
-
- // Add language to the url if the site is multilingual
- if (Multilanguage::isEnabled() && $menuItem->language && $menuItem->language !== '*')
- {
- $lang = '&lang=' . $menuItem->language;
- }
- else
- {
- $lang = '';
- }
- }
-
- $linkMode = Factory::getApplication()->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
-
- if (!$menuItem)
- {
- if (Multilanguage::isEnabled())
- {
- // Find the Itemid of the home menu item tagged to the site default language
- $params = ComponentHelper::getParams('com_languages');
- $defaultSiteLanguage = $params->get('site');
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__menu'))
- ->where(
- [
- $db->quoteName('client_id') . ' = 0',
- $db->quoteName('home') . ' = 1',
- $db->quoteName('language') . ' = :language',
- ]
- )
- ->bind(':language', $defaultSiteLanguage)
- ->setLimit(1);
- $db->setQuery($query);
-
- $homeId = (int) $db->loadResult();
- $itemId = $homeId ? '&Itemid=' . $homeId : '';
- }
- else
- {
- $itemId = '';
- }
-
- $status['link'] = Route::link('site', 'index.php?option=com_privacy&view=request' . $itemId, true, $linkMode);
- }
- else
- {
- $status['link'] = Route::link('site', 'index.php?Itemid=' . $menuItem->id . $lang, true, $linkMode);
- }
-
- return $status;
- }
-
- /**
- * Method to return number privacy requests older than X days.
- *
- * @return integer
- *
- * @since 4.0.0
- */
- public static function getNumberUrgentRequests()
- {
- // Load the parameters.
- $params = ComponentHelper::getComponent('com_privacy')->getParams();
- $notify = (int) $params->get('notify', 14);
- $now = Factory::getDate()->toSql();
- $period = '-' . $notify;
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true);
- $query->select('COUNT(*)')
- ->from($db->quoteName('#__privacy_requests'))
- ->where(
- [
- $db->quoteName('status') . ' = 1',
- $query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('requested_at'),
- ]
- );
- $db->setQuery($query);
-
- return (int) $db->loadResult();
- }
+ /**
+ * Get the information about the published privacy policy
+ *
+ * @return array Array containing a status of whether a privacy policy is set and a link to the policy document for editing
+ *
+ * @since 4.0.0
+ */
+ public static function getPrivacyPolicyInfo()
+ {
+ $policy = [
+ 'published' => false,
+ 'articlePublished' => false,
+ 'editLink' => '',
+ ];
+
+ /*
+ * Prior to 3.9.0 it was common for a plugin such as the User - Profile plugin to define a privacy policy or
+ * terms of service article, therefore we will also import the user plugin group to process this event.
+ */
+ PluginHelper::importPlugin('privacy');
+ PluginHelper::importPlugin('user');
+
+ Factory::getApplication()->triggerEvent('onPrivacyCheckPrivacyPolicyPublished', [&$policy]);
+
+ return $policy;
+ }
+
+ /**
+ * Check whether there is a menu item for the request form
+ *
+ * @return array Array containing a status of whether a menu is published for the request form and its current link
+ *
+ * @since 4.0.0
+ */
+ public static function getRequestFormPublished()
+ {
+ $status = [
+ 'exists' => false,
+ 'published' => false,
+ 'link' => '',
+ ];
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('published'),
+ $db->quoteName('language'),
+ ]
+ )
+ ->from($db->quoteName('#__menu'))
+ ->where(
+ [
+ $db->quoteName('client_id') . ' = 0',
+ $db->quoteName('link') . ' = ' . $db->quote('index.php?option=com_privacy&view=request'),
+ ]
+ )
+ ->setLimit(1);
+ $db->setQuery($query);
+
+ $menuItem = $db->loadObject();
+
+ // Check if the menu item exists in database
+ if ($menuItem) {
+ $status['exists'] = true;
+
+ // Check if the menu item is published
+ if ($menuItem->published == 1) {
+ $status['published'] = true;
+ }
+
+ // Add language to the url if the site is multilingual
+ if (Multilanguage::isEnabled() && $menuItem->language && $menuItem->language !== '*') {
+ $lang = '&lang=' . $menuItem->language;
+ } else {
+ $lang = '';
+ }
+ }
+
+ $linkMode = Factory::getApplication()->get('force_ssl', 0) == 2 ? Route::TLS_FORCE : Route::TLS_IGNORE;
+
+ if (!$menuItem) {
+ if (Multilanguage::isEnabled()) {
+ // Find the Itemid of the home menu item tagged to the site default language
+ $params = ComponentHelper::getParams('com_languages');
+ $defaultSiteLanguage = $params->get('site');
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__menu'))
+ ->where(
+ [
+ $db->quoteName('client_id') . ' = 0',
+ $db->quoteName('home') . ' = 1',
+ $db->quoteName('language') . ' = :language',
+ ]
+ )
+ ->bind(':language', $defaultSiteLanguage)
+ ->setLimit(1);
+ $db->setQuery($query);
+
+ $homeId = (int) $db->loadResult();
+ $itemId = $homeId ? '&Itemid=' . $homeId : '';
+ } else {
+ $itemId = '';
+ }
+
+ $status['link'] = Route::link('site', 'index.php?option=com_privacy&view=request' . $itemId, true, $linkMode);
+ } else {
+ $status['link'] = Route::link('site', 'index.php?Itemid=' . $menuItem->id . $lang, true, $linkMode);
+ }
+
+ return $status;
+ }
+
+ /**
+ * Method to return number privacy requests older than X days.
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ public static function getNumberUrgentRequests()
+ {
+ // Load the parameters.
+ $params = ComponentHelper::getComponent('com_privacy')->getParams();
+ $notify = (int) $params->get('notify', 14);
+ $now = Factory::getDate()->toSql();
+ $period = '-' . $notify;
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true);
+ $query->select('COUNT(*)')
+ ->from($db->quoteName('#__privacy_requests'))
+ ->where(
+ [
+ $db->quoteName('status') . ' = 1',
+ $query->dateAdd($db->quote($now), $period, 'DAY') . ' > ' . $db->quoteName('requested_at'),
+ ]
+ );
+ $db->setQuery($query);
+
+ return (int) $db->loadResult();
+ }
}
diff --git a/administrator/modules/mod_privacy_status/tmpl/default.php b/administrator/modules/mod_privacy_status/tmpl/default.php
index 35bc270b8ccc3..5b1c3663b872e 100644
--- a/administrator/modules/mod_privacy_status/tmpl/default.php
+++ b/administrator/modules/mod_privacy_status/tmpl/default.php
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0) : ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_quickicon/services/provider.php b/administrator/modules/mod_quickicon/services/provider.php
index 86c396968362b..34f4e43c5ac33 100644
--- a/administrator/modules/mod_quickicon/services/provider.php
+++ b/administrator/modules/mod_quickicon/services/provider.php
@@ -1,4 +1,5 @@
registerServiceProvider(new ModuleDispatcherFactory('\\Joomla\\Module\\Quickicon'));
- $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\Quickicon\\Administrator\\Helper'));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->registerServiceProvider(new ModuleDispatcherFactory('\\Joomla\\Module\\Quickicon'));
+ $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\Quickicon\\Administrator\\Helper'));
- $container->registerServiceProvider(new Module);
- }
+ $container->registerServiceProvider(new Module());
+ }
};
diff --git a/administrator/modules/mod_quickicon/src/Dispatcher/Dispatcher.php b/administrator/modules/mod_quickicon/src/Dispatcher/Dispatcher.php
index f047562713c66..6db1a597d1818 100644
--- a/administrator/modules/mod_quickicon/src/Dispatcher/Dispatcher.php
+++ b/administrator/modules/mod_quickicon/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
app->bootModule('mod_quickicon', 'administrator')->getHelper('QuickIconHelper');
- $data['buttons'] = $helper->getButtons($data['params'], $this->getApplication());
+ $helper = $this->app->bootModule('mod_quickicon', 'administrator')->getHelper('QuickIconHelper');
+ $data['buttons'] = $helper->getButtons($data['params'], $this->getApplication());
- return $data;
- }
+ return $data;
+ }
}
diff --git a/administrator/modules/mod_quickicon/src/Event/QuickIconsEvent.php b/administrator/modules/mod_quickicon/src/Event/QuickIconsEvent.php
index 3e0da89908299..c0cdac0ad37fa 100644
--- a/administrator/modules/mod_quickicon/src/Event/QuickIconsEvent.php
+++ b/administrator/modules/mod_quickicon/src/Event/QuickIconsEvent.php
@@ -1,4 +1,5 @@
context;
- }
+ /**
+ * Get the event context
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
- /**
- * Set the event context
- *
- * @param string $context The event context
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function setContext($context)
- {
- $this->context = $context;
+ /**
+ * Set the event context
+ *
+ * @param string $context The event context
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function setContext($context)
+ {
+ $this->context = $context;
- return $context;
- }
+ return $context;
+ }
}
diff --git a/administrator/modules/mod_quickicon/src/Helper/QuickIconHelper.php b/administrator/modules/mod_quickicon/src/Helper/QuickIconHelper.php
index e6496e281e254..b70ead7d4f402 100644
--- a/administrator/modules/mod_quickicon/src/Helper/QuickIconHelper.php
+++ b/administrator/modules/mod_quickicon/src/Helper/QuickIconHelper.php
@@ -1,4 +1,5 @@
get('context', 'mod_quickicon');
-
- if (!isset($this->buttons[$key]))
- {
- // Load mod_quickicon language file in case this method is called before rendering the module
- $application->getLanguage()->load('mod_quickicon');
-
- $this->buttons[$key] = [];
-
- if ($params->get('show_users'))
- {
- $tmp = [
- 'image' => 'icon-users',
- 'link' => Route::_('index.php?option=com_users&view=users'),
- 'linkadd' => Route::_('index.php?option=com_users&task=user.add'),
- 'name' => 'MOD_QUICKICON_USER_MANAGER',
- 'access' => array('core.manage', 'com_users', 'core.create', 'com_users'),
- 'group' => 'MOD_QUICKICON_SITE',
- ];
-
- if ($params->get('show_users') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_users&task=users.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_menuitems'))
- {
- $tmp = [
- 'image' => 'icon-list',
- 'link' => Route::_('index.php?option=com_menus&view=items&menutype='),
- 'linkadd' => Route::_('index.php?option=com_menus&task=item.add'),
- 'name' => 'MOD_QUICKICON_MENUITEMS_MANAGER',
- 'access' => array('core.manage', 'com_menus', 'core.create', 'com_menus'),
- 'group' => 'MOD_QUICKICON_STRUCTURE',
- ];
-
- if ($params->get('show_menuitems') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_menus&task=items.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_articles'))
- {
- $tmp = [
- 'image' => 'icon-file-alt',
- 'link' => Route::_('index.php?option=com_content&view=articles'),
- 'linkadd' => Route::_('index.php?option=com_content&task=article.add'),
- 'name' => 'MOD_QUICKICON_ARTICLE_MANAGER',
- 'access' => array('core.manage', 'com_content', 'core.create', 'com_content'),
- 'group' => 'MOD_QUICKICON_SITE',
- ];
-
- if ($params->get('show_articles') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_content&task=articles.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_tags'))
- {
- $tmp = [
- 'image' => 'icon-tag',
- 'link' => Route::_('index.php?option=com_tags&view=tags'),
- 'linkadd' => Route::_('index.php?option=com_tags&task=tag.edit'),
- 'name' => 'MOD_QUICKICON_TAGS_MANAGER',
- 'access' => array('core.manage', 'com_tags', 'core.create', 'com_tags'),
- 'group' => 'MOD_QUICKICON_SITE',
- ];
-
- if ($params->get('show_tags') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_tags&task=tags.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_categories'))
- {
- $tmp = [
- 'image' => 'icon-folder-open',
- 'link' => Route::_('index.php?option=com_categories&view=categories&extension=com_content'),
- 'linkadd' => Route::_('index.php?option=com_categories&task=category.add'),
- 'name' => 'MOD_QUICKICON_CATEGORY_MANAGER',
- 'access' => array('core.manage', 'com_categories', 'core.create', 'com_categories'),
- 'group' => 'MOD_QUICKICON_SITE',
- ];
-
- if ($params->get('show_categories') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_categories&task=categories.getQuickiconContent&extension=content&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_media'))
- {
- $this->buttons[$key][] = [
- 'image' => 'icon-images',
- 'link' => Route::_('index.php?option=com_media'),
- 'name' => 'MOD_QUICKICON_MEDIA_MANAGER',
- 'access' => array('core.manage', 'com_media'),
- 'group' => 'MOD_QUICKICON_SITE',
- ];
- }
-
- if ($params->get('show_modules'))
- {
- $tmp = [
- 'image' => 'icon-cube',
- 'link' => Route::_('index.php?option=com_modules&view=modules&client_id=0'),
- 'linkadd' => Route::_('index.php?option=com_modules&view=select&client_id=0'),
- 'name' => 'MOD_QUICKICON_MODULE_MANAGER',
- 'access' => array('core.manage', 'com_modules'),
- 'group' => 'MOD_QUICKICON_SITE'
- ];
-
- if ($params->get('show_modules') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_modules&task=modules.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_plugins'))
- {
- $tmp = [
- 'image' => 'icon-plug',
- 'link' => Route::_('index.php?option=com_plugins'),
- 'name' => 'MOD_QUICKICON_PLUGIN_MANAGER',
- 'access' => array('core.manage', 'com_plugins'),
- 'group' => 'MOD_QUICKICON_SITE'
- ];
-
- if ($params->get('show_plugins') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_plugins&task=plugins.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_template_styles'))
- {
- $this->buttons[$key][] = [
- 'image' => 'icon-paint-brush',
- 'link' => Route::_('index.php?option=com_templates&view=styles&client_id=0'),
- 'name' => 'MOD_QUICKICON_TEMPLATE_STYLES',
- 'access' => array('core.admin', 'com_templates'),
- 'group' => 'MOD_QUICKICON_SITE'
- ];
- }
-
- if ($params->get('show_template_code'))
- {
- $this->buttons[$key][] = [
- 'image' => 'icon-code',
- 'link' => Route::_('index.php?option=com_templates&view=templates&client_id=0'),
- 'name' => 'MOD_QUICKICON_TEMPLATE_CODE',
- 'access' => array('core.admin', 'com_templates'),
- 'group' => 'MOD_QUICKICON_SITE'
- ];
- }
-
- if ($params->get('show_checkin'))
- {
- $tmp = [
- 'image' => 'icon-unlock-alt',
- 'link' => Route::_('index.php?option=com_checkin'),
- 'name' => 'MOD_QUICKICON_CHECKINS',
- 'access' => array('core.admin', 'com_checkin'),
- 'group' => 'MOD_QUICKICON_SYSTEM'
- ];
-
- if ($params->get('show_checkin') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_checkin&task=getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_cache'))
- {
- $tmp = [
- 'image' => 'icon-cloud',
- 'link' => Route::_('index.php?option=com_cache'),
- 'name' => 'MOD_QUICKICON_CACHE',
- 'access' => array('core.admin', 'com_cache'),
- 'group' => 'MOD_QUICKICON_SYSTEM'
- ];
-
- if ($params->get('show_cache') == 2)
- {
- $tmp['ajaxurl'] = 'index.php?option=com_cache&task=display.getQuickiconContent&format=json';
- }
-
- $this->buttons[$key][] = $tmp;
- }
-
- if ($params->get('show_global'))
- {
- $this->buttons[$key][] = [
- 'image' => 'icon-cog',
- 'link' => Route::_('index.php?option=com_config'),
- 'name' => 'MOD_QUICKICON_GLOBAL_CONFIGURATION',
- 'access' => array('core.manage', 'com_config', 'core.admin', 'com_config'),
- 'group' => 'MOD_QUICKICON_SYSTEM',
- ];
- }
-
- PluginHelper::importPlugin('quickicon');
-
- $arrays = (array) $application->triggerEvent(
- 'onGetIcons',
- new QuickIconsEvent('onGetIcons', ['context' => $context])
- );
-
- foreach ($arrays as $response)
- {
- if (!\is_array($response))
- {
- continue;
- }
-
- foreach ($response as $icon)
- {
- $default = array(
- 'link' => null,
- 'image' => null,
- 'text' => null,
- 'name' => null,
- 'linkadd' => null,
- 'access' => true,
- 'class' => null,
- 'group' => 'MOD_QUICKICON',
- );
-
- $icon = array_merge($default, $icon);
-
- if (!\is_null($icon['link']) && (!\is_null($icon['text']) || !\is_null($icon['name'])))
- {
- $this->buttons[$key][] = $icon;
- }
- }
- }
- }
-
- return $this->buttons[$key];
- }
+ /**
+ * Stack to hold buttons
+ *
+ * @var array[]
+ * @since 1.6
+ */
+ protected $buttons = array();
+
+ /**
+ * Helper method to return button list.
+ *
+ * This method returns the array by reference so it can be
+ * used to add custom buttons or remove default ones.
+ *
+ * @param Registry $params The module parameters
+ * @param CMSApplication $application The application
+ *
+ * @return array An array of buttons
+ *
+ * @since 1.6
+ */
+ public function getButtons(Registry $params, CMSApplication $application = null)
+ {
+ if ($application == null) {
+ $application = Factory::getApplication();
+ }
+
+ $key = (string) $params;
+ $context = (string) $params->get('context', 'mod_quickicon');
+
+ if (!isset($this->buttons[$key])) {
+ // Load mod_quickicon language file in case this method is called before rendering the module
+ $application->getLanguage()->load('mod_quickicon');
+
+ $this->buttons[$key] = [];
+
+ if ($params->get('show_users')) {
+ $tmp = [
+ 'image' => 'icon-users',
+ 'link' => Route::_('index.php?option=com_users&view=users'),
+ 'linkadd' => Route::_('index.php?option=com_users&task=user.add'),
+ 'name' => 'MOD_QUICKICON_USER_MANAGER',
+ 'access' => array('core.manage', 'com_users', 'core.create', 'com_users'),
+ 'group' => 'MOD_QUICKICON_SITE',
+ ];
+
+ if ($params->get('show_users') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_users&task=users.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_menuitems')) {
+ $tmp = [
+ 'image' => 'icon-list',
+ 'link' => Route::_('index.php?option=com_menus&view=items&menutype='),
+ 'linkadd' => Route::_('index.php?option=com_menus&task=item.add'),
+ 'name' => 'MOD_QUICKICON_MENUITEMS_MANAGER',
+ 'access' => array('core.manage', 'com_menus', 'core.create', 'com_menus'),
+ 'group' => 'MOD_QUICKICON_STRUCTURE',
+ ];
+
+ if ($params->get('show_menuitems') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_menus&task=items.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_articles')) {
+ $tmp = [
+ 'image' => 'icon-file-alt',
+ 'link' => Route::_('index.php?option=com_content&view=articles'),
+ 'linkadd' => Route::_('index.php?option=com_content&task=article.add'),
+ 'name' => 'MOD_QUICKICON_ARTICLE_MANAGER',
+ 'access' => array('core.manage', 'com_content', 'core.create', 'com_content'),
+ 'group' => 'MOD_QUICKICON_SITE',
+ ];
+
+ if ($params->get('show_articles') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_content&task=articles.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_tags')) {
+ $tmp = [
+ 'image' => 'icon-tag',
+ 'link' => Route::_('index.php?option=com_tags&view=tags'),
+ 'linkadd' => Route::_('index.php?option=com_tags&task=tag.edit'),
+ 'name' => 'MOD_QUICKICON_TAGS_MANAGER',
+ 'access' => array('core.manage', 'com_tags', 'core.create', 'com_tags'),
+ 'group' => 'MOD_QUICKICON_SITE',
+ ];
+
+ if ($params->get('show_tags') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_tags&task=tags.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_categories')) {
+ $tmp = [
+ 'image' => 'icon-folder-open',
+ 'link' => Route::_('index.php?option=com_categories&view=categories&extension=com_content'),
+ 'linkadd' => Route::_('index.php?option=com_categories&task=category.add'),
+ 'name' => 'MOD_QUICKICON_CATEGORY_MANAGER',
+ 'access' => array('core.manage', 'com_categories', 'core.create', 'com_categories'),
+ 'group' => 'MOD_QUICKICON_SITE',
+ ];
+
+ if ($params->get('show_categories') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_categories&task=categories.getQuickiconContent&extension=content&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_media')) {
+ $this->buttons[$key][] = [
+ 'image' => 'icon-images',
+ 'link' => Route::_('index.php?option=com_media'),
+ 'name' => 'MOD_QUICKICON_MEDIA_MANAGER',
+ 'access' => array('core.manage', 'com_media'),
+ 'group' => 'MOD_QUICKICON_SITE',
+ ];
+ }
+
+ if ($params->get('show_modules')) {
+ $tmp = [
+ 'image' => 'icon-cube',
+ 'link' => Route::_('index.php?option=com_modules&view=modules&client_id=0'),
+ 'linkadd' => Route::_('index.php?option=com_modules&view=select&client_id=0'),
+ 'name' => 'MOD_QUICKICON_MODULE_MANAGER',
+ 'access' => array('core.manage', 'com_modules'),
+ 'group' => 'MOD_QUICKICON_SITE'
+ ];
+
+ if ($params->get('show_modules') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_modules&task=modules.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_plugins')) {
+ $tmp = [
+ 'image' => 'icon-plug',
+ 'link' => Route::_('index.php?option=com_plugins'),
+ 'name' => 'MOD_QUICKICON_PLUGIN_MANAGER',
+ 'access' => array('core.manage', 'com_plugins'),
+ 'group' => 'MOD_QUICKICON_SITE'
+ ];
+
+ if ($params->get('show_plugins') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_plugins&task=plugins.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_template_styles')) {
+ $this->buttons[$key][] = [
+ 'image' => 'icon-paint-brush',
+ 'link' => Route::_('index.php?option=com_templates&view=styles&client_id=0'),
+ 'name' => 'MOD_QUICKICON_TEMPLATE_STYLES',
+ 'access' => array('core.admin', 'com_templates'),
+ 'group' => 'MOD_QUICKICON_SITE'
+ ];
+ }
+
+ if ($params->get('show_template_code')) {
+ $this->buttons[$key][] = [
+ 'image' => 'icon-code',
+ 'link' => Route::_('index.php?option=com_templates&view=templates&client_id=0'),
+ 'name' => 'MOD_QUICKICON_TEMPLATE_CODE',
+ 'access' => array('core.admin', 'com_templates'),
+ 'group' => 'MOD_QUICKICON_SITE'
+ ];
+ }
+
+ if ($params->get('show_checkin')) {
+ $tmp = [
+ 'image' => 'icon-unlock-alt',
+ 'link' => Route::_('index.php?option=com_checkin'),
+ 'name' => 'MOD_QUICKICON_CHECKINS',
+ 'access' => array('core.admin', 'com_checkin'),
+ 'group' => 'MOD_QUICKICON_SYSTEM'
+ ];
+
+ if ($params->get('show_checkin') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_checkin&task=getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_cache')) {
+ $tmp = [
+ 'image' => 'icon-cloud',
+ 'link' => Route::_('index.php?option=com_cache'),
+ 'name' => 'MOD_QUICKICON_CACHE',
+ 'access' => array('core.admin', 'com_cache'),
+ 'group' => 'MOD_QUICKICON_SYSTEM'
+ ];
+
+ if ($params->get('show_cache') == 2) {
+ $tmp['ajaxurl'] = 'index.php?option=com_cache&task=display.getQuickiconContent&format=json';
+ }
+
+ $this->buttons[$key][] = $tmp;
+ }
+
+ if ($params->get('show_global')) {
+ $this->buttons[$key][] = [
+ 'image' => 'icon-cog',
+ 'link' => Route::_('index.php?option=com_config'),
+ 'name' => 'MOD_QUICKICON_GLOBAL_CONFIGURATION',
+ 'access' => array('core.manage', 'com_config', 'core.admin', 'com_config'),
+ 'group' => 'MOD_QUICKICON_SYSTEM',
+ ];
+ }
+
+ PluginHelper::importPlugin('quickicon');
+
+ $arrays = (array) $application->triggerEvent(
+ 'onGetIcons',
+ new QuickIconsEvent('onGetIcons', ['context' => $context])
+ );
+
+ foreach ($arrays as $response) {
+ if (!\is_array($response)) {
+ continue;
+ }
+
+ foreach ($response as $icon) {
+ $default = array(
+ 'link' => null,
+ 'image' => null,
+ 'text' => null,
+ 'name' => null,
+ 'linkadd' => null,
+ 'access' => true,
+ 'class' => null,
+ 'group' => 'MOD_QUICKICON',
+ );
+
+ $icon = array_merge($default, $icon);
+
+ if (!\is_null($icon['link']) && (!\is_null($icon['text']) || !\is_null($icon['name']))) {
+ $this->buttons[$key][] = $icon;
+ }
+ }
+ }
+ }
+
+ return $this->buttons[$key];
+ }
}
diff --git a/administrator/modules/mod_quickicon/tmpl/default.php b/administrator/modules/mod_quickicon/tmpl/default.php
index effa3b2e0d9a1..058db0db87e4d 100644
--- a/administrator/modules/mod_quickicon/tmpl/default.php
+++ b/administrator/modules/mod_quickicon/tmpl/default.php
@@ -1,4 +1,5 @@
-
-
-
+
+
+
diff --git a/administrator/modules/mod_sampledata/mod_sampledata.php b/administrator/modules/mod_sampledata/mod_sampledata.php
index 7feac851a7841..79ffb01326e69 100644
--- a/administrator/modules/mod_sampledata/mod_sampledata.php
+++ b/administrator/modules/mod_sampledata/mod_sampledata.php
@@ -1,4 +1,5 @@
getDispatcher()
- ->dispatch(
- 'onSampledataGetOverview',
- AbstractEvent::create(
- 'onSampledataGetOverview',
- [
- 'subject' => new \stdClass,
- ]
- )
- )
- ->getArgument('result') ?? [];
- }
+ return Factory::getApplication()
+ ->getDispatcher()
+ ->dispatch(
+ 'onSampledataGetOverview',
+ AbstractEvent::create(
+ 'onSampledataGetOverview',
+ [
+ 'subject' => new \stdClass(),
+ ]
+ )
+ )
+ ->getArgument('result') ?? [];
+ }
}
diff --git a/administrator/modules/mod_sampledata/tmpl/default.php b/administrator/modules/mod_sampledata/tmpl/default.php
index 6effa8da33976..f11599975f1b0 100644
--- a/administrator/modules/mod_sampledata/tmpl/default.php
+++ b/administrator/modules/mod_sampledata/tmpl/default.php
@@ -1,4 +1,5 @@
getDocument()->getWebAssetManager()
- ->registerAndUseScript('mod_sampledata', 'mod_sampledata/sampledata-process.js', [], ['type' => 'module'], ['core']);
+ ->registerAndUseScript('mod_sampledata', 'mod_sampledata/sampledata-process.js', [], ['type' => 'module'], ['core']);
Text::script('MOD_SAMPLEDATA_COMPLETED');
Text::script('MOD_SAMPLEDATA_CONFIRM_START');
@@ -21,37 +22,37 @@
Text::script('MOD_SAMPLEDATA_INVALID_RESPONSE');
$app->getDocument()->addScriptOptions(
- 'sample-data',
- [
- 'icon' => Uri::root(true) . '/media/system/images/ajax-loader.gif',
- ]
+ 'sample-data',
+ [
+ 'icon' => Uri::root(true) . '/media/system/images/ajax-loader.gif',
+ ]
);
?>
-
- $item) : ?>
-
-
-
-
- title, ENT_QUOTES, 'UTF-8'); ?>
-
-
-
- title; ?>
-
-
- description; ?>
-
-
-
-
-
-
-
-
-
-
+
+ $item) : ?>
+
+
+
+
+ title, ENT_QUOTES, 'UTF-8'); ?>
+
+
+
+ title; ?>
+
+
+ description; ?>
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_stats_admin/mod_stats_admin.php b/administrator/modules/mod_stats_admin/mod_stats_admin.php
index 1e23913e04cb4..49e9cb3f8c2d1 100644
--- a/administrator/modules/mod_stats_admin/mod_stats_admin.php
+++ b/administrator/modules/mod_stats_admin/mod_stats_admin.php
@@ -1,4 +1,5 @@
getIdentity();
-
- $rows = array();
- $query = $db->getQuery(true);
-
- $serverinfo = $params->get('serverinfo', 0);
- $siteinfo = $params->get('siteinfo', 0);
-
- $i = 0;
-
- if ($serverinfo)
- {
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_('MOD_STATS_PHP');
- $rows[$i]->icon = 'cogs';
- $rows[$i]->data = PHP_VERSION;
- $i++;
-
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_($db->name);
- $rows[$i]->icon = 'database';
- $rows[$i]->data = $db->getVersion();
- $i++;
-
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_('MOD_STATS_CACHING');
- $rows[$i]->icon = 'tachometer-alt';
- $rows[$i]->data = $app->get('caching') ? Text::_('JENABLED') : Text::_('JDISABLED');
- $i++;
-
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_('MOD_STATS_GZIP');
- $rows[$i]->icon = 'bolt';
- $rows[$i]->data = $app->get('gzip') ? Text::_('JENABLED') : Text::_('JDISABLED');
- $i++;
- }
-
- if ($siteinfo)
- {
- $query->select('COUNT(id) AS count_users')
- ->from('#__users');
- $db->setQuery($query);
-
- try
- {
- $users = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $users = false;
- }
-
- $query->clear()
- ->select('COUNT(id) AS count_items')
- ->from('#__content')
- ->where('state = 1');
- $db->setQuery($query);
-
- try
- {
- $items = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $items = false;
- }
-
- if ($users)
- {
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_('MOD_STATS_USERS');
- $rows[$i]->icon = 'users';
- $rows[$i]->data = $users;
-
- if ($user->authorise('core.manage', 'com_users'))
- {
- $rows[$i]->link = Route::_('index.php?option=com_users');
- }
-
- $i++;
- }
-
- if ($items)
- {
- $rows[$i] = new \stdClass;
- $rows[$i]->title = Text::_('MOD_STATS_ARTICLES');
- $rows[$i]->icon = 'file';
- $rows[$i]->data = $items;
- $rows[$i]->link = Route::_('index.php?option=com_content&view=articles&filter[published]=1');
- $i++;
- }
- }
-
- // Include additional data defined by published system plugins
- PluginHelper::importPlugin('system');
-
- $arrays = (array) $app->triggerEvent('onGetStats', array('mod_stats_admin'));
-
- foreach ($arrays as $response)
- {
- foreach ($response as $row)
- {
- // We only add a row if the title and data are given
- if (isset($row['title']) && isset($row['data']))
- {
- $rows[$i] = new \stdClass;
- $rows[$i]->title = $row['title'];
- $rows[$i]->icon = $row['icon'] ?? 'info';
- $rows[$i]->data = $row['data'];
- $rows[$i]->link = isset($row['link']) ? $row['link'] : null;
- $i++;
- }
- }
- }
-
- return $rows;
- }
+ /**
+ * Method to retrieve information about the site
+ *
+ * @param Registry $params The module parameters
+ * @param CMSApplication $app The application
+ * @param DatabaseInterface $db The database
+ *
+ * @return array Array containing site information
+ *
+ * @since 3.0
+ */
+ public static function getStats(Registry $params, CMSApplication $app, DatabaseInterface $db)
+ {
+ $user = $app->getIdentity();
+
+ $rows = array();
+ $query = $db->getQuery(true);
+
+ $serverinfo = $params->get('serverinfo', 0);
+ $siteinfo = $params->get('siteinfo', 0);
+
+ $i = 0;
+
+ if ($serverinfo) {
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_('MOD_STATS_PHP');
+ $rows[$i]->icon = 'cogs';
+ $rows[$i]->data = PHP_VERSION;
+ $i++;
+
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_($db->name);
+ $rows[$i]->icon = 'database';
+ $rows[$i]->data = $db->getVersion();
+ $i++;
+
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_('MOD_STATS_CACHING');
+ $rows[$i]->icon = 'tachometer-alt';
+ $rows[$i]->data = $app->get('caching') ? Text::_('JENABLED') : Text::_('JDISABLED');
+ $i++;
+
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_('MOD_STATS_GZIP');
+ $rows[$i]->icon = 'bolt';
+ $rows[$i]->data = $app->get('gzip') ? Text::_('JENABLED') : Text::_('JDISABLED');
+ $i++;
+ }
+
+ if ($siteinfo) {
+ $query->select('COUNT(id) AS count_users')
+ ->from('#__users');
+ $db->setQuery($query);
+
+ try {
+ $users = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $users = false;
+ }
+
+ $query->clear()
+ ->select('COUNT(id) AS count_items')
+ ->from('#__content')
+ ->where('state = 1');
+ $db->setQuery($query);
+
+ try {
+ $items = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $items = false;
+ }
+
+ if ($users) {
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_('MOD_STATS_USERS');
+ $rows[$i]->icon = 'users';
+ $rows[$i]->data = $users;
+
+ if ($user->authorise('core.manage', 'com_users')) {
+ $rows[$i]->link = Route::_('index.php?option=com_users');
+ }
+
+ $i++;
+ }
+
+ if ($items) {
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = Text::_('MOD_STATS_ARTICLES');
+ $rows[$i]->icon = 'file';
+ $rows[$i]->data = $items;
+ $rows[$i]->link = Route::_('index.php?option=com_content&view=articles&filter[published]=1');
+ $i++;
+ }
+ }
+
+ // Include additional data defined by published system plugins
+ PluginHelper::importPlugin('system');
+
+ $arrays = (array) $app->triggerEvent('onGetStats', array('mod_stats_admin'));
+
+ foreach ($arrays as $response) {
+ foreach ($response as $row) {
+ // We only add a row if the title and data are given
+ if (isset($row['title']) && isset($row['data'])) {
+ $rows[$i] = new \stdClass();
+ $rows[$i]->title = $row['title'];
+ $rows[$i]->icon = $row['icon'] ?? 'info';
+ $rows[$i]->data = $row['data'];
+ $rows[$i]->link = isset($row['link']) ? $row['link'] : null;
+ $i++;
+ }
+ }
+ }
+
+ return $rows;
+ }
}
diff --git a/administrator/modules/mod_stats_admin/tmpl/default.php b/administrator/modules/mod_stats_admin/tmpl/default.php
index 732c2fcc4f76b..c79e483e02dc9 100644
--- a/administrator/modules/mod_stats_admin/tmpl/default.php
+++ b/administrator/modules/mod_stats_admin/tmpl/default.php
@@ -1,4 +1,5 @@
-
-
- title; ?>
+
+
+ title; ?>
- link)) : ?>
- data; ?>
-
- data; ?>
-
-
-
+ link)) : ?>
+ data; ?>
+
+ data; ?>
+
+
+
diff --git a/administrator/modules/mod_submenu/mod_submenu.php b/administrator/modules/mod_submenu/mod_submenu.php
index 0fd9eb8720fd7..86cea5ee2aefa 100644
--- a/administrator/modules/mod_submenu/mod_submenu.php
+++ b/administrator/modules/mod_submenu/mod_submenu.php
@@ -1,4 +1,5 @@
get('menutype', '*');
$root = false;
-if ($menutype === '*')
-{
- $name = $params->get('preset', 'system');
- $root = MenusHelper::loadPreset($name);
-}
-else
-{
- $root = MenusHelper::getMenuItems($menutype, true);
+if ($menutype === '*') {
+ $name = $params->get('preset', 'system');
+ $root = MenusHelper::loadPreset($name);
+} else {
+ $root = MenusHelper::getMenuItems($menutype, true);
}
-if ($root && $root->hasChildren())
-{
- Factory::getLanguage()->load(
- 'mod_menu',
- JPATH_ADMINISTRATOR,
- Factory::getLanguage()->getTag(),
- true
- );
+if ($root && $root->hasChildren()) {
+ Factory::getLanguage()->load(
+ 'mod_menu',
+ JPATH_ADMINISTRATOR,
+ Factory::getLanguage()->getTag(),
+ true
+ );
- Menu::preprocess($root);
+ Menu::preprocess($root);
- // Render the module layout
- require ModuleHelper::getLayoutPath('mod_submenu', $params->get('layout', 'default'));
+ // Render the module layout
+ require ModuleHelper::getLayoutPath('mod_submenu', $params->get('layout', 'default'));
}
diff --git a/administrator/modules/mod_submenu/src/Menu/Menu.php b/administrator/modules/mod_submenu/src/Menu/Menu.php
index 61f7a50f6e09a..c02603b9a236b 100644
--- a/administrator/modules/mod_submenu/src/Menu/Menu.php
+++ b/administrator/modules/mod_submenu/src/Menu/Menu.php
@@ -1,4 +1,5 @@
getIdentity();
- $children = $parent->getChildren();
- $language = Factory::getLanguage();
-
- /**
- * Trigger onPreprocessMenuItems for the current level of backend menu items.
- * $children is an array of MenuItem objects. A plugin can traverse the whole tree,
- * but new nodes will only be run through this method if their parents have not been processed yet.
- */
- $app->triggerEvent('onPreprocessMenuItems', array('administrator.module.mod_submenu', $children));
-
- foreach ($children as $item)
- {
- if (substr($item->link, 0, 8) === 'special:')
- {
- $special = substr($item->link, 8);
-
- if ($special === 'language-forum')
- {
- $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
- }
- }
-
- $uri = new Uri($item->link);
- $query = $uri->getQuery(true);
-
- /**
- * This is needed to populate the element property when the component is no longer
- * installed but its core menu items are left behind.
- */
- if ($option = $uri->getVar('option'))
- {
- $item->element = $option;
- }
-
- // Exclude item if is not enabled
- if ($item->element && !ComponentHelper::isEnabled($item->element))
- {
- $parent->removeChild($item);
- continue;
- }
-
- /*
- * Multilingual Associations if the site is not set as multilingual and/or Associations is not enabled in
- * the Language Filter plugin
- */
-
- if ($item->element === 'com_associations' && !Associations::isEnabled())
- {
- $parent->removeChild($item);
- continue;
- }
-
- $itemParams = $item->getParams();
-
- // Exclude item with menu item option set to exclude from menu modules
- if ($itemParams->get('menu-permission'))
- {
- @list($action, $asset) = explode(';', $itemParams->get('menu-permission'));
-
- if (!$user->authorise($action, $asset))
- {
- $parent->removeChild($item);
- continue;
- }
- }
-
- // Populate automatic children for container items
- if ($item->type === 'container')
- {
- $exclude = (array) $itemParams->get('hideitems') ?: array();
- $components = MenusHelper::getMenuItems('main', false, $exclude);
-
- // We are adding the nodes first to preprocess them, then sort them and add them again.
- foreach ($components->getChildren() as $c)
- {
- if (!$c->hasChildren())
- {
- $temp = clone $c;
- $c->addChild($temp);
- }
-
- $item->addChild($c);
- }
-
- self::preprocess($item);
- $children = ArrayHelper::sortObjects($item->getChildren(), 'text', 1, false, true);
-
- foreach ($children as $c)
- {
- $parent->addChild($c);
- }
-
- $parent->removeChild($item);
- continue;
- }
-
- // Exclude Mass Mail if disabled in global configuration
- if ($item->scope === 'massmail' && ($app->get('massmailoff', 0) == 1))
- {
- $parent->removeChild($item);
- continue;
- }
-
- if ($item->element === 'com_fields')
- {
- parse_str($item->link, $query);
-
- // Only display Fields menus when enabled in the component
- $createFields = null;
-
- if (isset($query['context']))
- {
- $createFields = ComponentHelper::getParams(strstr($query['context'], '.', true))->get('custom_fields_enable', 1);
- }
-
- if (!$createFields || !$user->authorise('core.manage', 'com_users'))
- {
- $parent->removeChild($item);
- continue;
- }
- }
- elseif ($item->element === 'com_workflow')
- {
- parse_str($item->link, $query);
-
- // Only display Workflow menus when enabled in the component
- $workflow = null;
-
- if (isset($query['extension']))
- {
- $parts = explode('.', $query['extension']);
-
- $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled');
- }
-
- if (!$workflow)
- {
- $parent->removeChild($item);
- continue;
- }
-
- [$assetName] = isset($query['extension']) ? explode('.', $query['extension'], 2) : array('com_workflow');
- }
- // Special case for components which only allow super user access
- elseif (\in_array($item->element, array('com_config', 'com_privacy', 'com_actionlogs'), true) && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- elseif ($item->element === 'com_joomlaupdate' && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- elseif (($item->link === 'index.php?option=com_installer&view=install' || $item->link === 'index.php?option=com_installer&view=languages')
- && !$user->authorise('core.admin'))
- {
- continue;
- }
- elseif ($item->element === 'com_admin')
- {
- parse_str($item->link, $query);
-
- if (isset($query['view']) && $query['view'] === 'sysinfo' && !$user->authorise('core.admin'))
- {
- $parent->removeChild($item);
- continue;
- }
- }
- elseif ($item->element && !$user->authorise(($item->scope === 'edit') ? 'core.create' : 'core.manage', $item->element))
- {
- $parent->removeChild($item);
- continue;
- }
- elseif ($item->element === 'com_menus')
- {
- // Get badges for Menus containing a Home page.
- $iconImage = $item->icon;
-
- if ($iconImage)
- {
- if (substr($iconImage, 0, 6) === 'class:' && substr($iconImage, 6) === 'icon-home')
- {
- $iconImage = ' ';
- $iconImage .= '' . Text::_('JDEFAULT') . ' ';
- }
- elseif (substr($iconImage, 0, 6) === 'image:')
- {
- $iconImage = ' ' . substr($iconImage, 6) . ' ';
- }
-
- $item->iconImage = $iconImage;
- }
- }
-
- if ($item->hasChildren())
- {
- self::preprocess($item);
- }
-
- // Ok we passed everything, load language at last only
- if ($item->element)
- {
- $language->load($item->element . '.sys', JPATH_ADMINISTRATOR) ||
- $language->load($item->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->element);
- }
-
- if ($item->type === 'separator' && $item->getParams()->get('text_separator') == 0)
- {
- $item->title = '';
- }
-
- $item->text = Text::_($item->title);
- }
- }
+ /**
+ * Filter and perform other preparatory tasks for loaded menu items based on access rights and module configurations for display
+ *
+ * @param MenuItem $parent A menu item to process
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public static function preprocess($parent)
+ {
+ $app = Factory::getApplication();
+ $user = $app->getIdentity();
+ $children = $parent->getChildren();
+ $language = Factory::getLanguage();
+
+ /**
+ * Trigger onPreprocessMenuItems for the current level of backend menu items.
+ * $children is an array of MenuItem objects. A plugin can traverse the whole tree,
+ * but new nodes will only be run through this method if their parents have not been processed yet.
+ */
+ $app->triggerEvent('onPreprocessMenuItems', array('administrator.module.mod_submenu', $children));
+
+ foreach ($children as $item) {
+ if (substr($item->link, 0, 8) === 'special:') {
+ $special = substr($item->link, 8);
+
+ if ($special === 'language-forum') {
+ $item->link = 'index.php?option=com_admin&view=help&layout=langforum';
+ }
+ }
+
+ $uri = new Uri($item->link);
+ $query = $uri->getQuery(true);
+
+ /**
+ * This is needed to populate the element property when the component is no longer
+ * installed but its core menu items are left behind.
+ */
+ if ($option = $uri->getVar('option')) {
+ $item->element = $option;
+ }
+
+ // Exclude item if is not enabled
+ if ($item->element && !ComponentHelper::isEnabled($item->element)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ /*
+ * Multilingual Associations if the site is not set as multilingual and/or Associations is not enabled in
+ * the Language Filter plugin
+ */
+
+ if ($item->element === 'com_associations' && !Associations::isEnabled()) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ $itemParams = $item->getParams();
+
+ // Exclude item with menu item option set to exclude from menu modules
+ if ($itemParams->get('menu-permission')) {
+ @list($action, $asset) = explode(';', $itemParams->get('menu-permission'));
+
+ if (!$user->authorise($action, $asset)) {
+ $parent->removeChild($item);
+ continue;
+ }
+ }
+
+ // Populate automatic children for container items
+ if ($item->type === 'container') {
+ $exclude = (array) $itemParams->get('hideitems') ?: array();
+ $components = MenusHelper::getMenuItems('main', false, $exclude);
+
+ // We are adding the nodes first to preprocess them, then sort them and add them again.
+ foreach ($components->getChildren() as $c) {
+ if (!$c->hasChildren()) {
+ $temp = clone $c;
+ $c->addChild($temp);
+ }
+
+ $item->addChild($c);
+ }
+
+ self::preprocess($item);
+ $children = ArrayHelper::sortObjects($item->getChildren(), 'text', 1, false, true);
+
+ foreach ($children as $c) {
+ $parent->addChild($c);
+ }
+
+ $parent->removeChild($item);
+ continue;
+ }
+
+ // Exclude Mass Mail if disabled in global configuration
+ if ($item->scope === 'massmail' && ($app->get('massmailoff', 0) == 1)) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ if ($item->element === 'com_fields') {
+ parse_str($item->link, $query);
+
+ // Only display Fields menus when enabled in the component
+ $createFields = null;
+
+ if (isset($query['context'])) {
+ $createFields = ComponentHelper::getParams(strstr($query['context'], '.', true))->get('custom_fields_enable', 1);
+ }
+
+ if (!$createFields || !$user->authorise('core.manage', 'com_users')) {
+ $parent->removeChild($item);
+ continue;
+ }
+ } elseif ($item->element === 'com_workflow') {
+ parse_str($item->link, $query);
+
+ // Only display Workflow menus when enabled in the component
+ $workflow = null;
+
+ if (isset($query['extension'])) {
+ $parts = explode('.', $query['extension']);
+
+ $workflow = ComponentHelper::getParams($parts[0])->get('workflow_enabled');
+ }
+
+ if (!$workflow) {
+ $parent->removeChild($item);
+ continue;
+ }
+
+ [$assetName] = isset($query['extension']) ? explode('.', $query['extension'], 2) : array('com_workflow');
+ } elseif (\in_array($item->element, array('com_config', 'com_privacy', 'com_actionlogs'), true) && !$user->authorise('core.admin')) {
+ // Special case for components which only allow super user access
+ $parent->removeChild($item);
+ continue;
+ } elseif ($item->element === 'com_joomlaupdate' && !$user->authorise('core.admin')) {
+ $parent->removeChild($item);
+ continue;
+ } elseif (
+ ($item->link === 'index.php?option=com_installer&view=install' || $item->link === 'index.php?option=com_installer&view=languages')
+ && !$user->authorise('core.admin')
+ ) {
+ continue;
+ } elseif ($item->element === 'com_admin') {
+ parse_str($item->link, $query);
+
+ if (isset($query['view']) && $query['view'] === 'sysinfo' && !$user->authorise('core.admin')) {
+ $parent->removeChild($item);
+ continue;
+ }
+ } elseif ($item->element && !$user->authorise(($item->scope === 'edit') ? 'core.create' : 'core.manage', $item->element)) {
+ $parent->removeChild($item);
+ continue;
+ } elseif ($item->element === 'com_menus') {
+ // Get badges for Menus containing a Home page.
+ $iconImage = $item->icon;
+
+ if ($iconImage) {
+ if (substr($iconImage, 0, 6) === 'class:' && substr($iconImage, 6) === 'icon-home') {
+ $iconImage = ' ';
+ $iconImage .= '' . Text::_('JDEFAULT') . ' ';
+ } elseif (substr($iconImage, 0, 6) === 'image:') {
+ $iconImage = ' ' . substr($iconImage, 6) . ' ';
+ }
+
+ $item->iconImage = $iconImage;
+ }
+ }
+
+ if ($item->hasChildren()) {
+ self::preprocess($item);
+ }
+
+ // Ok we passed everything, load language at last only
+ if ($item->element) {
+ $language->load($item->element . '.sys', JPATH_ADMINISTRATOR) ||
+ $language->load($item->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->element);
+ }
+
+ if ($item->type === 'separator' && $item->getParams()->get('text_separator') == 0) {
+ $item->title = '';
+ }
+
+ $item->text = Text::_($item->title);
+ }
+ }
}
diff --git a/administrator/modules/mod_submenu/tmpl/default.php b/administrator/modules/mod_submenu/tmpl/default.php
index 1bf99857c4f58..d79089e894e6f 100644
--- a/administrator/modules/mod_submenu/tmpl/default.php
+++ b/administrator/modules/mod_submenu/tmpl/default.php
@@ -1,4 +1,5 @@
getChildren() as $child) : ?>
- hasChildren()) : ?>
-
-
- img = $child->img ?? '';
+ hasChildren()) : ?>
+
+
+ img = $child->img ?? '';
- if (substr($child->img, 0, 6) === 'class:')
- {
- $iconImage = '
';
- }
- elseif (substr($child->img, 0, 6) === 'image:')
- {
- $iconImage = '
';
- }
- elseif (!empty($child->img))
- {
- $iconImage = '
';
- }
- elseif ($child->icon)
- {
- $iconImage = '
';
- }
- else
- {
- $iconImage = '';
- }
- ?>
-
-
- getChildren() as $item) : ?>
- getParams(); ?>
-
- get('menu_show', 1)) : ?>
-
- get('menu-quicktask') ? '' : 'class="flex-grow-1"'; ?>
- href="link; ?>"
- target === '_blank' ? ' title="' . Text::sprintf('JBROWSERTARGET_NEW_TITLE', Text::_($item->title)) . '"' : ''; ?>
- target ? ' target="' . $item->target . '"' : ''; ?>>
- get('menu_image'))) : ?>
- get('menu_image'), ENT_QUOTES, 'UTF-8');
- $class = htmlspecialchars($params->get('menu_image_css'), ENT_QUOTES, 'UTF-8');
- $alt = $params->get('menu_text') ? '' : htmlspecialchars(Text::_($item->title), ENT_QUOTES, 'UTF-8');
- ?>
-
-
- get('menu_text', 1)) ? htmlspecialchars(Text::_($item->title), ENT_QUOTES, 'UTF-8') : ''; ?>
- ajaxbadge) : ?>
-
-
-
- iconImage; ?>
- get('menu-quicktask')) : ?>
- get('menu-quicktask-permission'); ?>
- scope !== 'default' ? $item->scope : null; ?>
- authorise($permission, $scope)) : ?>
-
-
-
+ $sronly = Text::_($item->title) . ' - ' . $title;
+ ?>
+
+
+
+
+
+
+
+ dashboard) : ?>
+
+
+
+
+
+
+
+
+
diff --git a/administrator/modules/mod_title/mod_title.php b/administrator/modules/mod_title/mod_title.php
index edcbc9bfc2b77..3e1340ffba5f8 100644
--- a/administrator/modules/mod_title/mod_title.php
+++ b/administrator/modules/mod_title/mod_title.php
@@ -1,4 +1,5 @@
JComponentTitle))
-{
- $title = $app->JComponentTitle;
+if (isset($app->JComponentTitle)) {
+ $title = $app->JComponentTitle;
}
require ModuleHelper::getLayoutPath('mod_title', $params->get('layout', 'default'));
diff --git a/administrator/modules/mod_title/tmpl/default.php b/administrator/modules/mod_title/tmpl/default.php
index 7db1059559420..0c24a082b2a65 100644
--- a/administrator/modules/mod_title/tmpl/default.php
+++ b/administrator/modules/mod_title/tmpl/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/modules/mod_toolbar/mod_toolbar.php b/administrator/modules/mod_toolbar/mod_toolbar.php
index 9b7fabde069a3..9466a9efd76b3 100644
--- a/administrator/modules/mod_toolbar/mod_toolbar.php
+++ b/administrator/modules/mod_toolbar/mod_toolbar.php
@@ -1,4 +1,5 @@
input->getBool('hidemainmenu');
-if ($hideLinks)
-{
- return;
+if ($hideLinks) {
+ return;
}
// Load the Bootstrap Dropdown
HTMLHelper::_('bootstrap.dropdown', '.dropdown-toggle');
?>
diff --git a/administrator/modules/mod_version/mod_version.php b/administrator/modules/mod_version/mod_version.php
index 5ce96037c52a8..c94b138b69e15 100644
--- a/administrator/modules/mod_version/mod_version.php
+++ b/administrator/modules/mod_version/mod_version.php
@@ -1,4 +1,5 @@
getShortVersion();
- }
+ return '' . $version->getShortVersion();
+ }
}
diff --git a/administrator/modules/mod_version/tmpl/default.php b/administrator/modules/mod_version/tmpl/default.php
index 5e77f801b56f0..fa0f143a44841 100644
--- a/administrator/modules/mod_version/tmpl/default.php
+++ b/administrator/modules/mod_version/tmpl/default.php
@@ -1,4 +1,5 @@
diff --git a/administrator/templates/atum/component.php b/administrator/templates/atum/component.php
index 2913982b05b50..0d52a6bed1064 100644
--- a/administrator/templates/atum/component.php
+++ b/administrator/templates/atum/component.php
@@ -1,4 +1,5 @@
usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
- ->useStyle('template.active.language')
- ->useStyle('template.user')
- ->addInlineStyle(':root {
+ ->useStyle('template.active.language')
+ ->useStyle('template.user')
+ ->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $this->params->get('bg-light', '--template-bg-light') . ';
--template-text-dark: ' . $this->params->get('text-dark', '--template-text-dark') . ';
@@ -47,12 +48,12 @@
-
-
-
+
+
+
-
-
+
+
diff --git a/administrator/templates/atum/cpanel.php b/administrator/templates/atum/cpanel.php
index e75d1c51179dd..8a2b1aafdd2c8 100644
--- a/administrator/templates/atum/cpanel.php
+++ b/administrator/templates/atum/cpanel.php
@@ -1,4 +1,5 @@
guest)
-{
- require __DIR__ . '/error_login.php';
-}
-else
-{
- require __DIR__ . '/error_full.php';
+if ($user->guest) {
+ require __DIR__ . '/error_login.php';
+} else {
+ require __DIR__ . '/error_full.php';
}
diff --git a/administrator/templates/atum/error_full.php b/administrator/templates/atum/error_full.php
index 6886a175da8ff..0014d08860938 100644
--- a/administrator/templates/atum/error_full.php
+++ b/administrator/templates/atum/error_full.php
@@ -1,4 +1,5 @@
params->get('logoBrandLarge')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
$logoBrandSmall = $this->params->get('logoBrandSmall')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
$logoBrandLargeAlt = empty($this->params->get('logoBrandLargeAlt')) && empty($this->params->get('emptyLogoBrandLargeAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
$logoBrandSmallAlt = empty($this->params->get('logoBrandSmallAlt')) && empty($this->params->get('emptyLogoBrandSmallAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
- // Get the hue value
- preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $this->params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
+ // Get the hue value
+ preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $this->params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
- // Enable assets
- $wa->usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
- ->useStyle('template.active.language')
- ->useStyle('template.user')
- ->addInlineStyle(':root {
+ // Enable assets
+ $wa->usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
+ ->useStyle('template.active.language')
+ ->useStyle('template.user')
+ ->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $this->params->get('bg-light', '#f0f4fb') . ';
--template-text-dark: ' . $this->params->get('text-dark', '#495057') . ';
@@ -66,122 +67,122 @@
}');
// Override 'template.active' asset to set correct ltr/rtl dependency
-$wa->registerStyle('template.active', '', [], [], ['template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
+ $wa->registerStyle('template.active', '', [], [], ['template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr')]);
// Set some meta data
-$this->setMetaData('viewport', 'width=device-width, initial-scale=1');
+ $this->setMetaData('viewport', 'width=device-width, initial-scale=1');
-$monochrome = (bool) $this->params->get('monochrome');
+ $monochrome = (bool) $this->params->get('monochrome');
// @see administrator/templates/atum/html/layouts/status.php
-$statusModules = LayoutHelper::render('status', ['modules' => 'status']);
-?>
+ $statusModules = LayoutHelper::render('status', ['modules' => 'status']);
+ ?>
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/templates/atum/error_login.php b/administrator/templates/atum/error_login.php
index 68144ad07aebc..fe6dc0f048681 100644
--- a/administrator/templates/atum/error_login.php
+++ b/administrator/templates/atum/error_login.php
@@ -1,4 +1,5 @@
params->get('logoBrandLarge')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
$loginLogo = $this->params->get('loginLogo')
- ? Uri::root() . $this->params->get('loginLogo')
- : Uri::root() . 'media/templates/administrator/atum/images/logos/login.svg';
+ ? Uri::root() . $this->params->get('loginLogo')
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/login.svg';
$logoBrandSmall = $this->params->get('logoBrandSmall')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
$logoBrandLargeAlt = empty($this->params->get('logoBrandLargeAlt')) && empty($this->params->get('emptyLogoBrandLargeAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
$logoBrandSmallAlt = empty($this->params->get('logoBrandSmallAlt')) && empty($this->params->get('emptyLogoBrandSmallAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
$loginLogoAlt = empty($this->params->get('loginLogoAlt')) && empty($this->params->get('emptyLoginLogoAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('loginLogoAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('loginLogoAlt'), ENT_COMPAT, 'UTF-8') . '"';
- // Get the hue value
+ // Get the hue value
preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $this->params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
// Enable assets
$wa->usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
- ->useStyle('template.active.language')
- ->useStyle('template.user')
- ->addInlineStyle(':root {
+ ->useStyle('template.active.language')
+ ->useStyle('template.user')
+ ->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $this->params->get('bg-light', '#f0f4fb') . ';
--template-text-dark: ' . $this->params->get('text-dark', '#495057') . ';
@@ -83,83 +84,83 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
>
-
-
-
-
- error->getCode(); ?>
- error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
-
- debug) : ?>
-
- renderBacktrace(); ?>
-
- error->getPrevious()) : ?>
-
- _error here and in the loop as setError() assigns errors to this property and we need this for the backtrace to work correctly ?>
-
- setError($this->_error->getPrevious()); ?>
-
-
-
_error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
- renderBacktrace(); ?>
- setError($this->_error->getPrevious()); ?>
-
-
- setError($this->error); ?>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
+
+
+
+
+ error->getCode(); ?>
+ error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
+
+ debug) : ?>
+
+ renderBacktrace(); ?>
+
+ error->getPrevious()) : ?>
+
+ _error here and in the loop as setError() assigns errors to this property and we need this for the backtrace to work correctly ?>
+
+ setError($this->_error->getPrevious()); ?>
+
+
+
_error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
+ renderBacktrace(); ?>
+ setError($this->_error->getPrevious()); ?>
+
+
+ setError($this->error); ?>
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/templates/atum/html/layouts/chromes/body.php b/administrator/templates/atum/html/layouts/chromes/body.php
index b73e693fe638b..ab4c8f317e2d9 100644
--- a/administrator/templates/atum/html/layouts/chromes/body.php
+++ b/administrator/templates/atum/html/layouts/chromes/body.php
@@ -1,4 +1,5 @@
content === '')
-{
- return;
+if ((string) $module->content === '') {
+ return;
}
$id = $module->id;
@@ -41,31 +41,31 @@
?>
- < class="card pt-3">
-
- isRtl() ? 'start' : 'end'; ?>
-
-
-
-
-
- showtitle) : ?>
- < class="card-header">title; ?>>
-
-
- content; ?>
-
- >
+ < class="card pt-3">
+
+ isRtl() ? 'start' : 'end'; ?>
+
+
+
+
+
+ showtitle) : ?>
+ < class="card-header">title; ?>>
+
+
+ content; ?>
+
+ >
diff --git a/administrator/templates/atum/html/layouts/chromes/header-item.php b/administrator/templates/atum/html/layouts/chromes/header-item.php
index 7e6d474557f08..6beaeadff20a3 100644
--- a/administrator/templates/atum/html/layouts/chromes/header-item.php
+++ b/administrator/templates/atum/html/layouts/chromes/header-item.php
@@ -1,4 +1,5 @@
content === '')
-{
- return;
+if ((string) $module->content === '') {
+ return;
}
?>
diff --git a/administrator/templates/atum/html/layouts/chromes/title.php b/administrator/templates/atum/html/layouts/chromes/title.php
index c4979a36c2152..23278b578d33e 100644
--- a/administrator/templates/atum/html/layouts/chromes/title.php
+++ b/administrator/templates/atum/html/layouts/chromes/title.php
@@ -1,4 +1,5 @@
content === '')
-{
- return;
+if ((string) $module->content === '') {
+ return;
}
?>
content; ?>
diff --git a/administrator/templates/atum/html/layouts/chromes/well.php b/administrator/templates/atum/html/layouts/chromes/well.php
index 76d5bac523e9b..be61e19d8f5e0 100644
--- a/administrator/templates/atum/html/layouts/chromes/well.php
+++ b/administrator/templates/atum/html/layouts/chromes/well.php
@@ -1,4 +1,5 @@
content === '')
-{
- return;
+if ((string) $module->content === '') {
+ return;
}
$id = $module->id;
@@ -44,39 +44,39 @@
?>
- < class="card mb-3 ">
- showtitle) : ?>
-
+
+
+ content; ?>
+
+ >
diff --git a/administrator/templates/atum/html/layouts/status.php b/administrator/templates/atum/html/layouts/status.php
index 41a28e953104a..364eec92381a4 100644
--- a/administrator/templates/atum/html/layouts/status.php
+++ b/administrator/templates/atum/html/layouts/status.php
@@ -1,4 +1,5 @@
$mod)
-{
- $out = $renderer->render($mod);
+foreach ($modules as $key => $mod) {
+ $out = $renderer->render($mod);
- if ($out !== '')
- {
- if (strpos($out, 'data-bs-toggle="modal"') !== false)
- {
- $dom = new \DOMDocument;
- $dom->loadHTML($out);
- $els = $dom->getElementsByTagName('a');
+ if ($out !== '') {
+ if (strpos($out, 'data-bs-toggle="modal"') !== false) {
+ $dom = new \DOMDocument();
+ $dom->loadHTML($out);
+ $els = $dom->getElementsByTagName('a');
- $moduleCollapsedHtml[] = $dom->saveHTML($els[0]); //$els[0]->nodeValue;
- }
- else
- {
- $moduleCollapsedHtml[] = $out;
- }
+ $moduleCollapsedHtml[] = $dom->saveHTML($els[0]); //$els[0]->nodeValue;
+ } else {
+ $moduleCollapsedHtml[] = $out;
+ }
- $moduleHtml[] = $out;
- }
+ $moduleHtml[] = $out;
+ }
}
?>
';
- }
- ?>
-
+ ' . $mod . '';
+ }
+ ?>
+
diff --git a/administrator/templates/atum/index.php b/administrator/templates/atum/index.php
index 734e0ca9e48f6..419590c1d88ab 100644
--- a/administrator/templates/atum/index.php
+++ b/administrator/templates/atum/index.php
@@ -1,4 +1,5 @@
params->get('logoBrandLarge')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
$logoBrandSmall = $this->params->get('logoBrandSmall')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
$logoBrandLargeAlt = empty($this->params->get('logoBrandLargeAlt')) && empty($this->params->get('emptyLogoBrandLargeAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
$logoBrandSmallAlt = empty($this->params->get('logoBrandSmallAlt')) && empty($this->params->get('emptyLogoBrandSmallAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
// Get the hue value
preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $this->params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
// Enable assets
$wa->usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
- ->useStyle('template.active.language')
- ->useStyle('template.user')
- ->addInlineStyle(':root {
+ ->useStyle('template.active.language')
+ ->useStyle('template.user')
+ ->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $this->params->get('bg-light', '#f0f4fb') . ';
--template-text-dark: ' . $this->params->get('text-dark', '#495057') . ';
@@ -89,99 +90,99 @@
>
-
-
-
+
+
+
-
-
-
+
+
+
diff --git a/administrator/templates/atum/login.php b/administrator/templates/atum/login.php
index 98c3bb7691b5e..43130e5e7b39f 100644
--- a/administrator/templates/atum/login.php
+++ b/administrator/templates/atum/login.php
@@ -1,4 +1,5 @@
params->get('logoBrandLarge')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandLarge'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-large.svg';
$loginLogo = $this->params->get('loginLogo')
- ? Uri::root() . $this->params->get('loginLogo')
- : Uri::root() . 'media/templates/administrator/atum/images/logos/login.svg';
+ ? Uri::root() . $this->params->get('loginLogo')
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/login.svg';
$logoBrandSmall = $this->params->get('logoBrandSmall')
- ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
- : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
+ ? Uri::root() . htmlspecialchars($this->params->get('logoBrandSmall'), ENT_QUOTES)
+ : Uri::root() . 'media/templates/administrator/atum/images/logos/brand-small.svg';
$logoBrandLargeAlt = empty($this->params->get('logoBrandLargeAlt')) && empty($this->params->get('emptyLogoBrandLargeAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandLargeAlt'), ENT_COMPAT, 'UTF-8') . '"';
$logoBrandSmallAlt = empty($this->params->get('logoBrandSmallAlt')) && empty($this->params->get('emptyLogoBrandSmallAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('logoBrandSmallAlt'), ENT_COMPAT, 'UTF-8') . '"';
$loginLogoAlt = empty($this->params->get('loginLogoAlt')) && empty($this->params->get('emptyLoginLogoAlt'))
- ? 'alt=""'
- : 'alt="' . htmlspecialchars($this->params->get('loginLogoAlt'), ENT_COMPAT, 'UTF-8') . '"';
+ ? 'alt=""'
+ : 'alt="' . htmlspecialchars($this->params->get('loginLogoAlt'), ENT_COMPAT, 'UTF-8') . '"';
// Get the hue value
preg_match('#^hsla?\(([0-9]+)[\D]+([0-9]+)[\D]+([0-9]+)[\D]+([0-9](?:.\d+)?)?\)$#i', $this->params->get('hue', 'hsl(214, 63%, 20%)'), $matches);
// Enable assets
$wa->usePreset('template.atum.' . ($this->direction === 'rtl' ? 'rtl' : 'ltr'))
- ->useStyle('template.active.language')
- ->useStyle('template.user')
- ->addInlineStyle(':root {
+ ->useStyle('template.active.language')
+ ->useStyle('template.user')
+ ->addInlineStyle(':root {
--hue: ' . $matches[1] . ';
--template-bg-light: ' . $this->params->get('bg-light', '#f0f4fb') . ';
--template-text-dark: ' . $this->params->get('text-dark', '#495057') . ';
@@ -89,62 +90,62 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
>
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
>
+
+
+
+
+
+
+
+
+
+
diff --git a/administrator/templates/system/component.php b/administrator/templates/system/component.php
index dd283dae3c972..672f4e6fb6648 100644
--- a/administrator/templates/system/component.php
+++ b/administrator/templates/system/component.php
@@ -1,4 +1,5 @@
-
+
-
-
+
+
diff --git a/administrator/templates/system/error.php b/administrator/templates/system/error.php
index 53c7a91e622a1..ea06bd4e7dc0c 100644
--- a/administrator/templates/system/error.php
+++ b/administrator/templates/system/error.php
@@ -1,4 +1,5 @@
-
-
-
+
+
+
-
-
-
- error->getCode() ?> -
-
-
-
-
-
- error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
- debug) : ?>
- error->getFile(), ENT_QUOTES, 'UTF-8');?>:error->getLine(); ?>
-
-
-
- debug) : ?>
-
- renderBacktrace(); ?>
-
- error->getPrevious()) : ?>
-
- _error here and in the loop as setError() assigns errors to this property and we need this for the backtrace to work correctly ?>
-
- setError($this->_error->getPrevious()); ?>
-
-
-
- _error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
- _error->getFile(), ENT_QUOTES, 'UTF-8');?>:_error->getLine(); ?>
-
- renderBacktrace(); ?>
- setError($this->_error->getPrevious()); ?>
-
-
- setError($this->error); ?>
-
-
-
-
-
-
+
+
+
+ error->getCode() ?> -
+
+
+
+
+
+ error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
+ debug) : ?>
+ error->getFile(), ENT_QUOTES, 'UTF-8');?>:error->getLine(); ?>
+
+
+
+ debug) : ?>
+
+ renderBacktrace(); ?>
+
+ error->getPrevious()) : ?>
+
+ _error here and in the loop as setError() assigns errors to this property and we need this for the backtrace to work correctly ?>
+
+ setError($this->_error->getPrevious()); ?>
+
+
+
+ _error->getMessage(), ENT_QUOTES, 'UTF-8'); ?>
+ _error->getFile(), ENT_QUOTES, 'UTF-8');?>:_error->getLine(); ?>
+
+ renderBacktrace(); ?>
+ setError($this->_error->getPrevious()); ?>
+
+
+ setError($this->error); ?>
+
+
+
+
+
+
-
+
diff --git a/administrator/templates/system/index.php b/administrator/templates/system/index.php
index 14ed2b2e575c2..c0d47db820eef 100644
--- a/administrator/templates/system/index.php
+++ b/administrator/templates/system/index.php
@@ -1,4 +1,5 @@
getExtensionFromInput();
- $data['extension'] = $extension;
-
- // TODO: This is a hack to drop the extension into the global input object - to satisfy how state is built
- // we should be able to improve this in the future
- $this->input->set('extension', $extension);
-
- return $data;
- }
-
- /**
- * Method to save a record.
- *
- * @param integer $recordKey The primary key of the item (if exists)
- *
- * @return integer The record ID on success, false on failure
- *
- * @since 4.0.6
- */
- protected function save($recordKey = null)
- {
- $recordId = parent::save($recordKey);
-
- if (!$recordId)
- {
- return $recordId;
- }
-
- $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
-
- if (empty($data['location']))
- {
- return $recordId;
- }
-
- /** @var Category $category */
- $category = $this->getModel('Category')->getTable('Category');
- $category->load((int) $recordId);
-
- $reference = $category->parent_id;
-
- if (!empty($data['location_reference']))
- {
- $reference = (int) $data['location_reference'];
- }
-
- $category->setLocation($reference, $data['location']);
- $category->store();
-
- return $recordId;
- }
-
- /**
- * Basic display of an item view
- *
- * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayItem($id = null)
- {
- $this->modelState->set('filter.extension', $this->getExtensionFromInput());
-
- return parent::displayItem($id);
- }
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.extension', $this->getExtensionFromInput());
-
- return parent::displayList();
- }
-
- /**
- * Get extension from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getExtensionFromInput()
- {
- return $this->input->exists('extension') ?
- $this->input->get('extension') : $this->input->post->get('extension');
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'categories';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'categories';
+
+ /**
+ * Method to allow extended classes to manipulate the data to be saved for an extension.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessSaveData(array $data): array
+ {
+ $extension = $this->getExtensionFromInput();
+ $data['extension'] = $extension;
+
+ // TODO: This is a hack to drop the extension into the global input object - to satisfy how state is built
+ // we should be able to improve this in the future
+ $this->input->set('extension', $extension);
+
+ return $data;
+ }
+
+ /**
+ * Method to save a record.
+ *
+ * @param integer $recordKey The primary key of the item (if exists)
+ *
+ * @return integer The record ID on success, false on failure
+ *
+ * @since 4.0.6
+ */
+ protected function save($recordKey = null)
+ {
+ $recordId = parent::save($recordKey);
+
+ if (!$recordId) {
+ return $recordId;
+ }
+
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+
+ if (empty($data['location'])) {
+ return $recordId;
+ }
+
+ /** @var Category $category */
+ $category = $this->getModel('Category')->getTable('Category');
+ $category->load((int) $recordId);
+
+ $reference = $category->parent_id;
+
+ if (!empty($data['location_reference'])) {
+ $reference = (int) $data['location_reference'];
+ }
+
+ $category->setLocation($reference, $data['location']);
+ $category->store();
+
+ return $recordId;
+ }
+
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.extension', $this->getExtensionFromInput());
+
+ return parent::displayItem($id);
+ }
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.extension', $this->getExtensionFromInput());
+
+ return parent::displayList();
+ }
+
+ /**
+ * Get extension from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getExtensionFromInput()
+ {
+ return $this->input->exists('extension') ?
+ $this->input->get('extension') : $this->input->post->get('extension');
+ }
}
diff --git a/api/components/com_categories/src/View/Categories/JsonapiView.php b/api/components/com_categories/src/View/Categories/JsonapiView.php
index c2d244c67fb99..2302646a0d89f 100644
--- a/api/components/com_categories/src/View/Categories/JsonapiView.php
+++ b/api/components/com_categories/src/View/Categories/JsonapiView.php
@@ -1,4 +1,5 @@
fieldsToRenderList[] = $field->name;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ foreach (FieldsHelper::getFields('com_content.categories') as $field) {
+ $this->fieldsToRenderList[] = $field->name;
+ }
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Execute and display a template script.
- *
- * @param object $item Item
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayItem($item = null)
- {
- foreach (FieldsHelper::getFields('com_content.categories') as $field)
- {
- $this->fieldsToRenderItem[] = $field->name;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ foreach (FieldsHelper::getFields('com_content.categories') as $field) {
+ $this->fieldsToRenderItem[] = $field->name;
+ }
- if ($item === null)
- {
- /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
- $model = $this->getModel();
- $item = $this->prepareItem($model->getItem());
- }
+ if ($item === null) {
+ /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
+ $model = $this->getModel();
+ $item = $this->prepareItem($model->getItem());
+ }
- if ($item->id === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->id === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- if ($item->extension != $this->getModel()->getState('filter.extension'))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->extension != $this->getModel()->getState('filter.extension')) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- return parent::displayItem($item);
- }
+ return parent::displayItem($item);
+ }
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- foreach (FieldsHelper::getFields('com_content.categories', $item, true) as $field)
- {
- $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
- }
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ foreach (FieldsHelper::getFields('com_content.categories', $item, true) as $field) {
+ $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
+ }
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_config/src/Controller/ApplicationController.php b/api/components/com_config/src/Controller/ApplicationController.php
index c273f891f0323..17bca79911be8 100644
--- a/api/components/com_config/src/Controller/ApplicationController.php
+++ b/api/components/com_config/src/Controller/ApplicationController.php
@@ -1,4 +1,5 @@
app->getDocument()->getType();
- $viewLayout = $this->input->get('layout', 'default', 'string');
-
- try
- {
- /** @var JsonapiView $view */
- $view = $this->getView(
- $this->default_view,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
-
- /** @var ApplicationModel $model */
- $model = $this->getModel($this->contentType);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
- }
-
- // Push the model into the view (as default)
- $view->setModel($model, true);
-
- $view->document = $this->app->getDocument();
- $view->displayList();
-
- return $this;
- }
-
- /**
- * Method to edit an existing record.
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function edit()
- {
- /** @var ApplicationModel $model */
- $model = $this->getModel($this->contentType);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
- }
-
- // Access check.
- if (!$this->allowEdit())
- {
- throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
- }
-
- $data = json_decode($this->input->json->getRaw(), true);
-
- // Complete data array if needed
- $oldData = $model->getData();
- $data = array_replace($oldData, $data);
-
- // @todo: Not the cleanest thing ever but it works...
- Form::addFormPath(JPATH_COMPONENT_ADMINISTRATOR . '/forms');
-
- // Must load after serving service-requests
- $form = $model->getForm();
-
- // Validate the posted data.
- $validData = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($validData === false)
- {
- $errors = $model->getErrors();
- $messages = [];
-
- for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $messages[] = "{$errors[$i]->getMessage()}";
- }
- else
- {
- $messages[] = "{$errors[$i]}";
- }
- }
-
- throw new InvalidParameterException(implode("\n", $messages));
- }
-
- if (!$model->save($validData))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500);
- }
-
- return $this;
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'application';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'application';
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $viewType = $this->app->getDocument()->getType();
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+
+ try {
+ /** @var JsonapiView $view */
+ $view = $this->getView(
+ $this->default_view,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ /** @var ApplicationModel $model */
+ $model = $this->getModel($this->contentType);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
+ }
+
+ // Push the model into the view (as default)
+ $view->setModel($model, true);
+
+ $view->document = $this->app->getDocument();
+ $view->displayList();
+
+ return $this;
+ }
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function edit()
+ {
+ /** @var ApplicationModel $model */
+ $model = $this->getModel($this->contentType);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
+ }
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
+ }
+
+ $data = json_decode($this->input->json->getRaw(), true);
+
+ // Complete data array if needed
+ $oldData = $model->getData();
+ $data = array_replace($oldData, $data);
+
+ // @todo: Not the cleanest thing ever but it works...
+ Form::addFormPath(JPATH_COMPONENT_ADMINISTRATOR . '/forms');
+
+ // Must load after serving service-requests
+ $form = $model->getForm();
+
+ // Validate the posted data.
+ $validData = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($validData === false) {
+ $errors = $model->getErrors();
+ $messages = [];
+
+ for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $messages[] = "{$errors[$i]->getMessage()}";
+ } else {
+ $messages[] = "{$errors[$i]}";
+ }
+ }
+
+ throw new InvalidParameterException(implode("\n", $messages));
+ }
+
+ if (!$model->save($validData)) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500);
+ }
+
+ return $this;
+ }
}
diff --git a/api/components/com_config/src/Controller/ComponentController.php b/api/components/com_config/src/Controller/ComponentController.php
index ef60de1d7754d..6188ae84596af 100644
--- a/api/components/com_config/src/Controller/ComponentController.php
+++ b/api/components/com_config/src/Controller/ComponentController.php
@@ -1,4 +1,5 @@
app->getDocument()->getType();
- $viewLayout = $this->input->get('layout', 'default', 'string');
-
- try
- {
- /** @var JsonapiView $view */
- $view = $this->getView(
- $this->default_view,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
-
- /** @var ComponentModel $model */
- $model = $this->getModel($this->contentType);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
- }
-
- // Push the model into the view (as default)
- $view->setModel($model, true);
- $view->set('component_name', $this->input->get('component_name'));
-
- $view->document = $this->app->getDocument();
- $view->displayList();
-
- return $this;
- }
-
- /**
- * Method to edit an existing record.
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function edit()
- {
- /** @var ComponentModel $model */
- $model = $this->getModel($this->contentType);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
- }
-
- // Access check.
- if (!$this->allowEdit())
- {
- throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
- }
-
- $option = $this->input->get('component_name');
-
- // @todo: Not the cleanest thing ever but it works...
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/' . $option);
-
- // Must load after serving service-requests
- $form = $model->getForm();
-
- $data = json_decode($this->input->json->getRaw(), true);
-
- $component = ComponentHelper::getComponent($option);
- $oldData = $component->getParams()->toArray();
- $data = array_replace($oldData, $data);
-
- // Validate the posted data.
- $validData = $model->validate($form, $data);
-
- if ($validData === false)
- {
- $errors = $model->getErrors();
- $messages = [];
-
- for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $messages[] = "{$errors[$i]->getMessage()}";
- }
- else
- {
- $messages[] = "{$errors[$i]}";
- }
- }
-
- throw new InvalidParameterException(implode("\n", $messages));
- }
-
- // Attempt to save the configuration.
- $data = [
- 'params' => $validData,
- 'id' => ExtensionHelper::getExtensionRecord($option, 'component')->extension_id,
- 'option' => $option,
- ];
-
- if (!$model->save($data))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500);
- }
-
- return $this;
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'component';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'component';
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $viewType = $this->app->getDocument()->getType();
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+
+ try {
+ /** @var JsonapiView $view */
+ $view = $this->getView(
+ $this->default_view,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ /** @var ComponentModel $model */
+ $model = $this->getModel($this->contentType);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
+ }
+
+ // Push the model into the view (as default)
+ $view->setModel($model, true);
+ $view->set('component_name', $this->input->get('component_name'));
+
+ $view->document = $this->app->getDocument();
+ $view->displayList();
+
+ return $this;
+ }
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function edit()
+ {
+ /** @var ComponentModel $model */
+ $model = $this->getModel($this->contentType);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'), 500);
+ }
+
+ // Access check.
+ if (!$this->allowEdit()) {
+ throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
+ }
+
+ $option = $this->input->get('component_name');
+
+ // @todo: Not the cleanest thing ever but it works...
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/' . $option);
+
+ // Must load after serving service-requests
+ $form = $model->getForm();
+
+ $data = json_decode($this->input->json->getRaw(), true);
+
+ $component = ComponentHelper::getComponent($option);
+ $oldData = $component->getParams()->toArray();
+ $data = array_replace($oldData, $data);
+
+ // Validate the posted data.
+ $validData = $model->validate($form, $data);
+
+ if ($validData === false) {
+ $errors = $model->getErrors();
+ $messages = [];
+
+ for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $messages[] = "{$errors[$i]->getMessage()}";
+ } else {
+ $messages[] = "{$errors[$i]}";
+ }
+ }
+
+ throw new InvalidParameterException(implode("\n", $messages));
+ }
+
+ // Attempt to save the configuration.
+ $data = [
+ 'params' => $validData,
+ 'id' => ExtensionHelper::getExtensionRecord($option, 'component')->extension_id,
+ 'option' => $option,
+ ];
+
+ if (!$model->save($data)) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500);
+ }
+
+ return $this;
+ }
}
diff --git a/api/components/com_config/src/View/Application/JsonapiView.php b/api/components/com_config/src/View/Application/JsonapiView.php
index 090b2ddb1404d..0aec96f4716d0 100644
--- a/api/components/com_config/src/View/Application/JsonapiView.php
+++ b/api/components/com_config/src/View/Application/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $items = [];
-
- foreach ($model->getData() as $key => $value)
- {
- $item = (object) [$key => $value];
- $items[] = $this->prepareItem($item);
- }
-
- // Set up links for pagination
- $currentUrl = Uri::getInstance();
- $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
- $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
-
- $offset = $currentPageQuery['offset'];
- $limit = $currentPageQuery['limit'];
- $totalItemsCount = \count($items);
- $totalPagesAvailable = ceil($totalItemsCount / $limit);
-
- $items = array_splice($items, $offset, $limit);
-
- $this->document->addMeta('total-pages', $totalPagesAvailable)
- ->addLink('self', (string) $currentUrl);
-
- // Check for first and previous pages
- if ($offset > 0)
- {
- $firstPage = clone $currentUrl;
- $firstPageQuery = $currentPageQuery;
- $firstPageQuery['offset'] = 0;
- $firstPage->setVar('page', $firstPageQuery);
-
- $previousPage = clone $currentUrl;
- $previousPageQuery = $currentPageQuery;
- $previousOffset = $currentPageQuery['offset'] - $limit;
- $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
- $previousPage->setVar('page', $previousPageQuery);
-
- $this->document->addLink('first', $this->queryEncode((string) $firstPage))
- ->addLink('previous', $this->queryEncode((string) $previousPage));
- }
-
- // Check for next and last pages
- if ($offset + $limit < $totalItemsCount)
- {
- $nextPage = clone $currentUrl;
- $nextPageQuery = $currentPageQuery;
- $nextOffset = $currentPageQuery['offset'] + $limit;
- $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
- $nextPage->setVar('page', $nextPageQuery);
-
- $lastPage = clone $currentUrl;
- $lastPageQuery = $currentPageQuery;
- $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit;
- $lastPage->setVar('page', $lastPageQuery);
-
- $this->document->addLink('next', $this->queryEncode((string) $nextPage))
- ->addLink('last', $this->queryEncode((string) $lastPage));
- }
-
- $collection = (new Collection($items, new JoomlaSerializer($this->type)));
-
- // Set the data into the document and render it
- $this->document->setData($collection);
-
- return $this->document->render();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- $item->id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
-
- return $item;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ /** @var ApplicationModel $model */
+ $model = $this->getModel();
+ $items = [];
+
+ foreach ($model->getData() as $key => $value) {
+ $item = (object) [$key => $value];
+ $items[] = $this->prepareItem($item);
+ }
+
+ // Set up links for pagination
+ $currentUrl = Uri::getInstance();
+ $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
+ $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
+
+ $offset = $currentPageQuery['offset'];
+ $limit = $currentPageQuery['limit'];
+ $totalItemsCount = \count($items);
+ $totalPagesAvailable = ceil($totalItemsCount / $limit);
+
+ $items = array_splice($items, $offset, $limit);
+
+ $this->document->addMeta('total-pages', $totalPagesAvailable)
+ ->addLink('self', (string) $currentUrl);
+
+ // Check for first and previous pages
+ if ($offset > 0) {
+ $firstPage = clone $currentUrl;
+ $firstPageQuery = $currentPageQuery;
+ $firstPageQuery['offset'] = 0;
+ $firstPage->setVar('page', $firstPageQuery);
+
+ $previousPage = clone $currentUrl;
+ $previousPageQuery = $currentPageQuery;
+ $previousOffset = $currentPageQuery['offset'] - $limit;
+ $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
+ $previousPage->setVar('page', $previousPageQuery);
+
+ $this->document->addLink('first', $this->queryEncode((string) $firstPage))
+ ->addLink('previous', $this->queryEncode((string) $previousPage));
+ }
+
+ // Check for next and last pages
+ if ($offset + $limit < $totalItemsCount) {
+ $nextPage = clone $currentUrl;
+ $nextPageQuery = $currentPageQuery;
+ $nextOffset = $currentPageQuery['offset'] + $limit;
+ $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
+ $nextPage->setVar('page', $nextPageQuery);
+
+ $lastPage = clone $currentUrl;
+ $lastPageQuery = $currentPageQuery;
+ $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit;
+ $lastPage->setVar('page', $lastPageQuery);
+
+ $this->document->addLink('next', $this->queryEncode((string) $nextPage))
+ ->addLink('last', $this->queryEncode((string) $lastPage));
+ }
+
+ $collection = (new Collection($items, new JoomlaSerializer($this->type)));
+
+ // Set the data into the document and render it
+ $this->document->setData($collection);
+
+ return $this->document->render();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = ExtensionHelper::getExtensionRecord('joomla', 'file')->extension_id;
+
+ return $item;
+ }
}
diff --git a/api/components/com_config/src/View/Component/JsonapiView.php b/api/components/com_config/src/View/Component/JsonapiView.php
index 1381854646ac1..02eb0345722ac 100644
--- a/api/components/com_config/src/View/Component/JsonapiView.php
+++ b/api/components/com_config/src/View/Component/JsonapiView.php
@@ -1,4 +1,5 @@
get('component_name'));
-
- if ($component === null || !$component->enabled)
- {
- // @todo: exception component unavailable
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_INVALID_COMPONENT_NAME'), 400);
- }
-
- $data = $component->getParams()->toObject();
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500, $e);
- }
-
- $items = [];
-
- foreach ($data as $key => $value)
- {
- $item = (object) [$key => $value];
- $items[] = $this->prepareItem($item);
- }
-
- // Set up links for pagination
- $currentUrl = Uri::getInstance();
- $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
- $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
-
- $offset = $currentPageQuery['offset'];
- $limit = $currentPageQuery['limit'];
- $totalItemsCount = \count($items);
- $totalPagesAvailable = ceil($totalItemsCount / $limit);
-
- $items = array_splice($items, $offset, $limit);
-
- $this->document->addMeta('total-pages', $totalPagesAvailable)
- ->addLink('self', (string) $currentUrl);
-
- // Check for first and previous pages
- if ($offset > 0)
- {
- $firstPage = clone $currentUrl;
- $firstPageQuery = $currentPageQuery;
- $firstPageQuery['offset'] = 0;
- $firstPage->setVar('page', $firstPageQuery);
-
- $previousPage = clone $currentUrl;
- $previousPageQuery = $currentPageQuery;
- $previousOffset = $currentPageQuery['offset'] - $limit;
- $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
- $previousPage->setVar('page', $previousPageQuery);
-
- $this->document->addLink('first', $this->queryEncode((string) $firstPage))
- ->addLink('previous', $this->queryEncode((string) $previousPage));
- }
-
- // Check for next and last pages
- if ($offset + $limit < $totalItemsCount)
- {
- $nextPage = clone $currentUrl;
- $nextPageQuery = $currentPageQuery;
- $nextOffset = $currentPageQuery['offset'] + $limit;
- $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
- $nextPage->setVar('page', $nextPageQuery);
-
- $lastPage = clone $currentUrl;
- $lastPageQuery = $currentPageQuery;
- $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit;
- $lastPage->setVar('page', $lastPageQuery);
-
- $this->document->addLink('next', $this->queryEncode((string) $nextPage))
- ->addLink('last', $this->queryEncode((string) $lastPage));
- }
-
- $collection = (new Collection($items, new JoomlaSerializer($this->type)));
-
- // Set the data into the document and render it
- $this->document->setData($collection);
-
- return $this->document->render();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- $item->id = ExtensionHelper::getExtensionRecord($this->get('component_name'), 'component')->extension_id;
-
- return $item;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ try {
+ $component = ComponentHelper::getComponent($this->get('component_name'));
+
+ if ($component === null || !$component->enabled) {
+ // @todo: exception component unavailable
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_INVALID_COMPONENT_NAME'), 400);
+ }
+
+ $data = $component->getParams()->toObject();
+ } catch (\Exception $e) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_SERVER'), 500, $e);
+ }
+
+ $items = [];
+
+ foreach ($data as $key => $value) {
+ $item = (object) [$key => $value];
+ $items[] = $this->prepareItem($item);
+ }
+
+ // Set up links for pagination
+ $currentUrl = Uri::getInstance();
+ $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
+ $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
+
+ $offset = $currentPageQuery['offset'];
+ $limit = $currentPageQuery['limit'];
+ $totalItemsCount = \count($items);
+ $totalPagesAvailable = ceil($totalItemsCount / $limit);
+
+ $items = array_splice($items, $offset, $limit);
+
+ $this->document->addMeta('total-pages', $totalPagesAvailable)
+ ->addLink('self', (string) $currentUrl);
+
+ // Check for first and previous pages
+ if ($offset > 0) {
+ $firstPage = clone $currentUrl;
+ $firstPageQuery = $currentPageQuery;
+ $firstPageQuery['offset'] = 0;
+ $firstPage->setVar('page', $firstPageQuery);
+
+ $previousPage = clone $currentUrl;
+ $previousPageQuery = $currentPageQuery;
+ $previousOffset = $currentPageQuery['offset'] - $limit;
+ $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
+ $previousPage->setVar('page', $previousPageQuery);
+
+ $this->document->addLink('first', $this->queryEncode((string) $firstPage))
+ ->addLink('previous', $this->queryEncode((string) $previousPage));
+ }
+
+ // Check for next and last pages
+ if ($offset + $limit < $totalItemsCount) {
+ $nextPage = clone $currentUrl;
+ $nextPageQuery = $currentPageQuery;
+ $nextOffset = $currentPageQuery['offset'] + $limit;
+ $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
+ $nextPage->setVar('page', $nextPageQuery);
+
+ $lastPage = clone $currentUrl;
+ $lastPageQuery = $currentPageQuery;
+ $lastPageQuery['offset'] = ($totalPagesAvailable - 1) * $limit;
+ $lastPage->setVar('page', $lastPageQuery);
+
+ $this->document->addLink('next', $this->queryEncode((string) $nextPage))
+ ->addLink('last', $this->queryEncode((string) $lastPage));
+ }
+
+ $collection = (new Collection($items, new JoomlaSerializer($this->type)));
+
+ // Set the data into the document and render it
+ $this->document->setData($collection);
+
+ return $this->document->render();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = ExtensionHelper::getExtensionRecord($this->get('component_name'), 'component')->extension_id;
+
+ return $item;
+ }
}
diff --git a/api/components/com_contact/src/Controller/ContactController.php b/api/components/com_contact/src/Controller/ContactController.php
index b9805d33ab5e9..d23df969bddb8 100644
--- a/api/components/com_contact/src/Controller/ContactController.php
+++ b/api/components/com_contact/src/Controller/ContactController.php
@@ -1,4 +1,5 @@
name]))
- {
- !isset($data['com_fields']) && $data['com_fields'] = [];
-
- $data['com_fields'][$field->name] = $data[$field->name];
- unset($data[$field->name]);
- }
- }
-
- return $data;
- }
-
- /**
- * Submit contact form
- *
- * @param integer $id Leave empty if you want to retrieve data from the request
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function submitForm($id = null)
- {
- if ($id === null)
- {
- $id = $this->input->post->get('id', 0, 'int');
- }
-
- $modelName = Inflector::singularize($this->contentType);
-
- /** @var \Joomla\Component\Contact\Site\Model\ContactModel $model */
- $model = $this->getModel($modelName, 'Site');
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $model->setState('filter.published', 1);
-
- $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
- $contact = $model->getItem($id);
-
- if ($contact->id === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
-
- $contactParams = new Registry($contact->params);
-
- if (!$contactParams->get('show_email_form'))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_DISPLAY_EMAIL_FORM'));
- }
-
- // Contact plugins
- PluginHelper::importPlugin('contact');
-
- Form::addFormPath(JPATH_COMPONENT_SITE . '/forms');
-
- // Validate the posted data.
- $form = $model->getForm();
-
- if (!$form)
- {
- throw new \RuntimeException($model->getError(), 500);
- }
-
- if (!$model->validate($form, $data))
- {
- $errors = $model->getErrors();
- $messages = [];
-
- for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $messages[] = "{$errors[$i]->getMessage()}";
- }
- else
- {
- $messages[] = "{$errors[$i]}";
- }
- }
-
- throw new InvalidParameterException(implode("\n", $messages));
- }
-
- // Validation succeeded, continue with custom handlers
- $results = $this->app->triggerEvent('onValidateContact', [&$contact, &$data]);
-
- foreach ($results as $result)
- {
- if ($result instanceof \Exception)
- {
- throw new InvalidParameterException($result->getMessage());
- }
- }
-
- // Passed Validation: Process the contact plugins to integrate with other applications
- $this->app->triggerEvent('onSubmitContact', [&$contact, &$data]);
-
- // Send the email
- $sent = false;
-
- $params = ComponentHelper::getParams('com_contact');
-
- if (!$params->get('custom_reply'))
- {
- $sent = $this->_sendEmail($data, $contact, $params->get('show_email_copy', 0));
- }
-
- if (!$sent)
- {
- throw new SendEmail('Error sending message');
- }
-
- return $this;
- }
-
- /**
- * Method to get a model object, loading it if required.
- *
- * @param array $data The data to send in the email.
- * @param \stdClass $contact The user information to send the email to
- * @param boolean $emailCopyToSender True to send a copy of the email to the user.
- *
- * @return boolean True on success sending the email, false on failure.
- *
- * @since 1.6.4
- */
- private function _sendEmail($data, $contact, $emailCopyToSender)
- {
- $app = $this->app;
-
- Factory::getLanguage()->load('com_contact', JPATH_SITE, $app->getLanguage()->getTag(), true);
-
- if ($contact->email_to == '' && $contact->user_id != 0)
- {
- $contact_user = User::getInstance($contact->user_id);
- $contact->email_to = $contact_user->get('email');
- }
-
- $templateData = [
- 'sitename' => $app->get('sitename'),
- 'name' => $data['contact_name'],
- 'contactname' => $contact->name,
- 'email' => PunycodeHelper::emailToPunycode($data['contact_email']),
- 'subject' => $data['contact_subject'],
- 'body' => stripslashes($data['contact_message']),
- 'url' => Uri::base(),
- 'customfields' => '',
- ];
-
- // Load the custom fields
- if (!empty($data['com_fields']) && $fields = FieldsHelper::getFields('com_contact.mail', $contact, true, $data['com_fields']))
- {
- $output = FieldsHelper::render(
- 'com_contact.mail',
- 'fields.render',
- array(
- 'context' => 'com_contact.mail',
- 'item' => $contact,
- 'fields' => $fields,
- )
- );
-
- if ($output)
- {
- $templateData['customfields'] = $output;
- }
- }
-
- try
- {
- $mailer = new MailTemplate('com_contact.mail', $app->getLanguage()->getTag());
- $mailer->addRecipient($contact->email_to);
- $mailer->setReplyTo($templateData['email'], $templateData['name']);
- $mailer->addTemplateData($templateData);
- $sent = $mailer->send();
-
- // If we are supposed to copy the sender, do so.
- if ($emailCopyToSender == true && !empty($data['contact_email_copy']))
- {
- $mailer = new MailTemplate('com_contact.mail.copy', $app->getLanguage()->getTag());
- $mailer->addRecipient($templateData['email']);
- $mailer->setReplyTo($templateData['email'], $templateData['name']);
- $mailer->addTemplateData($templateData);
- $sent = $mailer->send();
- }
- }
- catch (MailDisabledException | phpMailerException $exception)
- {
- try
- {
- Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
-
- $sent = false;
- }
- catch (\RuntimeException $exception)
- {
- Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
-
- $sent = false;
- }
- }
-
- return $sent;
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'contacts';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'contacts';
+
+ /**
+ * Method to allow extended classes to manipulate the data to be saved for an extension.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessSaveData(array $data): array
+ {
+ foreach (FieldsHelper::getFields('com_contact.contact') as $field) {
+ if (isset($data[$field->name])) {
+ !isset($data['com_fields']) && $data['com_fields'] = [];
+
+ $data['com_fields'][$field->name] = $data[$field->name];
+ unset($data[$field->name]);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Submit contact form
+ *
+ * @param integer $id Leave empty if you want to retrieve data from the request
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function submitForm($id = null)
+ {
+ if ($id === null) {
+ $id = $this->input->post->get('id', 0, 'int');
+ }
+
+ $modelName = Inflector::singularize($this->contentType);
+
+ /** @var \Joomla\Component\Contact\Site\Model\ContactModel $model */
+ $model = $this->getModel($modelName, 'Site');
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $model->setState('filter.published', 1);
+
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+ $contact = $model->getItem($id);
+
+ if ($contact->id === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
+
+ $contactParams = new Registry($contact->params);
+
+ if (!$contactParams->get('show_email_form')) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_DISPLAY_EMAIL_FORM'));
+ }
+
+ // Contact plugins
+ PluginHelper::importPlugin('contact');
+
+ Form::addFormPath(JPATH_COMPONENT_SITE . '/forms');
+
+ // Validate the posted data.
+ $form = $model->getForm();
+
+ if (!$form) {
+ throw new \RuntimeException($model->getError(), 500);
+ }
+
+ if (!$model->validate($form, $data)) {
+ $errors = $model->getErrors();
+ $messages = [];
+
+ for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $messages[] = "{$errors[$i]->getMessage()}";
+ } else {
+ $messages[] = "{$errors[$i]}";
+ }
+ }
+
+ throw new InvalidParameterException(implode("\n", $messages));
+ }
+
+ // Validation succeeded, continue with custom handlers
+ $results = $this->app->triggerEvent('onValidateContact', [&$contact, &$data]);
+
+ foreach ($results as $result) {
+ if ($result instanceof \Exception) {
+ throw new InvalidParameterException($result->getMessage());
+ }
+ }
+
+ // Passed Validation: Process the contact plugins to integrate with other applications
+ $this->app->triggerEvent('onSubmitContact', [&$contact, &$data]);
+
+ // Send the email
+ $sent = false;
+
+ $params = ComponentHelper::getParams('com_contact');
+
+ if (!$params->get('custom_reply')) {
+ $sent = $this->_sendEmail($data, $contact, $params->get('show_email_copy', 0));
+ }
+
+ if (!$sent) {
+ throw new SendEmail('Error sending message');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to get a model object, loading it if required.
+ *
+ * @param array $data The data to send in the email.
+ * @param \stdClass $contact The user information to send the email to
+ * @param boolean $emailCopyToSender True to send a copy of the email to the user.
+ *
+ * @return boolean True on success sending the email, false on failure.
+ *
+ * @since 1.6.4
+ */
+ private function _sendEmail($data, $contact, $emailCopyToSender)
+ {
+ $app = $this->app;
+
+ Factory::getLanguage()->load('com_contact', JPATH_SITE, $app->getLanguage()->getTag(), true);
+
+ if ($contact->email_to == '' && $contact->user_id != 0) {
+ $contact_user = User::getInstance($contact->user_id);
+ $contact->email_to = $contact_user->get('email');
+ }
+
+ $templateData = [
+ 'sitename' => $app->get('sitename'),
+ 'name' => $data['contact_name'],
+ 'contactname' => $contact->name,
+ 'email' => PunycodeHelper::emailToPunycode($data['contact_email']),
+ 'subject' => $data['contact_subject'],
+ 'body' => stripslashes($data['contact_message']),
+ 'url' => Uri::base(),
+ 'customfields' => '',
+ ];
+
+ // Load the custom fields
+ if (!empty($data['com_fields']) && $fields = FieldsHelper::getFields('com_contact.mail', $contact, true, $data['com_fields'])) {
+ $output = FieldsHelper::render(
+ 'com_contact.mail',
+ 'fields.render',
+ array(
+ 'context' => 'com_contact.mail',
+ 'item' => $contact,
+ 'fields' => $fields,
+ )
+ );
+
+ if ($output) {
+ $templateData['customfields'] = $output;
+ }
+ }
+
+ try {
+ $mailer = new MailTemplate('com_contact.mail', $app->getLanguage()->getTag());
+ $mailer->addRecipient($contact->email_to);
+ $mailer->setReplyTo($templateData['email'], $templateData['name']);
+ $mailer->addTemplateData($templateData);
+ $sent = $mailer->send();
+
+ // If we are supposed to copy the sender, do so.
+ if ($emailCopyToSender == true && !empty($data['contact_email_copy'])) {
+ $mailer = new MailTemplate('com_contact.mail.copy', $app->getLanguage()->getTag());
+ $mailer->addRecipient($templateData['email']);
+ $mailer->setReplyTo($templateData['email'], $templateData['name']);
+ $mailer->addTemplateData($templateData);
+ $sent = $mailer->send();
+ }
+ } catch (MailDisabledException | phpMailerException $exception) {
+ try {
+ Log::add(Text::_($exception->getMessage()), Log::WARNING, 'jerror');
+
+ $sent = false;
+ } catch (\RuntimeException $exception) {
+ Factory::getApplication()->enqueueMessage(Text::_($exception->errorMessage()), 'warning');
+
+ $sent = false;
+ }
+ }
+
+ return $sent;
+ }
}
diff --git a/api/components/com_contact/src/Serializer/ContactSerializer.php b/api/components/com_contact/src/Serializer/ContactSerializer.php
index 23fe7333f3b40..89f7e19020d3e 100644
--- a/api/components/com_contact/src/Serializer/ContactSerializer.php
+++ b/api/components/com_contact/src/Serializer/ContactSerializer.php
@@ -1,4 +1,5 @@
type);
-
- foreach ($model->associations as $association)
- {
- $resources[] = (new Resource($association, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/contact/' . $association->id));
- }
-
- $collection = new Collection($resources, $serializer);
-
- return new Relationship($collection);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function category($model)
- {
- $serializer = new JoomlaSerializer('categories');
-
- $resource = (new Resource($model->catid, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/categories/' . $model->catid));
-
- return new Relationship($resource);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function createdBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->created_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
-
- return new Relationship($resource);
- }
-
- /**
- * Build editor relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function modifiedBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->modified_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
-
- return new Relationship($resource);
- }
-
- /**
- * Build contact user relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function userId($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->user_id, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->user_id));
-
- return new Relationship($resource);
- }
+ use TagApiSerializerTrait;
+
+ /**
+ * Build content relationships by associations
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function languageAssociations($model)
+ {
+ $resources = [];
+
+ // @todo: This can't be hardcoded in the future?
+ $serializer = new JoomlaSerializer($this->type);
+
+ foreach ($model->associations as $association) {
+ $resources[] = (new Resource($association, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/contact/' . $association->id));
+ }
+
+ $collection = new Collection($resources, $serializer);
+
+ return new Relationship($collection);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function category($model)
+ {
+ $serializer = new JoomlaSerializer('categories');
+
+ $resource = (new Resource($model->catid, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/categories/' . $model->catid));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function createdBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->created_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build editor relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function modifiedBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->modified_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build contact user relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function userId($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->user_id, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->user_id));
+
+ return new Relationship($resource);
+ }
}
diff --git a/api/components/com_contact/src/View/Contacts/JsonapiView.php b/api/components/com_contact/src/View/Contacts/JsonapiView.php
index de1091fd6a676..7814dfe7e70b5 100644
--- a/api/components/com_contact/src/View/Contacts/JsonapiView.php
+++ b/api/components/com_contact/src/View/Contacts/JsonapiView.php
@@ -1,4 +1,5 @@
serializer = new ContactSerializer($config['contentType']);
- }
-
- parent::__construct($config);
- }
-
- /**
- * Execute and display a template script.
- *
- * @param array|null $items Array of items
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayList(array $items = null)
- {
- foreach (FieldsHelper::getFields('com_contact.contact') as $field)
- {
- $this->fieldsToRenderList[] = $field->name;
- }
-
- return parent::displayList();
- }
-
- /**
- * Execute and display a template script.
- *
- * @param object $item Item
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayItem($item = null)
- {
- foreach (FieldsHelper::getFields('com_contact.contact') as $field)
- {
- $this->fieldsToRenderItem[] = $field->name;
- }
-
- if (Multilanguage::isEnabled())
- {
- $this->fieldsToRenderItem[] = 'languageAssociations';
- $this->relationship[] = 'languageAssociations';
- }
-
- return parent::displayItem();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- foreach (FieldsHelper::getFields('com_contact.contact', $item, true) as $field)
- {
- $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
- }
-
- if (Multilanguage::isEnabled() && !empty($item->associations))
- {
- $associations = [];
-
- foreach ($item->associations as $language => $association)
- {
- $itemId = explode(':', $association)[0];
-
- $associations[] = (object) [
- 'id' => $itemId,
- 'language' => $language,
- ];
- }
-
- $item->associations = $associations;
- }
-
- if (!empty($item->tags->tags))
- {
- $tagsIds = explode(',', $item->tags->tags);
- $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
-
- $item->tags = array_combine($tagsIds, $tagsNames);
- }
- else
- {
- $item->tags = [];
- }
-
- if (isset($item->image))
- {
- $item->image = ContentHelper::resolve($item->image);
- }
-
- return parent::prepareItem($item);
- }
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderItem = [
+ 'id',
+ 'alias',
+ 'name',
+ 'category',
+ 'created',
+ 'created_by',
+ 'created_by_alias',
+ 'modified',
+ 'modified_by',
+ 'image',
+ 'tags',
+ 'featured',
+ 'publish_up',
+ 'publish_down',
+ 'version',
+ 'hits',
+ 'metakey',
+ 'metadesc',
+ 'metadata',
+ 'con_position',
+ 'address',
+ 'suburb',
+ 'state',
+ 'country',
+ 'postcode',
+ 'telephone',
+ 'fax',
+ 'misc',
+ 'email_to',
+ 'default_con',
+ 'user_id',
+ 'access',
+ 'mobile',
+ 'webpage',
+ 'sortname1',
+ 'sortname2',
+ 'sortname3',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderList = [
+ 'id',
+ 'alias',
+ 'name',
+ 'category',
+ 'created',
+ 'created_by',
+ 'created_by_alias',
+ 'modified',
+ 'modified_by',
+ 'image',
+ 'tags',
+ 'user_id',
+ ];
+
+ /**
+ * The relationships the item has
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $relationship = [
+ 'category',
+ 'created_by',
+ 'modified_by',
+ 'user_id',
+ 'tags',
+ ];
+
+ /**
+ * Constructor.
+ *
+ * @param array $config A named configuration array for object construction.
+ * contentType: the name (optional) of the content type to use for the serialization
+ *
+ * @since 4.0.0
+ */
+ public function __construct($config = [])
+ {
+ if (\array_key_exists('contentType', $config)) {
+ $this->serializer = new ContactSerializer($config['contentType']);
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ foreach (FieldsHelper::getFields('com_contact.contact') as $field) {
+ $this->fieldsToRenderList[] = $field->name;
+ }
+
+ return parent::displayList();
+ }
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ foreach (FieldsHelper::getFields('com_contact.contact') as $field) {
+ $this->fieldsToRenderItem[] = $field->name;
+ }
+
+ if (Multilanguage::isEnabled()) {
+ $this->fieldsToRenderItem[] = 'languageAssociations';
+ $this->relationship[] = 'languageAssociations';
+ }
+
+ return parent::displayItem();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ foreach (FieldsHelper::getFields('com_contact.contact', $item, true) as $field) {
+ $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
+ }
+
+ if (Multilanguage::isEnabled() && !empty($item->associations)) {
+ $associations = [];
+
+ foreach ($item->associations as $language => $association) {
+ $itemId = explode(':', $association)[0];
+
+ $associations[] = (object) [
+ 'id' => $itemId,
+ 'language' => $language,
+ ];
+ }
+
+ $item->associations = $associations;
+ }
+
+ if (!empty($item->tags->tags)) {
+ $tagsIds = explode(',', $item->tags->tags);
+ $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
+
+ $item->tags = array_combine($tagsIds, $tagsNames);
+ } else {
+ $item->tags = [];
+ }
+
+ if (isset($item->image)) {
+ $item->image = ContentHelper::resolve($item->image);
+ }
+
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_content/src/Controller/ArticlesController.php b/api/components/com_content/src/Controller/ArticlesController.php
index bf616d451a866..6e5bac20f5ef9 100644
--- a/api/components/com_content/src/Controller/ArticlesController.php
+++ b/api/components/com_content/src/Controller/ArticlesController.php
@@ -1,4 +1,5 @@
input->get('filter', [], 'array');
- $filter = InputFilter::getInstance();
-
- if (\array_key_exists('author', $apiFilterInfo))
- {
- $this->modelState->set('filter.author_id', $filter->clean($apiFilterInfo['author'], 'INT'));
- }
-
- if (\array_key_exists('category', $apiFilterInfo))
- {
- $this->modelState->set('filter.category_id', $filter->clean($apiFilterInfo['category'], 'INT'));
- }
-
- if (\array_key_exists('search', $apiFilterInfo))
- {
- $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
- }
-
- if (\array_key_exists('state', $apiFilterInfo))
- {
- $this->modelState->set('filter.published', $filter->clean($apiFilterInfo['state'], 'INT'));
- }
-
- if (\array_key_exists('language', $apiFilterInfo))
- {
- $this->modelState->set('filter.language', $filter->clean($apiFilterInfo['language'], 'STRING'));
- }
-
- $apiListInfo = $this->input->get('list', [], 'array');
-
- if (array_key_exists('ordering', $apiListInfo))
- {
- $this->modelState->set('list.ordering', $filter->clean($apiListInfo['ordering'], 'STRING'));
- }
-
- if (array_key_exists('direction', $apiListInfo))
- {
- $this->modelState->set('list.direction', $filter->clean($apiListInfo['direction'], 'STRING'));
- }
-
- return parent::displayList();
- }
-
- /**
- * Method to allow extended classes to manipulate the data to be saved for an extension.
- *
- * @param array $data An array of input data.
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function preprocessSaveData(array $data): array
- {
- foreach (FieldsHelper::getFields('com_content.article') as $field)
- {
- if (isset($data[$field->name]))
- {
- !isset($data['com_fields']) && $data['com_fields'] = [];
-
- $data['com_fields'][$field->name] = $data[$field->name];
- unset($data[$field->name]);
- }
- }
-
- return $data;
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'articles';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'articles';
+
+ /**
+ * Article list view amended to add filtering of data
+ *
+ * @return static A BaseController object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $apiFilterInfo = $this->input->get('filter', [], 'array');
+ $filter = InputFilter::getInstance();
+
+ if (\array_key_exists('author', $apiFilterInfo)) {
+ $this->modelState->set('filter.author_id', $filter->clean($apiFilterInfo['author'], 'INT'));
+ }
+
+ if (\array_key_exists('category', $apiFilterInfo)) {
+ $this->modelState->set('filter.category_id', $filter->clean($apiFilterInfo['category'], 'INT'));
+ }
+
+ if (\array_key_exists('search', $apiFilterInfo)) {
+ $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
+ }
+
+ if (\array_key_exists('state', $apiFilterInfo)) {
+ $this->modelState->set('filter.published', $filter->clean($apiFilterInfo['state'], 'INT'));
+ }
+
+ if (\array_key_exists('language', $apiFilterInfo)) {
+ $this->modelState->set('filter.language', $filter->clean($apiFilterInfo['language'], 'STRING'));
+ }
+
+ $apiListInfo = $this->input->get('list', [], 'array');
+
+ if (array_key_exists('ordering', $apiListInfo)) {
+ $this->modelState->set('list.ordering', $filter->clean($apiListInfo['ordering'], 'STRING'));
+ }
+
+ if (array_key_exists('direction', $apiListInfo)) {
+ $this->modelState->set('list.direction', $filter->clean($apiListInfo['direction'], 'STRING'));
+ }
+
+ return parent::displayList();
+ }
+
+ /**
+ * Method to allow extended classes to manipulate the data to be saved for an extension.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessSaveData(array $data): array
+ {
+ foreach (FieldsHelper::getFields('com_content.article') as $field) {
+ if (isset($data[$field->name])) {
+ !isset($data['com_fields']) && $data['com_fields'] = [];
+
+ $data['com_fields'][$field->name] = $data[$field->name];
+ unset($data[$field->name]);
+ }
+ }
+
+ return $data;
+ }
}
diff --git a/api/components/com_content/src/Helper/ContentHelper.php b/api/components/com_content/src/Helper/ContentHelper.php
index d7e64de506108..60cfd4681b387 100644
--- a/api/components/com_content/src/Helper/ContentHelper.php
+++ b/api/components/com_content/src/Helper/ContentHelper.php
@@ -1,4 +1,5 @@
type);
-
- foreach ($model->associations as $association)
- {
- $resources[] = (new Resource($association, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/articles/' . $association->id));
- }
-
- $collection = new Collection($resources, $serializer);
-
- return new Relationship($collection);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function category($model)
- {
- $serializer = new JoomlaSerializer('categories');
-
- $resource = (new Resource($model->catid, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/categories/' . $model->catid));
-
- return new Relationship($resource);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function createdBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->created_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
-
- return new Relationship($resource);
- }
-
- /**
- * Build editor relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function modifiedBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->modified_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
-
- return new Relationship($resource);
- }
+ /**
+ * Build content relationships by associations
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function languageAssociations($model)
+ {
+ $resources = [];
+
+ // @todo: This can't be hardcoded in the future?
+ $serializer = new JoomlaSerializer($this->type);
+
+ foreach ($model->associations as $association) {
+ $resources[] = (new Resource($association, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/articles/' . $association->id));
+ }
+
+ $collection = new Collection($resources, $serializer);
+
+ return new Relationship($collection);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function category($model)
+ {
+ $serializer = new JoomlaSerializer('categories');
+
+ $resource = (new Resource($model->catid, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/content/categories/' . $model->catid));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function createdBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->created_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build editor relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function modifiedBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->modified_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
+
+ return new Relationship($resource);
+ }
}
diff --git a/api/components/com_content/src/View/Articles/JsonapiView.php b/api/components/com_content/src/View/Articles/JsonapiView.php
index 6b4170a544495..6feed013eb735 100644
--- a/api/components/com_content/src/View/Articles/JsonapiView.php
+++ b/api/components/com_content/src/View/Articles/JsonapiView.php
@@ -1,4 +1,5 @@
serializer = new ContentSerializer($config['contentType']);
- }
-
- parent::__construct($config);
- }
-
- /**
- * Execute and display a template script.
- *
- * @param array|null $items Array of items
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayList(array $items = null)
- {
- foreach (FieldsHelper::getFields('com_content.article') as $field)
- {
- $this->fieldsToRenderList[] = $field->name;
- }
-
- return parent::displayList();
- }
-
- /**
- * Execute and display a template script.
- *
- * @param object $item Item
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayItem($item = null)
- {
- $this->relationship[] = 'modified_by';
-
- foreach (FieldsHelper::getFields('com_content.article') as $field)
- {
- $this->fieldsToRenderItem[] = $field->name;
- }
-
- if (Multilanguage::isEnabled())
- {
- $this->fieldsToRenderItem[] = 'languageAssociations';
- $this->relationship[] = 'languageAssociations';
- }
-
- return parent::displayItem();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- $item->text = $item->introtext . ' ' . $item->fulltext;
-
- // Process the content plugins.
- PluginHelper::importPlugin('content');
- Factory::getApplication()->triggerEvent('onContentPrepare', ['com_content.article', &$item, &$item->params]);
-
- foreach (FieldsHelper::getFields('com_content.article', $item, true) as $field)
- {
- $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
- }
-
- if (Multilanguage::isEnabled() && !empty($item->associations))
- {
- $associations = [];
-
- foreach ($item->associations as $language => $association)
- {
- $itemId = explode(':', $association)[0];
-
- $associations[] = (object) [
- 'id' => $itemId,
- 'language' => $language,
- ];
- }
-
- $item->associations = $associations;
- }
-
- if (!empty($item->tags->tags))
- {
- $tagsIds = explode(',', $item->tags->tags);
- $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
-
- $item->tags = array_combine($tagsIds, $tagsNames);
- }
- else
- {
- $item->tags = [];
- }
-
- if (isset($item->images))
- {
- $registry = new Registry($item->images);
- $item->images = $registry->toArray();
-
- if (!empty($item->images['image_intro']))
- {
- $item->images['image_intro'] = ContentHelper::resolve($item->images['image_intro']);
- }
-
- if (!empty($item->images['image_fulltext']))
- {
- $item->images['image_fulltext'] = ContentHelper::resolve($item->images['image_fulltext']);
- }
- }
-
- return parent::prepareItem($item);
- }
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderItem = [
+ 'id',
+ 'typeAlias',
+ 'asset_id',
+ 'title',
+ 'text',
+ 'tags',
+ 'language',
+ 'state',
+ 'category',
+ 'images',
+ 'metakey',
+ 'metadesc',
+ 'metadata',
+ 'access',
+ 'featured',
+ 'alias',
+ 'note',
+ 'publish_up',
+ 'publish_down',
+ 'urls',
+ 'created',
+ 'created_by',
+ 'created_by_alias',
+ 'modified',
+ 'modified_by',
+ 'hits',
+ 'version',
+ 'featured_up',
+ 'featured_down',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderList = [
+ 'id',
+ 'typeAlias',
+ 'asset_id',
+ 'title',
+ 'text',
+ 'tags',
+ 'language',
+ 'state',
+ 'category',
+ 'images',
+ 'metakey',
+ 'metadesc',
+ 'metadata',
+ 'access',
+ 'featured',
+ 'alias',
+ 'note',
+ 'publish_up',
+ 'publish_down',
+ 'urls',
+ 'created',
+ 'created_by',
+ 'created_by_alias',
+ 'modified',
+ 'hits',
+ 'version',
+ 'featured_up',
+ 'featured_down',
+ ];
+
+ /**
+ * The relationships the item has
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $relationship = [
+ 'category',
+ 'created_by',
+ 'tags',
+ ];
+
+ /**
+ * Constructor.
+ *
+ * @param array $config A named configuration array for object construction.
+ * contentType: the name (optional) of the content type to use for the serialization
+ *
+ * @since 4.0.0
+ */
+ public function __construct($config = [])
+ {
+ if (\array_key_exists('contentType', $config)) {
+ $this->serializer = new ContentSerializer($config['contentType']);
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ foreach (FieldsHelper::getFields('com_content.article') as $field) {
+ $this->fieldsToRenderList[] = $field->name;
+ }
+
+ return parent::displayList();
+ }
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ $this->relationship[] = 'modified_by';
+
+ foreach (FieldsHelper::getFields('com_content.article') as $field) {
+ $this->fieldsToRenderItem[] = $field->name;
+ }
+
+ if (Multilanguage::isEnabled()) {
+ $this->fieldsToRenderItem[] = 'languageAssociations';
+ $this->relationship[] = 'languageAssociations';
+ }
+
+ return parent::displayItem();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->text = $item->introtext . ' ' . $item->fulltext;
+
+ // Process the content plugins.
+ PluginHelper::importPlugin('content');
+ Factory::getApplication()->triggerEvent('onContentPrepare', ['com_content.article', &$item, &$item->params]);
+
+ foreach (FieldsHelper::getFields('com_content.article', $item, true) as $field) {
+ $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
+ }
+
+ if (Multilanguage::isEnabled() && !empty($item->associations)) {
+ $associations = [];
+
+ foreach ($item->associations as $language => $association) {
+ $itemId = explode(':', $association)[0];
+
+ $associations[] = (object) [
+ 'id' => $itemId,
+ 'language' => $language,
+ ];
+ }
+
+ $item->associations = $associations;
+ }
+
+ if (!empty($item->tags->tags)) {
+ $tagsIds = explode(',', $item->tags->tags);
+ $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
+
+ $item->tags = array_combine($tagsIds, $tagsNames);
+ } else {
+ $item->tags = [];
+ }
+
+ if (isset($item->images)) {
+ $registry = new Registry($item->images);
+ $item->images = $registry->toArray();
+
+ if (!empty($item->images['image_intro'])) {
+ $item->images['image_intro'] = ContentHelper::resolve($item->images['image_intro']);
+ }
+
+ if (!empty($item->images['image_fulltext'])) {
+ $item->images['image_fulltext'] = ContentHelper::resolve($item->images['image_fulltext']);
+ }
+ }
+
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_contenthistory/src/Controller/HistoryController.php b/api/components/com_contenthistory/src/Controller/HistoryController.php
index fba5a7f2dd75e..3bdf1107a07b2 100644
--- a/api/components/com_contenthistory/src/Controller/HistoryController.php
+++ b/api/components/com_contenthistory/src/Controller/HistoryController.php
@@ -1,4 +1,5 @@
modelState->set('type_alias', $this->getTypeAliasFromInput());
- $this->modelState->set('type_id', $this->getTypeIdFromInput());
- $this->modelState->set('item_id', $this->getTypeAliasFromInput() . '.' . $this->getItemIdFromInput());
- $this->modelState->set('list.ordering', 'h.save_date');
- $this->modelState->set('list.direction', 'DESC');
-
- return parent::displayList();
- }
-
- /**
- * Method to edit an existing record.
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function keep()
- {
- /** @var HistoryModel $model */
- $model = $this->getModel($this->contentType);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $recordId = $this->input->getInt('id');
-
- if (!$recordId)
- {
- throw new Exception\ResourceNotFound(Text::_('JLIB_APPLICATION_ERROR_RECORD'), 404);
- }
-
- $cid = [$recordId];
-
- if (!$model->keep($cid))
- {
- throw new Exception\Save(Text::plural('COM_CONTENTHISTORY_N_ITEMS_KEEP_TOGGLE', \count($cid)));
- }
-
- return $this;
- }
-
- /**
- * Get item id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getItemIdFromInput()
- {
- return $this->input->exists('id') ?
- $this->input->get('id') : $this->input->post->get('id');
- }
-
- /**
- * Get type id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getTypeIdFromInput()
- {
- return $this->input->exists('type_id') ?
- $this->input->get('type_id') : $this->input->post->get('type_id');
- }
-
- /**
- * Get type alias from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getTypeAliasFromInput()
- {
- return $this->input->exists('type_alias') ?
- $this->input->get('type_alias') : $this->input->post->get('type_alias');
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'history';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'history';
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('type_alias', $this->getTypeAliasFromInput());
+ $this->modelState->set('type_id', $this->getTypeIdFromInput());
+ $this->modelState->set('item_id', $this->getTypeAliasFromInput() . '.' . $this->getItemIdFromInput());
+ $this->modelState->set('list.ordering', 'h.save_date');
+ $this->modelState->set('list.direction', 'DESC');
+
+ return parent::displayList();
+ }
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function keep()
+ {
+ /** @var HistoryModel $model */
+ $model = $this->getModel($this->contentType);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $recordId = $this->input->getInt('id');
+
+ if (!$recordId) {
+ throw new Exception\ResourceNotFound(Text::_('JLIB_APPLICATION_ERROR_RECORD'), 404);
+ }
+
+ $cid = [$recordId];
+
+ if (!$model->keep($cid)) {
+ throw new Exception\Save(Text::plural('COM_CONTENTHISTORY_N_ITEMS_KEEP_TOGGLE', \count($cid)));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get item id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getItemIdFromInput()
+ {
+ return $this->input->exists('id') ?
+ $this->input->get('id') : $this->input->post->get('id');
+ }
+
+ /**
+ * Get type id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getTypeIdFromInput()
+ {
+ return $this->input->exists('type_id') ?
+ $this->input->get('type_id') : $this->input->post->get('type_id');
+ }
+
+ /**
+ * Get type alias from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getTypeAliasFromInput()
+ {
+ return $this->input->exists('type_alias') ?
+ $this->input->get('type_alias') : $this->input->post->get('type_alias');
+ }
}
diff --git a/api/components/com_contenthistory/src/View/History/JsonapiView.php b/api/components/com_contenthistory/src/View/History/JsonapiView.php
index 686c049c8922b..0b2b4e90ad73c 100644
--- a/api/components/com_contenthistory/src/View/History/JsonapiView.php
+++ b/api/components/com_contenthistory/src/View/History/JsonapiView.php
@@ -1,4 +1,5 @@
id = $item->version_id;
- unset($item->version_id);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->version_id;
+ unset($item->version_id);
- $item->version_data = (array) json_decode($item->version_data, true);
+ $item->version_data = (array) json_decode($item->version_data, true);
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_fields/src/Controller/FieldsController.php b/api/components/com_fields/src/Controller/FieldsController.php
index d3611102f6714..f0033416e8c64 100644
--- a/api/components/com_fields/src/Controller/FieldsController.php
+++ b/api/components/com_fields/src/Controller/FieldsController.php
@@ -1,4 +1,5 @@
modelState->set('filter.context', $this->getContextFromInput());
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.context', $this->getContextFromInput());
- return parent::displayItem($id);
- }
+ return parent::displayItem($id);
+ }
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.context', $this->getContextFromInput());
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.context', $this->getContextFromInput());
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Get extension from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getContextFromInput()
- {
- return $this->input->exists('context') ?
- $this->input->get('context') : $this->input->post->get('context');
- }
+ /**
+ * Get extension from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getContextFromInput()
+ {
+ return $this->input->exists('context') ?
+ $this->input->get('context') : $this->input->post->get('context');
+ }
}
diff --git a/api/components/com_fields/src/Controller/GroupsController.php b/api/components/com_fields/src/Controller/GroupsController.php
index 85e42217ec930..0da11a598d1ee 100644
--- a/api/components/com_fields/src/Controller/GroupsController.php
+++ b/api/components/com_fields/src/Controller/GroupsController.php
@@ -1,4 +1,5 @@
modelState->set('filter.context', $this->getContextFromInput());
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.context', $this->getContextFromInput());
- return parent::displayItem($id);
- }
+ return parent::displayItem($id);
+ }
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.context', $this->getContextFromInput());
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.context', $this->getContextFromInput());
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Get extension from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getContextFromInput()
- {
- return $this->input->exists('context') ?
- $this->input->get('context') : $this->input->post->get('context');
- }
+ /**
+ * Get extension from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getContextFromInput()
+ {
+ return $this->input->exists('context') ?
+ $this->input->get('context') : $this->input->post->get('context');
+ }
}
diff --git a/api/components/com_fields/src/View/Fields/JsonapiView.php b/api/components/com_fields/src/View/Fields/JsonapiView.php
index 06adcbcf9d51a..3697dba21ac76 100644
--- a/api/components/com_fields/src/View/Fields/JsonapiView.php
+++ b/api/components/com_fields/src/View/Fields/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $item = $this->prepareItem($model->getItem());
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ if ($item === null) {
+ /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
+ $model = $this->getModel();
+ $item = $this->prepareItem($model->getItem());
+ }
- if ($item->id === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->id === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- if ($item->context != $this->getModel()->getState('filter.context'))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->context != $this->getModel()->getState('filter.context')) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- return parent::displayItem($item);
- }
+ return parent::displayItem($item);
+ }
}
diff --git a/api/components/com_fields/src/View/Groups/JsonapiView.php b/api/components/com_fields/src/View/Groups/JsonapiView.php
index 30d6f55e01e47..21acaa1d02dbf 100644
--- a/api/components/com_fields/src/View/Groups/JsonapiView.php
+++ b/api/components/com_fields/src/View/Groups/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $item = $this->prepareItem($model->getItem());
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ if ($item === null) {
+ /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
+ $model = $this->getModel();
+ $item = $this->prepareItem($model->getItem());
+ }
- if ($item->id === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->id === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- if ($item->context != $this->getModel()->getState('filter.context'))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->context != $this->getModel()->getState('filter.context')) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- return parent::displayItem($item);
- }
+ return parent::displayItem($item);
+ }
}
diff --git a/api/components/com_installer/src/Controller/ManageController.php b/api/components/com_installer/src/Controller/ManageController.php
index c87ebc370f990..74879937c6af2 100644
--- a/api/components/com_installer/src/Controller/ManageController.php
+++ b/api/components/com_installer/src/Controller/ManageController.php
@@ -1,4 +1,5 @@
input->get('core', $this->input->get->get('core'));
+ /**
+ * Extension list view amended to add filtering of data
+ *
+ * @return static A BaseController object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $requestBool = $this->input->get('core', $this->input->get->get('core'));
- if (!\is_null($requestBool) && $requestBool !== 'true' && $requestBool !== 'false')
- {
- // Send the error response
- $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'core');
+ if (!\is_null($requestBool) && $requestBool !== 'true' && $requestBool !== 'false') {
+ // Send the error response
+ $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'core');
- throw new InvalidParameterException($error, 400, null, 'core');
- }
+ throw new InvalidParameterException($error, 400, null, 'core');
+ }
- if (!\is_null($requestBool))
- {
- $this->modelState->set('filter.core', ($requestBool === 'true') ? '1' : '0');
- }
+ if (!\is_null($requestBool)) {
+ $this->modelState->set('filter.core', ($requestBool === 'true') ? '1' : '0');
+ }
- $this->modelState->set('filter.status', $this->input->get('status', $this->input->get->get('status', null, 'INT'), 'INT'));
- $this->modelState->set('filter.type', $this->input->get('type', $this->input->get->get('type', null, 'STRING'), 'STRING'));
+ $this->modelState->set('filter.status', $this->input->get('status', $this->input->get->get('status', null, 'INT'), 'INT'));
+ $this->modelState->set('filter.type', $this->input->get('type', $this->input->get->get('type', null, 'STRING'), 'STRING'));
- return parent::displayList();
- }
+ return parent::displayList();
+ }
}
diff --git a/api/components/com_installer/src/View/Manage/JsonapiView.php b/api/components/com_installer/src/View/Manage/JsonapiView.php
index 9cd75874731d7..7a403bbb14f39 100644
--- a/api/components/com_installer/src/View/Manage/JsonapiView.php
+++ b/api/components/com_installer/src/View/Manage/JsonapiView.php
@@ -1,4 +1,5 @@
id = $item->extension_id;
- unset($item->extension_id);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->extension_id;
+ unset($item->extension_id);
- return $item;
- }
+ return $item;
+ }
}
diff --git a/api/components/com_languages/src/Controller/LanguagesController.php b/api/components/com_languages/src/Controller/LanguagesController.php
index 5ef456a9d2416..a801185c9494a 100644
--- a/api/components/com_languages/src/Controller/LanguagesController.php
+++ b/api/components/com_languages/src/Controller/LanguagesController.php
@@ -1,4 +1,5 @@
modelState->set('filter.language', $this->getLanguageFromInput());
- $this->modelState->set('filter.client', $this->getClientFromInput());
-
- return parent::displayItem($id);
- }
-
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.language', $this->getLanguageFromInput());
- $this->modelState->set('filter.client', $this->getClientFromInput());
-
- return parent::displayList();
- }
-
- /**
- * Method to save a record.
- *
- * @param integer $recordKey The primary key of the item (if exists)
- *
- * @return integer The record ID on success, false on failure
- *
- * @since 4.0.0
- */
- protected function save($recordKey = null)
- {
- /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
- $model = $this->getModel(Inflector::singularize($this->contentType));
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $model->setState('filter.language', $this->input->post->get('lang_code'));
- $model->setState('filter.client', $this->input->post->get('app'));
-
- $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
-
- // @todo: Not the cleanest thing ever but it works...
- Form::addFormPath(JPATH_COMPONENT_ADMINISTRATOR . '/forms');
-
- // Validate the posted data.
- $form = $model->getForm($data, false);
-
- if (!$form)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_FORM_CREATE'));
- }
-
- // Test whether the data is valid.
- $validData = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($validData === false)
- {
- $errors = $model->getErrors();
- $messages = [];
-
- for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++)
- {
- if ($errors[$i] instanceof \Exception)
- {
- $messages[] = "{$errors[$i]->getMessage()}";
- }
- else
- {
- $messages[] = "{$errors[$i]}";
- }
- }
-
- throw new InvalidParameterException(implode("\n", $messages));
- }
-
- if (!isset($validData['tags']))
- {
- $validData['tags'] = [];
- }
-
- if (!$model->save($validData))
- {
- throw new Exception\Save(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
- }
-
- return $validData['key'];
- }
-
- /**
- * Removes an item.
- *
- * @param integer $id The primary key to delete item.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function delete($id = null)
- {
- $id = $this->input->get('id', '', 'string');
-
- $this->input->set('model', $this->contentType);
-
- $this->modelState->set('filter.language', $this->getLanguageFromInput());
- $this->modelState->set('filter.client', $this->getClientFromInput());
-
- parent::delete($id);
- }
-
- /**
- * Get client code from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getClientFromInput()
- {
- return $this->input->exists('app') ? $this->input->get('app') : $this->input->post->get('app');
- }
-
- /**
- * Get language code from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getLanguageFromInput()
- {
- return $this->input->exists('lang_code') ?
- $this->input->get('lang_code') : $this->input->post->get('lang_code');
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'overrides';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'overrides';
+
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.language', $this->getLanguageFromInput());
+ $this->modelState->set('filter.client', $this->getClientFromInput());
+
+ return parent::displayItem($id);
+ }
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.language', $this->getLanguageFromInput());
+ $this->modelState->set('filter.client', $this->getClientFromInput());
+
+ return parent::displayList();
+ }
+
+ /**
+ * Method to save a record.
+ *
+ * @param integer $recordKey The primary key of the item (if exists)
+ *
+ * @return integer The record ID on success, false on failure
+ *
+ * @since 4.0.0
+ */
+ protected function save($recordKey = null)
+ {
+ /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
+ $model = $this->getModel(Inflector::singularize($this->contentType));
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $model->setState('filter.language', $this->input->post->get('lang_code'));
+ $model->setState('filter.client', $this->input->post->get('app'));
+
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+
+ // @todo: Not the cleanest thing ever but it works...
+ Form::addFormPath(JPATH_COMPONENT_ADMINISTRATOR . '/forms');
+
+ // Validate the posted data.
+ $form = $model->getForm($data, false);
+
+ if (!$form) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_FORM_CREATE'));
+ }
+
+ // Test whether the data is valid.
+ $validData = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($validData === false) {
+ $errors = $model->getErrors();
+ $messages = [];
+
+ for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
+ if ($errors[$i] instanceof \Exception) {
+ $messages[] = "{$errors[$i]->getMessage()}";
+ } else {
+ $messages[] = "{$errors[$i]}";
+ }
+ }
+
+ throw new InvalidParameterException(implode("\n", $messages));
+ }
+
+ if (!isset($validData['tags'])) {
+ $validData['tags'] = [];
+ }
+
+ if (!$model->save($validData)) {
+ throw new Exception\Save(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
+ }
+
+ return $validData['key'];
+ }
+
+ /**
+ * Removes an item.
+ *
+ * @param integer $id The primary key to delete item.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function delete($id = null)
+ {
+ $id = $this->input->get('id', '', 'string');
+
+ $this->input->set('model', $this->contentType);
+
+ $this->modelState->set('filter.language', $this->getLanguageFromInput());
+ $this->modelState->set('filter.client', $this->getClientFromInput());
+
+ parent::delete($id);
+ }
+
+ /**
+ * Get client code from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getClientFromInput()
+ {
+ return $this->input->exists('app') ? $this->input->get('app') : $this->input->post->get('app');
+ }
+
+ /**
+ * Get language code from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getLanguageFromInput()
+ {
+ return $this->input->exists('lang_code') ?
+ $this->input->get('lang_code') : $this->input->post->get('lang_code');
+ }
}
diff --git a/api/components/com_languages/src/Controller/StringsController.php b/api/components/com_languages/src/Controller/StringsController.php
index 4bdd554f7040e..656ceb4bb29b2 100644
--- a/api/components/com_languages/src/Controller/StringsController.php
+++ b/api/components/com_languages/src/Controller/StringsController.php
@@ -1,4 +1,5 @@
input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
-
- if (!isset($data['searchstring']) || !\is_string($data['searchstring']))
- {
- throw new InvalidParameterException("Invalid param 'searchstring'");
- }
-
- if (!isset($data['searchtype']) || !\in_array($data['searchtype'], ['constant', 'value']))
- {
- throw new InvalidParameterException("Invalid param 'searchtype'");
- }
-
- $app = Factory::getApplication();
- $app->input->set('searchstring', $data['searchstring']);
- $app->input->set('searchtype', $data['searchtype']);
- $app->input->set('more', 0);
-
- $viewType = $this->app->getDocument()->getType();
- $viewName = $this->input->get('view', $this->default_view);
- $viewLayout = $this->input->get('layout', 'default', 'string');
-
- try
- {
- /** @var \Joomla\Component\Languages\Api\View\Strings\JsonapiView $view */
- $view = $this->getView(
- $viewName,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
-
- /** @var \Joomla\Component\Languages\Administrator\Model\StringsModel $model */
- $model = $this->getModel($this->contentType, '', ['ignore_request' => true]);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- // Push the model into the view (as default)
- $view->setModel($model, true);
-
- $view->document = $this->app->getDocument();
- $view->displayList();
-
- return $this;
- }
-
- /**
- * Refresh cache
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @throws \Exception
- * @since 4.0.0
- */
- public function refresh()
- {
- /** @var \Joomla\Component\Languages\Administrator\Model\StringsModel $model */
- $model = $this->getModel($this->contentType, '', ['ignore_request' => true]);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $result = $model->refresh();
-
- if ($result instanceof \Exception)
- {
- throw $result;
- }
-
- return $this;
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'strings';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'strings';
+
+ /**
+ * Search by languages constants
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @throws InvalidParameterException
+ * @since 4.0.0
+ */
+ public function search()
+ {
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+
+ if (!isset($data['searchstring']) || !\is_string($data['searchstring'])) {
+ throw new InvalidParameterException("Invalid param 'searchstring'");
+ }
+
+ if (!isset($data['searchtype']) || !\in_array($data['searchtype'], ['constant', 'value'])) {
+ throw new InvalidParameterException("Invalid param 'searchtype'");
+ }
+
+ $app = Factory::getApplication();
+ $app->input->set('searchstring', $data['searchstring']);
+ $app->input->set('searchtype', $data['searchtype']);
+ $app->input->set('more', 0);
+
+ $viewType = $this->app->getDocument()->getType();
+ $viewName = $this->input->get('view', $this->default_view);
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+
+ try {
+ /** @var \Joomla\Component\Languages\Api\View\Strings\JsonapiView $view */
+ $view = $this->getView(
+ $viewName,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ /** @var \Joomla\Component\Languages\Administrator\Model\StringsModel $model */
+ $model = $this->getModel($this->contentType, '', ['ignore_request' => true]);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ // Push the model into the view (as default)
+ $view->setModel($model, true);
+
+ $view->document = $this->app->getDocument();
+ $view->displayList();
+
+ return $this;
+ }
+
+ /**
+ * Refresh cache
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @throws \Exception
+ * @since 4.0.0
+ */
+ public function refresh()
+ {
+ /** @var \Joomla\Component\Languages\Administrator\Model\StringsModel $model */
+ $model = $this->getModel($this->contentType, '', ['ignore_request' => true]);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $result = $model->refresh();
+
+ if ($result instanceof \Exception) {
+ throw $result;
+ }
+
+ return $this;
+ }
}
diff --git a/api/components/com_languages/src/View/Languages/JsonapiView.php b/api/components/com_languages/src/View/Languages/JsonapiView.php
index 803a9896d22c0..9d7f03ffaa111 100644
--- a/api/components/com_languages/src/View/Languages/JsonapiView.php
+++ b/api/components/com_languages/src/View/Languages/JsonapiView.php
@@ -1,4 +1,5 @@
id = $item->lang_id;
- unset($item->lang->id);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->lang_id;
+ unset($item->lang->id);
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_languages/src/View/Overrides/JsonapiView.php b/api/components/com_languages/src/View/Overrides/JsonapiView.php
index eb23f85d65048..b346b117990fa 100644
--- a/api/components/com_languages/src/View/Overrides/JsonapiView.php
+++ b/api/components/com_languages/src/View/Overrides/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $id = $model->getState($model->getName() . '.id');
- $item = $this->prepareItem($model->getItem($id));
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ /** @var \Joomla\Component\Languages\Administrator\Model\OverrideModel $model */
+ $model = $this->getModel();
+ $id = $model->getState($model->getName() . '.id');
+ $item = $this->prepareItem($model->getItem($id));
- return parent::displayItem($item);
- }
- /**
- * Execute and display a template script.
- *
- * @param array|null $items Array of items
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayList(array $items = null)
- {
- /** @var \Joomla\Component\Languages\Administrator\Model\OverridesModel $model */
- $model = $this->getModel();
- $items = [];
+ return parent::displayItem($item);
+ }
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ /** @var \Joomla\Component\Languages\Administrator\Model\OverridesModel $model */
+ $model = $this->getModel();
+ $items = [];
- foreach ($model->getOverrides() as $key => $override)
- {
- $item = (object) [
- 'key' => $key,
- 'override' => $override,
- ];
+ foreach ($model->getOverrides() as $key => $override) {
+ $item = (object) [
+ 'key' => $key,
+ 'override' => $override,
+ ];
- $items[] = $this->prepareItem($item);
- }
+ $items[] = $this->prepareItem($item);
+ }
- return parent::displayList($items);
- }
+ return parent::displayList($items);
+ }
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- $item->id = $item->key;
- $item->value = $item->override;
- unset($item->key);
- unset($item->override);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->key;
+ $item->value = $item->override;
+ unset($item->key);
+ unset($item->override);
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_languages/src/View/Strings/JsonapiView.php b/api/components/com_languages/src/View/Strings/JsonapiView.php
index 73766eec29132..2d6d36e6b54a9 100644
--- a/api/components/com_languages/src/View/Strings/JsonapiView.php
+++ b/api/components/com_languages/src/View/Strings/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $result = $model->search();
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ /** @var \Joomla\Component\Languages\Administrator\Model\StringsModel $model */
+ $model = $this->getModel();
+ $result = $model->search();
- if ($result instanceof \Exception)
- {
- throw $result;
- }
+ if ($result instanceof \Exception) {
+ throw $result;
+ }
- $items = [];
+ $items = [];
- foreach ($result['results'] as $item)
- {
- $items[] = $this->prepareItem($item);
- }
+ foreach ($result['results'] as $item) {
+ $items[] = $this->prepareItem($item);
+ }
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
- if ($this->type === null)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CONTENT_TYPE_MISSING'), 400);
- }
+ if ($this->type === null) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CONTENT_TYPE_MISSING'), 400);
+ }
- $collection = (new Collection($items, new JoomlaSerializer($this->type)))
- ->fields([$this->type => $this->fieldsToRenderList]);
+ $collection = (new Collection($items, new JoomlaSerializer($this->type)))
+ ->fields([$this->type => $this->fieldsToRenderList]);
- // Set the data into the document and render it
- $this->document->setData($collection);
+ // Set the data into the document and render it
+ $this->document->setData($collection);
- return $this->document->render();
- }
+ return $this->document->render();
+ }
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- $item->id = $item->constant;
- unset($item->constant);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->constant;
+ unset($item->constant);
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_menus/src/Controller/ItemsController.php b/api/components/com_menus/src/Controller/ItemsController.php
index 44f5da57bab61..a958cd4ca322b 100644
--- a/api/components/com_menus/src/Controller/ItemsController.php
+++ b/api/components/com_menus/src/Controller/ItemsController.php
@@ -1,4 +1,5 @@
modelState->set('filter.client_id', $this->getClientIdFromInput());
-
- return parent::displayItem($id);
- }
-
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
-
- return parent::displayList();
- }
-
- /**
- * Method to add a new record.
- *
- * @return void
- *
- * @since 4.0.0
- * @throws NotAllowed
- * @throws \RuntimeException
- */
- public function add()
- {
- $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
-
- if (isset($data['menutype']))
- {
- $this->input->set('menutype', $data['menutype']);
- $this->input->set('com_menus.items.menutype', $data['menutype']);
- }
-
- isset($data['type']) && $this->input->set('type', $data['type']);
- isset($data['parent_id']) && $this->input->set('parent_id', $data['parent_id']);
- isset($data['link']) && $this->input->set('link', $data['link']);
-
- $this->input->set('id', '0');
-
- parent::add();
- }
-
- /**
- * Method to edit an existing record.
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function edit()
- {
- $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
-
- if (isset($data['menutype']))
- {
- $this->input->set('menutype', $data['menutype']);
- $this->input->set('com_menus.items.menutype', $data['menutype']);
- }
-
- isset($data['type']) && $this->input->set('type', $data['type']);
- isset($data['parent_id']) && $this->input->set('parent_id', $data['parent_id']);
- isset($data['link']) && $this->input->set('link', $data['link']);
-
- return parent::edit();
- }
-
- /**
- * Return menu items types
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function getTypes()
- {
- $viewType = $this->app->getDocument()->getType();
- $viewName = $this->input->get('view', $this->default_view);
- $viewLayout = $this->input->get('layout', 'default', 'string');
-
- try
- {
- /** @var JsonapiView $view */
- $view = $this->getView(
- $viewName,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
-
- /** @var ListModel $model */
- $model = $this->getModel('menutypes', '', ['ignore_request' => true]);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $model->setState('client_id', $this->getClientIdFromInput());
-
- $view->setModel($model, true);
-
- $view->document = $this->app->getDocument();
-
- $view->displayListTypes();
-
- return $this;
- }
-
- /**
- * Get client id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getClientIdFromInput()
- {
- return $this->input->exists('client_id') ?
- $this->input->get('client_id') : $this->input->post->get('client_id');
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'items';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'items';
+
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
+
+ return parent::displayItem($id);
+ }
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
+
+ return parent::displayList();
+ }
+
+ /**
+ * Method to add a new record.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws NotAllowed
+ * @throws \RuntimeException
+ */
+ public function add()
+ {
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+
+ if (isset($data['menutype'])) {
+ $this->input->set('menutype', $data['menutype']);
+ $this->input->set('com_menus.items.menutype', $data['menutype']);
+ }
+
+ isset($data['type']) && $this->input->set('type', $data['type']);
+ isset($data['parent_id']) && $this->input->set('parent_id', $data['parent_id']);
+ isset($data['link']) && $this->input->set('link', $data['link']);
+
+ $this->input->set('id', '0');
+
+ parent::add();
+ }
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function edit()
+ {
+ $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array');
+
+ if (isset($data['menutype'])) {
+ $this->input->set('menutype', $data['menutype']);
+ $this->input->set('com_menus.items.menutype', $data['menutype']);
+ }
+
+ isset($data['type']) && $this->input->set('type', $data['type']);
+ isset($data['parent_id']) && $this->input->set('parent_id', $data['parent_id']);
+ isset($data['link']) && $this->input->set('link', $data['link']);
+
+ return parent::edit();
+ }
+
+ /**
+ * Return menu items types
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function getTypes()
+ {
+ $viewType = $this->app->getDocument()->getType();
+ $viewName = $this->input->get('view', $this->default_view);
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+
+ try {
+ /** @var JsonapiView $view */
+ $view = $this->getView(
+ $viewName,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ /** @var ListModel $model */
+ $model = $this->getModel('menutypes', '', ['ignore_request' => true]);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $model->setState('client_id', $this->getClientIdFromInput());
+
+ $view->setModel($model, true);
+
+ $view->document = $this->app->getDocument();
+
+ $view->displayListTypes();
+
+ return $this;
+ }
+
+ /**
+ * Get client id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getClientIdFromInput()
+ {
+ return $this->input->exists('client_id') ?
+ $this->input->get('client_id') : $this->input->post->get('client_id');
+ }
}
diff --git a/api/components/com_menus/src/Controller/MenusController.php b/api/components/com_menus/src/Controller/MenusController.php
index b7424ff70b22f..4a72fb70ea26b 100644
--- a/api/components/com_menus/src/Controller/MenusController.php
+++ b/api/components/com_menus/src/Controller/MenusController.php
@@ -1,4 +1,5 @@
modelState->set('filter.client_id', $this->getClientIdFromInput());
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
- return parent::displayItem($id);
- }
+ return parent::displayItem($id);
+ }
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Get client id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getClientIdFromInput()
- {
- return $this->input->exists('client_id') ?
- $this->input->get('client_id') : $this->input->post->get('client_id');
- }
+ /**
+ * Get client id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getClientIdFromInput()
+ {
+ return $this->input->exists('client_id') ?
+ $this->input->get('client_id') : $this->input->post->get('client_id');
+ }
}
diff --git a/api/components/com_menus/src/View/Items/JsonapiView.php b/api/components/com_menus/src/View/Items/JsonapiView.php
index 02b5b8d0920f8..002a34fdf4a0a 100644
--- a/api/components/com_menus/src/View/Items/JsonapiView.php
+++ b/api/components/com_menus/src/View/Items/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
- $items = [];
-
- foreach ($model->getTypeOptions() as $type => $data)
- {
- $groupItems = [];
-
- foreach ($data as $item)
- {
- $item->id = implode('/', $item->request);
- $item->title = Text::_($item->title);
- $item->description = Text::_($item->description);
- $item->group = Text::_($type);
-
- $groupItems[] = $item;
- }
-
- $items = array_merge($items, $groupItems);
- }
-
- // Set up links for pagination
- $currentUrl = Uri::getInstance();
- $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
- $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
-
- $offset = $currentPageQuery['offset'];
- $limit = $currentPageQuery['limit'];
- $totalItemsCount = \count($items);
- $totalPagesAvailable = ceil($totalItemsCount / $limit);
-
- $items = array_splice($items, $offset, $limit);
-
- $firstPage = clone $currentUrl;
- $firstPageQuery = $currentPageQuery;
- $firstPageQuery['offset'] = 0;
- $firstPage->setVar('page', $firstPageQuery);
-
- $nextPage = clone $currentUrl;
- $nextPageQuery = $currentPageQuery;
- $nextOffset = $currentPageQuery['offset'] + $limit;
- $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
- $nextPage->setVar('page', $nextPageQuery);
-
- $previousPage = clone $currentUrl;
- $previousPageQuery = $currentPageQuery;
- $previousOffset = $currentPageQuery['offset'] - $limit;
- $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
- $previousPage->setVar('page', $previousPageQuery);
-
- $lastPage = clone $currentUrl;
- $lastPageQuery = $currentPageQuery;
- $lastPageQuery['offset'] = $totalPagesAvailable - $limit;
- $lastPage->setVar('page', $lastPageQuery);
-
- $collection = (new Collection($items, new JoomlaSerializer('menutypes')));
-
- // Set the data into the document and render it
- $this->document->addMeta('total-pages', $totalPagesAvailable)
- ->setData($collection)
- ->addLink('self', (string) $currentUrl)
- ->addLink('first', (string) $firstPage)
- ->addLink('next', (string) $nextPage)
- ->addLink('previous', (string) $previousPage)
- ->addLink('last', (string) $lastPage);
-
- return $this->document->render();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- if (\is_string($item->params))
- {
- $item->params = json_decode($item->params);
- }
-
- return parent::prepareItem($item);
- }
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderItem = [
+ 'id',
+ 'parent_id',
+ 'level',
+ 'lft',
+ 'rgt',
+ 'alias',
+ 'typeAlias',
+ 'menutype',
+ 'title',
+ 'note',
+ 'path',
+ 'link',
+ 'type',
+ 'published',
+ 'component_id',
+ 'checked_out',
+ 'checked_out_time',
+ 'browserNav',
+ 'access',
+ 'img',
+ 'template_style_id',
+ 'params',
+ 'home',
+ 'language',
+ 'client_id',
+ 'publish_up',
+ 'publish_down',
+ 'request',
+ 'associations',
+ 'menuordering',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderList = [
+ 'id',
+ 'menutype',
+ 'title',
+ 'alias',
+ 'note',
+ 'path',
+ 'link',
+ 'type',
+ 'parent_id',
+ 'level',
+ 'a.published',
+ 'component_id',
+ 'checked_out',
+ 'checked_out_time',
+ 'browserNav',
+ 'access',
+ 'img',
+ 'template_style_id',
+ 'params',
+ 'lft',
+ 'rgt',
+ 'home',
+ 'language',
+ 'client_id',
+ 'enabled',
+ 'publish_up',
+ 'publish_down',
+ 'published',
+ 'language_title',
+ 'language_image',
+ 'language_sef',
+ 'editor',
+ 'componentname',
+ 'access_level',
+ 'menutype_id',
+ 'menutype_title',
+ 'association',
+ 'name',
+ ];
+
+ /**
+ * Execute and display a list items types.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayListTypes()
+ {
+ /** @var \Joomla\Component\Menus\Administrator\Model\MenutypesModel $model */
+ $model = $this->getModel();
+ $items = [];
+
+ foreach ($model->getTypeOptions() as $type => $data) {
+ $groupItems = [];
+
+ foreach ($data as $item) {
+ $item->id = implode('/', $item->request);
+ $item->title = Text::_($item->title);
+ $item->description = Text::_($item->description);
+ $item->group = Text::_($type);
+
+ $groupItems[] = $item;
+ }
+
+ $items = array_merge($items, $groupItems);
+ }
+
+ // Set up links for pagination
+ $currentUrl = Uri::getInstance();
+ $currentPageDefaultInformation = ['offset' => 0, 'limit' => 20];
+ $currentPageQuery = $currentUrl->getVar('page', $currentPageDefaultInformation);
+
+ $offset = $currentPageQuery['offset'];
+ $limit = $currentPageQuery['limit'];
+ $totalItemsCount = \count($items);
+ $totalPagesAvailable = ceil($totalItemsCount / $limit);
+
+ $items = array_splice($items, $offset, $limit);
+
+ $firstPage = clone $currentUrl;
+ $firstPageQuery = $currentPageQuery;
+ $firstPageQuery['offset'] = 0;
+ $firstPage->setVar('page', $firstPageQuery);
+
+ $nextPage = clone $currentUrl;
+ $nextPageQuery = $currentPageQuery;
+ $nextOffset = $currentPageQuery['offset'] + $limit;
+ $nextPageQuery['offset'] = ($nextOffset > ($totalPagesAvailable * $limit)) ? $totalPagesAvailable - $limit : $nextOffset;
+ $nextPage->setVar('page', $nextPageQuery);
+
+ $previousPage = clone $currentUrl;
+ $previousPageQuery = $currentPageQuery;
+ $previousOffset = $currentPageQuery['offset'] - $limit;
+ $previousPageQuery['offset'] = $previousOffset >= 0 ? $previousOffset : 0;
+ $previousPage->setVar('page', $previousPageQuery);
+
+ $lastPage = clone $currentUrl;
+ $lastPageQuery = $currentPageQuery;
+ $lastPageQuery['offset'] = $totalPagesAvailable - $limit;
+ $lastPage->setVar('page', $lastPageQuery);
+
+ $collection = (new Collection($items, new JoomlaSerializer('menutypes')));
+
+ // Set the data into the document and render it
+ $this->document->addMeta('total-pages', $totalPagesAvailable)
+ ->setData($collection)
+ ->addLink('self', (string) $currentUrl)
+ ->addLink('first', (string) $firstPage)
+ ->addLink('next', (string) $nextPage)
+ ->addLink('previous', (string) $previousPage)
+ ->addLink('last', (string) $lastPage);
+
+ return $this->document->render();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ if (\is_string($item->params)) {
+ $item->params = json_decode($item->params);
+ }
+
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_menus/src/View/Menus/JsonapiView.php b/api/components/com_menus/src/View/Menus/JsonapiView.php
index 0750e5a37febd..6e8f21cb91172 100644
--- a/api/components/com_menus/src/View/Menus/JsonapiView.php
+++ b/api/components/com_menus/src/View/Menus/JsonapiView.php
@@ -1,4 +1,5 @@
id = $item->message_id;
- unset($item->message_id);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->message_id;
+ unset($item->message_id);
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_modules/src/Controller/ModulesController.php b/api/components/com_modules/src/Controller/ModulesController.php
index bb900e2bbb1dd..4bf825adf70ba 100644
--- a/api/components/com_modules/src/Controller/ModulesController.php
+++ b/api/components/com_modules/src/Controller/ModulesController.php
@@ -1,4 +1,5 @@
modelState->set('filter.client_id', $this->getClientIdFromInput());
-
- return parent::displayItem($id);
- }
-
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
-
- return parent::displayList();
- }
-
- /**
- * Return module items types
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function getTypes()
- {
- $viewType = $this->app->getDocument()->getType();
- $viewName = $this->input->get('view', $this->default_view);
- $viewLayout = $this->input->get('layout', 'default', 'string');
-
- try
- {
- /** @var JsonapiView $view */
- $view = $this->getView(
- $viewName,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
-
- /** @var SelectModel $model */
- $model = $this->getModel('select', '', ['ignore_request' => true]);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $model->setState('client_id', $this->getClientIdFromInput());
-
- $view->setModel($model, true);
-
- $view->document = $this->app->getDocument();
-
- $view->displayListTypes();
-
- return $this;
- }
-
- /**
- * Get client id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getClientIdFromInput()
- {
- return $this->input->exists('client_id') ?
- $this->input->get('client_id') : $this->input->post->get('client_id');
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'modules';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'modules';
+
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
+
+ return parent::displayItem($id);
+ }
+
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('filter.client_id', $this->getClientIdFromInput());
+
+ return parent::displayList();
+ }
+
+ /**
+ * Return module items types
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function getTypes()
+ {
+ $viewType = $this->app->getDocument()->getType();
+ $viewName = $this->input->get('view', $this->default_view);
+ $viewLayout = $this->input->get('layout', 'default', 'string');
+
+ try {
+ /** @var JsonapiView $view */
+ $view = $this->getView(
+ $viewName,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
+
+ /** @var SelectModel $model */
+ $model = $this->getModel('select', '', ['ignore_request' => true]);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $model->setState('client_id', $this->getClientIdFromInput());
+
+ $view->setModel($model, true);
+
+ $view->document = $this->app->getDocument();
+
+ $view->displayListTypes();
+
+ return $this;
+ }
+
+ /**
+ * Get client id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getClientIdFromInput()
+ {
+ return $this->input->exists('client_id') ?
+ $this->input->get('client_id') : $this->input->post->get('client_id');
+ }
}
diff --git a/api/components/com_modules/src/View/Modules/JsonapiView.php b/api/components/com_modules/src/View/Modules/JsonapiView.php
index 0cd3f3f65bc43..dbac55bd59039 100644
--- a/api/components/com_modules/src/View/Modules/JsonapiView.php
+++ b/api/components/com_modules/src/View/Modules/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ /** @var \Joomla\CMS\MVC\Model\AdminModel $model */
+ $model = $this->getModel();
- if ($item === null)
- {
- $item = $this->prepareItem($model->getItem());
- }
+ if ($item === null) {
+ $item = $this->prepareItem($model->getItem());
+ }
- if ($item->id === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($item->id === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- if ((int) $model->getState('client_id') !== $item->client_id)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ((int) $model->getState('client_id') !== $item->client_id) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- return parent::displayItem($item);
- }
+ return parent::displayItem($item);
+ }
- /**
- * Execute and display a list modules types.
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayListTypes()
- {
- /** @var SelectModel $model */
- $model = $this->getModel();
- $items = [];
+ /**
+ * Execute and display a list modules types.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayListTypes()
+ {
+ /** @var SelectModel $model */
+ $model = $this->getModel();
+ $items = [];
- foreach ($model->getItems() as $item)
- {
- $item->id = $item->extension_id;
- unset($item->extension_id);
+ foreach ($model->getItems() as $item) {
+ $item->id = $item->extension_id;
+ unset($item->extension_id);
- $items[] = $item;
- }
+ $items[] = $item;
+ }
- $this->fieldsToRenderList = ['id', 'name', 'module', 'xml', 'desc'];
+ $this->fieldsToRenderList = ['id', 'name', 'module', 'xml', 'desc'];
- return parent::displayList($items);
- }
+ return parent::displayList($items);
+ }
}
diff --git a/api/components/com_newsfeeds/src/Controller/FeedsController.php b/api/components/com_newsfeeds/src/Controller/FeedsController.php
index 2612252576691..23034a6fa64ec 100644
--- a/api/components/com_newsfeeds/src/Controller/FeedsController.php
+++ b/api/components/com_newsfeeds/src/Controller/FeedsController.php
@@ -1,4 +1,5 @@
type);
-
- foreach ($model->associations as $association)
- {
- $resources[] = (new Resource($association, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/newsfeeds/feeds/' . $association->id));
- }
-
- $collection = new Collection($resources, $serializer);
-
- return new Relationship($collection);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function category($model)
- {
- $serializer = new JoomlaSerializer('categories');
-
- $resource = (new Resource($model->catid, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/newfeeds/categories/' . $model->catid));
-
- return new Relationship($resource);
- }
-
- /**
- * Build category relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function createdBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->created_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
-
- return new Relationship($resource);
- }
-
- /**
- * Build editor relationship
- *
- * @param \stdClass $model Item model
- *
- * @return Relationship
- *
- * @since 4.0.0
- */
- public function modifiedBy($model)
- {
- $serializer = new JoomlaSerializer('users');
-
- $resource = (new Resource($model->modified_by, $serializer))
- ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
-
- return new Relationship($resource);
- }
+ use TagApiSerializerTrait;
+
+ /**
+ * Build content relationships by associations
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function languageAssociations($model)
+ {
+ $resources = [];
+
+ // @todo: This can't be hardcoded in the future?
+ $serializer = new JoomlaSerializer($this->type);
+
+ foreach ($model->associations as $association) {
+ $resources[] = (new Resource($association, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/newsfeeds/feeds/' . $association->id));
+ }
+
+ $collection = new Collection($resources, $serializer);
+
+ return new Relationship($collection);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function category($model)
+ {
+ $serializer = new JoomlaSerializer('categories');
+
+ $resource = (new Resource($model->catid, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/newfeeds/categories/' . $model->catid));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build category relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function createdBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->created_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->created_by));
+
+ return new Relationship($resource);
+ }
+
+ /**
+ * Build editor relationship
+ *
+ * @param \stdClass $model Item model
+ *
+ * @return Relationship
+ *
+ * @since 4.0.0
+ */
+ public function modifiedBy($model)
+ {
+ $serializer = new JoomlaSerializer('users');
+
+ $resource = (new Resource($model->modified_by, $serializer))
+ ->addLink('self', Route::link('site', Uri::root() . 'api/index.php/v1/users/' . $model->modified_by));
+
+ return new Relationship($resource);
+ }
}
diff --git a/api/components/com_newsfeeds/src/View/Feeds/JsonapiView.php b/api/components/com_newsfeeds/src/View/Feeds/JsonapiView.php
index 191b642735986..72518fc6c2980 100644
--- a/api/components/com_newsfeeds/src/View/Feeds/JsonapiView.php
+++ b/api/components/com_newsfeeds/src/View/Feeds/JsonapiView.php
@@ -1,4 +1,5 @@
serializer = new NewsfeedSerializer($config['contentType']);
- }
-
- parent::__construct($config);
- }
-
- /**
- * Execute and display a template script.
- *
- * @param object $item Item
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayItem($item = null)
- {
- if (Multilanguage::isEnabled())
- {
- $this->fieldsToRenderItem[] = 'languageAssociations';
- $this->relationship[] = 'languageAssociations';
- }
-
- return parent::displayItem();
- }
-
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- if (Multilanguage::isEnabled() && !empty($item->associations))
- {
- $associations = [];
-
- foreach ($item->associations as $language => $association)
- {
- $itemId = explode(':', $association)[0];
-
- $associations[] = (object) [
- 'id' => $itemId,
- 'language' => $language,
- ];
- }
-
- $item->associations = $associations;
- }
-
- if (!empty($item->tags->tags))
- {
- $tagsIds = explode(',', $item->tags->tags);
- $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
-
- $item->tags = array_combine($tagsIds, $tagsNames);
- }
- else
- {
- $item->tags = [];
- }
-
- return parent::prepareItem($item);
- }
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderItem = [
+ 'id',
+ 'category',
+ 'name',
+ 'alias',
+ 'link',
+ 'published',
+ 'numarticles',
+ 'cache_time',
+ 'checked_out',
+ 'checked_out_time',
+ 'ordering',
+ 'rtl',
+ 'access',
+ 'language',
+ 'params',
+ 'created',
+ 'created_by',
+ 'created_by_alias',
+ 'modified',
+ 'modified_by',
+ 'metakey',
+ 'metadesc',
+ 'metadata',
+ 'publish_up',
+ 'publish_down',
+ 'description',
+ 'version',
+ 'hits',
+ 'images',
+ 'tags',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderList = [
+ 'id',
+ 'name',
+ 'alias',
+ 'checked_out',
+ 'checked_out_time',
+ 'category',
+ 'numarticles',
+ 'cache_time',
+ 'created_by',
+ 'published',
+ 'access',
+ 'ordering',
+ 'language',
+ 'publish_up',
+ 'publish_down',
+ 'language_title',
+ 'language_image',
+ 'editor',
+ 'access_level',
+ 'category_title',
+ ];
+
+ /**
+ * The relationships the item has
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $relationship = [
+ 'category',
+ 'created_by',
+ 'modified_by',
+ 'tags',
+ ];
+
+ /**
+ * Constructor.
+ *
+ * @param array $config A named configuration array for object construction.
+ * contentType: the name (optional) of the content type to use for the serialization
+ *
+ * @since 4.0.0
+ */
+ public function __construct($config = [])
+ {
+ if (\array_key_exists('contentType', $config)) {
+ $this->serializer = new NewsfeedSerializer($config['contentType']);
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ if (Multilanguage::isEnabled()) {
+ $this->fieldsToRenderItem[] = 'languageAssociations';
+ $this->relationship[] = 'languageAssociations';
+ }
+
+ return parent::displayItem();
+ }
+
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ if (Multilanguage::isEnabled() && !empty($item->associations)) {
+ $associations = [];
+
+ foreach ($item->associations as $language => $association) {
+ $itemId = explode(':', $association)[0];
+
+ $associations[] = (object) [
+ 'id' => $itemId,
+ 'language' => $language,
+ ];
+ }
+
+ $item->associations = $associations;
+ }
+
+ if (!empty($item->tags->tags)) {
+ $tagsIds = explode(',', $item->tags->tags);
+ $tagsNames = $item->tagsHelper->getTagNames($tagsIds);
+
+ $item->tags = array_combine($tagsIds, $tagsNames);
+ } else {
+ $item->tags = [];
+ }
+
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_plugins/src/Controller/PluginsController.php b/api/components/com_plugins/src/Controller/PluginsController.php
index 67bfb198175b0..c0668a19a3dc2 100644
--- a/api/components/com_plugins/src/Controller/PluginsController.php
+++ b/api/components/com_plugins/src/Controller/PluginsController.php
@@ -1,4 +1,5 @@
input->getInt('id');
-
- if (!$recordId)
- {
- throw new Exception\ResourceNotFound(Text::_('JLIB_APPLICATION_ERROR_RECORD'), 404);
- }
-
- $data = json_decode($this->input->json->getRaw(), true);
-
- foreach ($data as $key => $value)
- {
- if (!\in_array($key, ['enabled', 'access', 'ordering']))
- {
- throw new InvalidParameterException("Invalid parameter {$key}.", 400);
- }
- }
-
- /** @var \Joomla\Component\Plugins\Administrator\Model\PluginModel $model */
- $model = $this->getModel(Inflector::singularize($this->contentType), '', ['ignore_request' => true]);
-
- if (!$model)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
- }
-
- $item = $model->getItem($recordId);
-
- if (!isset($item->extension_id))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
-
- $data['folder'] = $item->folder;
- $data['element'] = $item->element;
-
- $this->input->set('data', $data);
-
- return parent::edit();
- }
-
- /**
- * Plugin list view with filtering of data
- *
- * @return static A BaseController object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $apiFilterInfo = $this->input->get('filter', [], 'array');
- $filter = InputFilter::getInstance();
-
- if (\array_key_exists('element', $apiFilterInfo))
- {
- $this->modelState->set('filter.element', $filter->clean($apiFilterInfo['element'], 'STRING'));
- }
-
- if (\array_key_exists('status', $apiFilterInfo))
- {
- $this->modelState->set('filter.enabled', $filter->clean($apiFilterInfo['status'], 'INT'));
- }
-
- if (\array_key_exists('search', $apiFilterInfo))
- {
- $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
- }
-
- if (\array_key_exists('type', $apiFilterInfo))
- {
- $this->modelState->set('filter.folder', $filter->clean($apiFilterInfo['type'], 'STRING'));
- }
-
- return parent::displayList();
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'plugins';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ *
+ * @since 3.0
+ */
+ protected $default_view = 'plugins';
+
+ /**
+ * Method to edit an existing record.
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function edit()
+ {
+ $recordId = $this->input->getInt('id');
+
+ if (!$recordId) {
+ throw new Exception\ResourceNotFound(Text::_('JLIB_APPLICATION_ERROR_RECORD'), 404);
+ }
+
+ $data = json_decode($this->input->json->getRaw(), true);
+
+ foreach ($data as $key => $value) {
+ if (!\in_array($key, ['enabled', 'access', 'ordering'])) {
+ throw new InvalidParameterException("Invalid parameter {$key}.", 400);
+ }
+ }
+
+ /** @var \Joomla\Component\Plugins\Administrator\Model\PluginModel $model */
+ $model = $this->getModel(Inflector::singularize($this->contentType), '', ['ignore_request' => true]);
+
+ if (!$model) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE'));
+ }
+
+ $item = $model->getItem($recordId);
+
+ if (!isset($item->extension_id)) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
+
+ $data['folder'] = $item->folder;
+ $data['element'] = $item->element;
+
+ $this->input->set('data', $data);
+
+ return parent::edit();
+ }
+
+ /**
+ * Plugin list view with filtering of data
+ *
+ * @return static A BaseController object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $apiFilterInfo = $this->input->get('filter', [], 'array');
+ $filter = InputFilter::getInstance();
+
+ if (\array_key_exists('element', $apiFilterInfo)) {
+ $this->modelState->set('filter.element', $filter->clean($apiFilterInfo['element'], 'STRING'));
+ }
+
+ if (\array_key_exists('status', $apiFilterInfo)) {
+ $this->modelState->set('filter.enabled', $filter->clean($apiFilterInfo['status'], 'INT'));
+ }
+
+ if (\array_key_exists('search', $apiFilterInfo)) {
+ $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
+ }
+
+ if (\array_key_exists('type', $apiFilterInfo)) {
+ $this->modelState->set('filter.folder', $filter->clean($apiFilterInfo['type'], 'STRING'));
+ }
+
+ return parent::displayList();
+ }
}
diff --git a/api/components/com_plugins/src/View/Plugins/JsonapiView.php b/api/components/com_plugins/src/View/Plugins/JsonapiView.php
index c26d50b874951..f0c118d30da7e 100644
--- a/api/components/com_plugins/src/View/Plugins/JsonapiView.php
+++ b/api/components/com_plugins/src/View/Plugins/JsonapiView.php
@@ -1,4 +1,5 @@
id = $item->extension_id;
- unset($item->extension_id);
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ $item->id = $item->extension_id;
+ unset($item->extension_id);
- return $item;
- }
+ return $item;
+ }
}
diff --git a/api/components/com_privacy/src/Controller/ConsentsController.php b/api/components/com_privacy/src/Controller/ConsentsController.php
index 66949d2da7a09..97df5be753978 100644
--- a/api/components/com_privacy/src/Controller/ConsentsController.php
+++ b/api/components/com_privacy/src/Controller/ConsentsController.php
@@ -1,4 +1,5 @@
input->get('id', 0, 'int');
- }
-
- $this->input->set('model', $this->contentType);
-
- return parent::displayItem($id);
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'consents';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 3.0
+ */
+ protected $default_view = 'consents';
+
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ if ($id === null) {
+ $id = $this->input->get('id', 0, 'int');
+ }
+
+ $this->input->set('model', $this->contentType);
+
+ return parent::displayItem($id);
+ }
}
diff --git a/api/components/com_privacy/src/Controller/RequestsController.php b/api/components/com_privacy/src/Controller/RequestsController.php
index 772d1b0feeacd..c7716be2fb1bb 100644
--- a/api/components/com_privacy/src/Controller/RequestsController.php
+++ b/api/components/com_privacy/src/Controller/RequestsController.php
@@ -1,4 +1,5 @@
input->get('id', 0, 'int');
- }
+ /**
+ * Export request data
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function export($id = null)
+ {
+ if ($id === null) {
+ $id = $this->input->get('id', 0, 'int');
+ }
- $viewType = $this->app->getDocument()->getType();
- $viewName = $this->input->get('view', $this->default_view);
- $viewLayout = $this->input->get('layout', 'default', 'string');
+ $viewType = $this->app->getDocument()->getType();
+ $viewName = $this->input->get('view', $this->default_view);
+ $viewLayout = $this->input->get('layout', 'default', 'string');
- try
- {
- /** @var JsonapiView $view */
- $view = $this->getView(
- $viewName,
- $viewType,
- '',
- ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
- );
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
+ try {
+ /** @var JsonapiView $view */
+ $view = $this->getView(
+ $viewName,
+ $viewType,
+ '',
+ ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType]
+ );
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
- $model = $this->getModel('export');
+ $model = $this->getModel('export');
- try
- {
- $modelName = $model->getName();
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException($e->getMessage());
- }
+ try {
+ $modelName = $model->getName();
+ } catch (\Exception $e) {
+ throw new \RuntimeException($e->getMessage());
+ }
- $model->setState($modelName . '.request_id', $id);
+ $model->setState($modelName . '.request_id', $id);
- $view->setModel($model, true);
+ $view->setModel($model, true);
- $view->document = $this->app->getDocument();
- $view->export();
+ $view->document = $this->app->getDocument();
+ $view->export();
- return $this;
- }
+ return $this;
+ }
}
diff --git a/api/components/com_privacy/src/View/Consents/JsonapiView.php b/api/components/com_privacy/src/View/Consents/JsonapiView.php
index 60f8faddeb176..56d6cbc0e97aa 100644
--- a/api/components/com_privacy/src/View/Consents/JsonapiView.php
+++ b/api/components/com_privacy/src/View/Consents/JsonapiView.php
@@ -1,4 +1,5 @@
get('state')->get($this->getName() . '.id');
-
- if ($id === null)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ITEMID_MISSING'));
- }
-
- /** @var \Joomla\CMS\MVC\Model\ListModel $model */
- $model = $this->getModel();
- $displayItem = null;
-
- foreach ($model->getItems() as $item)
- {
- $item = $this->prepareItem($item);
-
- if ($item->id === $id)
- {
- $displayItem = $item;
- break;
- }
- }
-
- if ($displayItem === null)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
-
- // Check for errors.
- if (\count($errors = $this->get('Errors')))
- {
- throw new GenericDataException(implode("\n", $errors), 500);
- }
-
- if ($this->type === null)
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CONTENT_TYPE_MISSING'));
- }
-
- $serializer = new JoomlaSerializer($this->type);
- $element = (new Resource($displayItem, $serializer))
- ->fields([$this->type => $this->fieldsToRenderItem]);
-
- $this->document->setData($element);
- $this->document->addLink('self', Uri::current());
-
- return $this->document->render();
- }
+ /**
+ * The fields to render item in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderItem = [
+ 'id',
+ 'user_id',
+ 'state',
+ 'created',
+ 'subject',
+ 'body',
+ 'remind',
+ 'token',
+ 'username',
+ ];
+
+ /**
+ * The fields to render items in the documents
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $fieldsToRenderList = [
+ 'id',
+ 'user_id',
+ 'state',
+ 'created',
+ 'subject',
+ 'body',
+ 'remind',
+ 'token',
+ 'username',
+ ];
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ $id = $this->get('state')->get($this->getName() . '.id');
+
+ if ($id === null) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_ITEMID_MISSING'));
+ }
+
+ /** @var \Joomla\CMS\MVC\Model\ListModel $model */
+ $model = $this->getModel();
+ $displayItem = null;
+
+ foreach ($model->getItems() as $item) {
+ $item = $this->prepareItem($item);
+
+ if ($item->id === $id) {
+ $displayItem = $item;
+ break;
+ }
+ }
+
+ if ($displayItem === null) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
+
+ // Check for errors.
+ if (\count($errors = $this->get('Errors'))) {
+ throw new GenericDataException(implode("\n", $errors), 500);
+ }
+
+ if ($this->type === null) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CONTENT_TYPE_MISSING'));
+ }
+
+ $serializer = new JoomlaSerializer($this->type);
+ $element = (new Resource($displayItem, $serializer))
+ ->fields([$this->type => $this->fieldsToRenderItem]);
+
+ $this->document->setData($element);
+ $this->document->addLink('self', Uri::current());
+
+ return $this->document->render();
+ }
}
diff --git a/api/components/com_privacy/src/View/Requests/JsonapiView.php b/api/components/com_privacy/src/View/Requests/JsonapiView.php
index 65cdefc6bc8ea..b8d747c3c921a 100644
--- a/api/components/com_privacy/src/View/Requests/JsonapiView.php
+++ b/api/components/com_privacy/src/View/Requests/JsonapiView.php
@@ -1,4 +1,5 @@
getModel();
+ /**
+ * Execute and display a template script.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function export()
+ {
+ /** @var ExportModel $model */
+ $model = $this->getModel();
- $exportData = $model->collectDataForExportRequest();
+ $exportData = $model->collectDataForExportRequest();
- if ($exportData == false)
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ if ($exportData == false) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- $serializer = new JoomlaSerializer('export');
- $element = (new Resource($exportData, $serializer));
+ $serializer = new JoomlaSerializer('export');
+ $element = (new Resource($exportData, $serializer));
- $this->document->setData($element);
- $this->document->addLink('self', Uri::current());
+ $this->document->setData($element);
+ $this->document->addLink('self', Uri::current());
- return $this->document->render();
- }
+ return $this->document->render();
+ }
}
diff --git a/api/components/com_redirect/src/Controller/RedirectController.php b/api/components/com_redirect/src/Controller/RedirectController.php
index 58a1ba43b6ed1..fd54176ecdb26 100644
--- a/api/components/com_redirect/src/Controller/RedirectController.php
+++ b/api/components/com_redirect/src/Controller/RedirectController.php
@@ -1,4 +1,5 @@
modelState->set('client_id', $this->getClientIdFromInput());
+ /**
+ * Basic display of an item view
+ *
+ * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($id = null)
+ {
+ $this->modelState->set('client_id', $this->getClientIdFromInput());
- return parent::displayItem($id);
- }
+ return parent::displayItem($id);
+ }
- /**
- * Basic display of a list view
- *
- * @return static A \JControllerLegacy object to support chaining.
- *
- * @since 4.0.0
- */
- public function displayList()
- {
- $this->modelState->set('client_id', $this->getClientIdFromInput());
+ /**
+ * Basic display of a list view
+ *
+ * @return static A \JControllerLegacy object to support chaining.
+ *
+ * @since 4.0.0
+ */
+ public function displayList()
+ {
+ $this->modelState->set('client_id', $this->getClientIdFromInput());
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Method to allow extended classes to manipulate the data to be saved for an extension.
- *
- * @param array $data An array of input data.
- *
- * @return array
- *
- * @since 4.0.0
- * @throws InvalidParameterException
- */
- protected function preprocessSaveData(array $data): array
- {
- $data['client_id'] = $this->getClientIdFromInput();
+ /**
+ * Method to allow extended classes to manipulate the data to be saved for an extension.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ * @throws InvalidParameterException
+ */
+ protected function preprocessSaveData(array $data): array
+ {
+ $data['client_id'] = $this->getClientIdFromInput();
- // If we are updating an item the template is a readonly property based on the ID
- if ($this->input->getMethod() === 'PATCH')
- {
- if (\array_key_exists('template', $data))
- {
- throw new InvalidParameterException('The template property cannot be modified for an existing style');
- }
+ // If we are updating an item the template is a readonly property based on the ID
+ if ($this->input->getMethod() === 'PATCH') {
+ if (\array_key_exists('template', $data)) {
+ throw new InvalidParameterException('The template property cannot be modified for an existing style');
+ }
- $model = $this->getModel(Inflector::singularize($this->contentType), '', ['ignore_request' => true]);
- $data['template'] = $model->getItem($this->input->getInt('id'))->template;
- }
+ $model = $this->getModel(Inflector::singularize($this->contentType), '', ['ignore_request' => true]);
+ $data['template'] = $model->getItem($this->input->getInt('id'))->template;
+ }
- return $data;
- }
+ return $data;
+ }
- /**
- * Get client id from input
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getClientIdFromInput()
- {
- return $this->input->exists('client_id') ? $this->input->get('client_id') : $this->input->post->get('client_id');
- }
+ /**
+ * Get client id from input
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getClientIdFromInput()
+ {
+ return $this->input->exists('client_id') ? $this->input->get('client_id') : $this->input->post->get('client_id');
+ }
}
diff --git a/api/components/com_templates/src/View/Styles/JsonapiView.php b/api/components/com_templates/src/View/Styles/JsonapiView.php
index 880716be6f618..4bbe848a51d8b 100644
--- a/api/components/com_templates/src/View/Styles/JsonapiView.php
+++ b/api/components/com_templates/src/View/Styles/JsonapiView.php
@@ -1,4 +1,5 @@
client_id != $this->getModel()->getState('client_id'))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ if ($item->client_id != $this->getModel()->getState('client_id')) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/components/com_users/src/Controller/GroupsController.php b/api/components/com_users/src/Controller/GroupsController.php
index 8c49ef4b9cf73..33cf77b0b4ffd 100644
--- a/api/components/com_users/src/Controller/GroupsController.php
+++ b/api/components/com_users/src/Controller/GroupsController.php
@@ -1,4 +1,5 @@
name]))
- {
- !isset($data['com_fields']) && $data['com_fields'] = [];
-
- $data['com_fields'][$field->name] = $data[$field->name];
- unset($data[$field->name]);
- }
- }
-
- if (isset($data['password']) && $this->task !== 'add')
- {
- $data['password2'] = $data['password'];
- }
-
- return $data;
- }
-
- /**
- * User list view with filtering of data
- *
- * @return static A BaseController object to support chaining.
- *
- * @since 4.0.0
- * @throws InvalidParameterException
- */
- public function displayList()
- {
- $apiFilterInfo = $this->input->get('filter', [], 'array');
- $filter = InputFilter::getInstance();
-
- if (\array_key_exists('state', $apiFilterInfo))
- {
- $this->modelState->set('filter.state', $filter->clean($apiFilterInfo['state'], 'INT'));
- }
-
- if (\array_key_exists('active', $apiFilterInfo))
- {
- $this->modelState->set('filter.active', $filter->clean($apiFilterInfo['active'], 'INT'));
- }
-
- if (\array_key_exists('groupid', $apiFilterInfo))
- {
- $this->modelState->set('filter.group_id', $filter->clean($apiFilterInfo['groupid'], 'INT'));
- }
-
- if (\array_key_exists('search', $apiFilterInfo))
- {
- $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
- }
-
- if (\array_key_exists('registrationDateStart', $apiFilterInfo))
- {
- $registrationStartInput = $filter->clean($apiFilterInfo['registrationDateStart'], 'STRING');
- $registrationStartDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $registrationStartInput);
-
- if (!$registrationStartDate)
- {
- // Send the error response
- $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'registrationDateStart');
-
- throw new InvalidParameterException($error, 400, null, 'registrationDateStart');
- }
-
- $this->modelState->set('filter.registrationDateStart', $registrationStartDate);
- }
-
- if (\array_key_exists('registrationDateEnd', $apiFilterInfo))
- {
- $registrationEndInput = $filter->clean($apiFilterInfo['registrationDateEnd'], 'STRING');
- $registrationEndDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $registrationEndInput);
-
- if (!$registrationEndDate)
- {
- // Send the error response
- $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'registrationDateEnd');
- throw new InvalidParameterException($error, 400, null, 'registrationDateEnd');
- }
-
- $this->modelState->set('filter.registrationDateEnd', $registrationEndDate);
- }
- elseif (\array_key_exists('registrationDateStart', $apiFilterInfo)
- && !\array_key_exists('registrationDateEnd', $apiFilterInfo))
- {
- // If no end date specified the end date is now
- $this->modelState->set('filter.registrationDateEnd', new Date);
- }
-
- if (\array_key_exists('lastVisitDateStart', $apiFilterInfo))
- {
- $lastVisitStartInput = $filter->clean($apiFilterInfo['lastVisitDateStart'], 'STRING');
- $lastVisitStartDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $lastVisitStartInput);
-
- if (!$lastVisitStartDate)
- {
- // Send the error response
- $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'lastVisitDateStart');
- throw new InvalidParameterException($error, 400, null, 'lastVisitDateStart');
- }
-
- $this->modelState->set('filter.lastVisitStart', $lastVisitStartDate);
- }
-
- if (\array_key_exists('lastVisitDateEnd', $apiFilterInfo))
- {
- $lastVisitEndInput = $filter->clean($apiFilterInfo['lastVisitDateEnd'], 'STRING');
- $lastVisitEndDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $lastVisitEndInput);
-
- if (!$lastVisitEndDate)
- {
- // Send the error response
- $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'lastVisitDateEnd');
-
- throw new InvalidParameterException($error, 400, null, 'lastVisitDateEnd');
- }
-
- $this->modelState->set('filter.lastVisitEnd', $lastVisitEndDate);
- }
- elseif (\array_key_exists('lastVisitDateStart', $apiFilterInfo)
- && !\array_key_exists('lastVisitDateEnd', $apiFilterInfo))
- {
- // If no end date specified the end date is now
- $this->modelState->set('filter.lastVisitEnd', new Date);
- }
-
- return parent::displayList();
- }
+ /**
+ * The content type of the item.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $contentType = 'users';
+
+ /**
+ * The default view for the display method.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $default_view = 'users';
+
+ /**
+ * Method to allow extended classes to manipulate the data to be saved for an extension.
+ *
+ * @param array $data An array of input data.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function preprocessSaveData(array $data): array
+ {
+ foreach (FieldsHelper::getFields('com_users.user') as $field) {
+ if (isset($data[$field->name])) {
+ !isset($data['com_fields']) && $data['com_fields'] = [];
+
+ $data['com_fields'][$field->name] = $data[$field->name];
+ unset($data[$field->name]);
+ }
+ }
+
+ if (isset($data['password']) && $this->task !== 'add') {
+ $data['password2'] = $data['password'];
+ }
+
+ return $data;
+ }
+
+ /**
+ * User list view with filtering of data
+ *
+ * @return static A BaseController object to support chaining.
+ *
+ * @since 4.0.0
+ * @throws InvalidParameterException
+ */
+ public function displayList()
+ {
+ $apiFilterInfo = $this->input->get('filter', [], 'array');
+ $filter = InputFilter::getInstance();
+
+ if (\array_key_exists('state', $apiFilterInfo)) {
+ $this->modelState->set('filter.state', $filter->clean($apiFilterInfo['state'], 'INT'));
+ }
+
+ if (\array_key_exists('active', $apiFilterInfo)) {
+ $this->modelState->set('filter.active', $filter->clean($apiFilterInfo['active'], 'INT'));
+ }
+
+ if (\array_key_exists('groupid', $apiFilterInfo)) {
+ $this->modelState->set('filter.group_id', $filter->clean($apiFilterInfo['groupid'], 'INT'));
+ }
+
+ if (\array_key_exists('search', $apiFilterInfo)) {
+ $this->modelState->set('filter.search', $filter->clean($apiFilterInfo['search'], 'STRING'));
+ }
+
+ if (\array_key_exists('registrationDateStart', $apiFilterInfo)) {
+ $registrationStartInput = $filter->clean($apiFilterInfo['registrationDateStart'], 'STRING');
+ $registrationStartDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $registrationStartInput);
+
+ if (!$registrationStartDate) {
+ // Send the error response
+ $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'registrationDateStart');
+
+ throw new InvalidParameterException($error, 400, null, 'registrationDateStart');
+ }
+
+ $this->modelState->set('filter.registrationDateStart', $registrationStartDate);
+ }
+
+ if (\array_key_exists('registrationDateEnd', $apiFilterInfo)) {
+ $registrationEndInput = $filter->clean($apiFilterInfo['registrationDateEnd'], 'STRING');
+ $registrationEndDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $registrationEndInput);
+
+ if (!$registrationEndDate) {
+ // Send the error response
+ $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'registrationDateEnd');
+ throw new InvalidParameterException($error, 400, null, 'registrationDateEnd');
+ }
+
+ $this->modelState->set('filter.registrationDateEnd', $registrationEndDate);
+ } elseif (
+ \array_key_exists('registrationDateStart', $apiFilterInfo)
+ && !\array_key_exists('registrationDateEnd', $apiFilterInfo)
+ ) {
+ // If no end date specified the end date is now
+ $this->modelState->set('filter.registrationDateEnd', new Date());
+ }
+
+ if (\array_key_exists('lastVisitDateStart', $apiFilterInfo)) {
+ $lastVisitStartInput = $filter->clean($apiFilterInfo['lastVisitDateStart'], 'STRING');
+ $lastVisitStartDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $lastVisitStartInput);
+
+ if (!$lastVisitStartDate) {
+ // Send the error response
+ $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'lastVisitDateStart');
+ throw new InvalidParameterException($error, 400, null, 'lastVisitDateStart');
+ }
+
+ $this->modelState->set('filter.lastVisitStart', $lastVisitStartDate);
+ }
+
+ if (\array_key_exists('lastVisitDateEnd', $apiFilterInfo)) {
+ $lastVisitEndInput = $filter->clean($apiFilterInfo['lastVisitDateEnd'], 'STRING');
+ $lastVisitEndDate = Date::createFromFormat(\DateTimeInterface::RFC3339, $lastVisitEndInput);
+
+ if (!$lastVisitEndDate) {
+ // Send the error response
+ $error = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', 'lastVisitDateEnd');
+
+ throw new InvalidParameterException($error, 400, null, 'lastVisitDateEnd');
+ }
+
+ $this->modelState->set('filter.lastVisitEnd', $lastVisitEndDate);
+ } elseif (
+ \array_key_exists('lastVisitDateStart', $apiFilterInfo)
+ && !\array_key_exists('lastVisitDateEnd', $apiFilterInfo)
+ ) {
+ // If no end date specified the end date is now
+ $this->modelState->set('filter.lastVisitEnd', new Date());
+ }
+
+ return parent::displayList();
+ }
}
diff --git a/api/components/com_users/src/View/Groups/JsonapiView.php b/api/components/com_users/src/View/Groups/JsonapiView.php
index 34e70606616d0..9cd42b5772871 100644
--- a/api/components/com_users/src/View/Groups/JsonapiView.php
+++ b/api/components/com_users/src/View/Groups/JsonapiView.php
@@ -1,4 +1,5 @@
fieldsToRenderList[] = $field->name;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param array|null $items Array of items
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayList(array $items = null)
+ {
+ foreach (FieldsHelper::getFields('com_users.user') as $field) {
+ $this->fieldsToRenderList[] = $field->name;
+ }
- return parent::displayList();
- }
+ return parent::displayList();
+ }
- /**
- * Execute and display a template script.
- *
- * @param object $item Item
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function displayItem($item = null)
- {
- foreach (FieldsHelper::getFields('com_users.user') as $field)
- {
- $this->fieldsToRenderItem[] = $field->name;
- }
+ /**
+ * Execute and display a template script.
+ *
+ * @param object $item Item
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function displayItem($item = null)
+ {
+ foreach (FieldsHelper::getFields('com_users.user') as $field) {
+ $this->fieldsToRenderItem[] = $field->name;
+ }
- return parent::displayItem();
- }
+ return parent::displayItem();
+ }
- /**
- * Prepare item before render.
- *
- * @param object $item The model item
- *
- * @return object
- *
- * @since 4.0.0
- */
- protected function prepareItem($item)
- {
- if (empty($item->username))
- {
- throw new RouteNotFoundException('Item does not exist');
- }
+ /**
+ * Prepare item before render.
+ *
+ * @param object $item The model item
+ *
+ * @return object
+ *
+ * @since 4.0.0
+ */
+ protected function prepareItem($item)
+ {
+ if (empty($item->username)) {
+ throw new RouteNotFoundException('Item does not exist');
+ }
- foreach (FieldsHelper::getFields('com_users.user', $item, true) as $field)
- {
- $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
- }
+ foreach (FieldsHelper::getFields('com_users.user', $item, true) as $field) {
+ $item->{$field->name} = $field->apivalue ?? $field->rawvalue;
+ }
- return parent::prepareItem($item);
- }
+ return parent::prepareItem($item);
+ }
}
diff --git a/api/includes/app.php b/api/includes/app.php
index 50c934cd6c80c..9f10825f9f10b 100644
--- a/api/includes/app.php
+++ b/api/includes/app.php
@@ -1,4 +1,5 @@
alias('session', 'session.cli')
- ->alias('JSession', 'session.cli')
- ->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
- ->alias(\Joomla\Session\Session::class, 'session.cli')
- ->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
+ ->alias('JSession', 'session.cli')
+ ->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
+ ->alias(\Joomla\Session\Session::class, 'session.cli')
+ ->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
// Instantiate the application.
$app = $container->get(\Joomla\CMS\Application\ApiApplication::class);
diff --git a/api/includes/defines.php b/api/includes/defines.php
index 63922bd809819..6148b98c0969d 100644
--- a/api/includes/defines.php
+++ b/api/includes/defines.php
@@ -1,4 +1,5 @@
isInDevelopmentState())))
-{
- if (file_exists(JPATH_INSTALLATION . '/index.php'))
- {
- header('HTTP/1.1 500 Internal Server Error');
- echo json_encode(
- array('error' => 'You must install Joomla to use the API')
- );
-
- exit();
- }
- else
- {
- header('HTTP/1.1 500 Internal Server Error');
- echo json_encode(
- array('error' => 'No configuration file found and no installation code available. Exiting...')
- );
-
- exit;
- }
+if (
+ !file_exists(JPATH_CONFIGURATION . '/configuration.php')
+ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10)
+ || (file_exists(JPATH_INSTALLATION . '/index.php') && (false === (new Version())->isInDevelopmentState()))
+) {
+ if (file_exists(JPATH_INSTALLATION . '/index.php')) {
+ header('HTTP/1.1 500 Internal Server Error');
+ echo json_encode(
+ array('error' => 'You must install Joomla to use the API')
+ );
+
+ exit();
+ } else {
+ header('HTTP/1.1 500 Internal Server Error');
+ echo json_encode(
+ array('error' => 'No configuration file found and no installation code available. Exiting...')
+ );
+
+ exit;
+ }
}
// Pre-Load configuration. Don't remove the Output Buffering due to BOM issues, see JCode 26026
@@ -45,64 +44,59 @@
ob_end_clean();
// System configuration.
-$config = new JConfig;
+$config = new JConfig();
// Set the error_reporting
-switch ($config->error_reporting)
-{
- case 'default':
- case '-1':
- break;
+switch ($config->error_reporting) {
+ case 'default':
+ case '-1':
+ break;
- case 'none':
- case '0':
- error_reporting(0);
+ case 'none':
+ case '0':
+ error_reporting(0);
- break;
+ break;
- case 'simple':
- error_reporting(E_ERROR | E_WARNING | E_PARSE);
- ini_set('display_errors', 1);
+ case 'simple':
+ error_reporting(E_ERROR | E_WARNING | E_PARSE);
+ ini_set('display_errors', 1);
- break;
+ break;
- case 'maximum':
- case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
- error_reporting(E_ALL);
- ini_set('display_errors', 1);
+ case 'maximum':
+ case 'development': // <= Stays for backward compatibility, @TODO: can be removed in 5.0
+ error_reporting(E_ALL);
+ ini_set('display_errors', 1);
- break;
+ break;
- default:
- error_reporting($config->error_reporting);
- ini_set('display_errors', 1);
+ default:
+ error_reporting($config->error_reporting);
+ ini_set('display_errors', 1);
- break;
+ break;
}
define('JDEBUG', $config->debug);
// Check deprecation logging
-if (empty($config->log_deprecated))
-{
- // Reset handler for E_USER_DEPRECATED
- set_error_handler(null, E_USER_DEPRECATED);
-}
-else
-{
- // Make sure handler for E_USER_DEPRECATED is registered
- set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
+if (empty($config->log_deprecated)) {
+ // Reset handler for E_USER_DEPRECATED
+ set_error_handler(null, E_USER_DEPRECATED);
+} else {
+ // Make sure handler for E_USER_DEPRECATED is registered
+ set_error_handler(['Joomla\CMS\Exception\ExceptionHandler', 'handleUserDeprecatedErrors'], E_USER_DEPRECATED);
}
-if (JDEBUG || $config->error_reporting === 'maximum')
-{
- // Set new Exception handler with debug enabled
- $errorHandler->setExceptionHandler(
- [
- new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
- 'renderException',
- ]
- );
+if (JDEBUG || $config->error_reporting === 'maximum') {
+ // Set new Exception handler with debug enabled
+ $errorHandler->setExceptionHandler(
+ [
+ new \Symfony\Component\ErrorHandler\ErrorHandler(null, true),
+ 'renderException',
+ ]
+ );
}
/**
@@ -111,15 +105,12 @@
* We need to do this as high up the stack as we can, as the default in \Joomla\Utilities\IpHelper is to
* $allowIpOverride = true which is the wrong default for a generic site NOT behind a trusted proxy/load balancer.
*/
-if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1)
-{
- // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
- IpHelper::setAllowIpOverrides(true);
-}
-else
-{
- // We disable the allowing of IP overriding using headers by default.
- IpHelper::setAllowIpOverrides(false);
+if (property_exists($config, 'behind_loadbalancer') && $config->behind_loadbalancer == 1) {
+ // If Joomla is configured to be behind a trusted proxy/load balancer, allow HTTP Headers to override the REMOTE_ADDR
+ IpHelper::setAllowIpOverrides(true);
+} else {
+ // We disable the allowing of IP overriding using headers by default.
+ IpHelper::setAllowIpOverrides(false);
}
unset($config);
diff --git a/api/index.php b/api/index.php
index 0f65a63313ebd..08dac50c4a97b 100644
--- a/api/index.php
+++ b/api/index.php
@@ -1,4 +1,5 @@
sprintf('Joomla requires PHP version %s to run', JOOMLA_MINIMUM_PHP))
- );
+if (version_compare(PHP_VERSION, JOOMLA_MINIMUM_PHP, '<')) {
+ header('HTTP/1.1 500 Internal Server Error');
+ echo json_encode(
+ array('error' => sprintf('Joomla requires PHP version %s to run', JOOMLA_MINIMUM_PHP))
+ );
- return;
+ return;
}
/**
diff --git a/build/psr12/.editorconfig b/build/psr12/.editorconfig
new file mode 100644
index 0000000000000..34010d1b7e1dc
--- /dev/null
+++ b/build/psr12/.editorconfig
@@ -0,0 +1,16 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style end of lines and a blank line at the end of the file
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{js,json,scss,css,vue}]
+indent_size = 2
diff --git a/build/psr12/clean_errors.php b/build/psr12/clean_errors.php
new file mode 100644
index 0000000000000..257cd183d49f8
--- /dev/null
+++ b/build/psr12/clean_errors.php
@@ -0,0 +1,174 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+$tmpDir = dirname(__DIR__) . '/tmp/psr12';
+
+$cleaned = [];
+
+$json = file_get_contents($tmpDir . '/cleanup.json');
+
+$data = json_decode($json, JSON_OBJECT_AS_ARRAY);
+
+// Fixing the later issues in a file first should allow us to preserve the line value per error
+$data = array_reverse($data);
+
+foreach ($data as $error) {
+ $file = file_get_contents($error['file']);
+ switch ($error['cleanup']) {
+ // Remove defined JEXEC statement, PSR-12 doesn't allow functional and symbol code in the same file
+ case 'definedJEXEC':
+ $file = str_replace([
+ /**
+ * I know this looks silly but makes it more clear what's happening
+ * We remove the different types of execution check from files which
+ * only defines symbols (like classes).
+ *
+ * The order is important.
+ */
+ "\defined('_JEXEC') || die();",
+ "defined('_JEXEC') || die();",
+ "\defined('_JEXEC') || die;",
+ "defined('_JEXEC') || die;",
+ "\defined('_JEXEC') or die();",
+ "defined('_JEXEC') or die();",
+ "\defined('_JEXEC') or die;",
+ "defined('_JEXEC') or die;",
+ "\defined('JPATH_PLATFORM') or die();",
+ "defined('JPATH_PLATFORM') or die();",
+ "\defined('JPATH_PLATFORM') or die;",
+ "defined('JPATH_PLATFORM') or die;",
+ "\defined('JPATH_BASE') or die();",
+ "defined('JPATH_BASE') or die();",
+ "\defined('JPATH_BASE') or die;",
+ "defined('JPATH_BASE') or die;",
+ /**
+ * We have variants of comments in front of the 'defined die' statement
+ * which we would like to remove too.
+ *
+ * The order is important.
+ */
+ "// No direct access.",
+ "// No direct access",
+ "// no direct access",
+ "// Restrict direct access",
+ "// Protect from unauthorized access",
+ ], '', $file);
+ break;
+
+ // Not all files need a namespace
+ case 'MissingNamespace':
+ // We search for the end of the first doc block and add the exception for this file
+ $pos = strpos($file, ' */');
+ $file = substr_replace(
+ $file,
+ "\n * @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace\n",
+ $pos,
+ 0
+ );
+
+ break;
+ // Not all classes have to be camelcase
+ case 'ValidClassNameNotCamelCaps':
+ // We search for the end of the first doc block and add the exception for this file
+ $pos = strpos($file, ' */');
+ $file = substr_replace(
+ $file,
+ "\n * @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps\n",
+ $pos,
+ 0
+ );
+
+ break;
+
+ case 'ConstantVisibility':
+ // add public to const declaration if defined in a class
+ $fileContent = file($error['file']);
+ $fileContent[$error['line'] - 1] = substr_replace($fileContent[$error['line'] - 1], 'public ', $error['column'] - 1, 0);
+ $file = implode('', $fileContent);
+
+ break;
+
+ case 'SpaceAfterCloseBrace':
+ // We only move single comments (starting with //) to the next line
+
+ $fileContent = file($error['file']);
+
+ $lineNo = $error['line'];
+
+ // We skip blank lines
+ do {
+ $nextLine = ltrim($fileContent[$lineNo]);
+ if (empty($nextLine)) {
+ $lineNo = $lineNo + 1;
+ $nextLine = ltrim($fileContent[$lineNo]);
+ }
+ } while (empty($nextLine));
+
+ $sourceLineStartNo = $lineNo;
+ $sourceLineEndNo = $lineNo;
+ $found = false;
+
+ while (substr(ltrim($fileContent[$sourceLineEndNo]), 0, 2) === '//') {
+ $sourceLineEndNo++;
+ $found = true;
+ }
+
+ if ($sourceLineStartNo === $sourceLineEndNo) {
+ if (substr(ltrim($fileContent[$sourceLineStartNo]), 0, 2) === '/*') {
+ while (substr(ltrim($fileContent[$sourceLineEndNo]), 0, 2) !== '*/') {
+ $sourceLineEndNo++;
+ }
+ $sourceLineEndNo++;
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ echo "Unrecoverable error while running SpaceAfterCloseBrace cleanup";
+ var_dump($error['file'], $sourceLineStartNo, $sourceLineEndNo);
+ die(1);
+ }
+ $targetLineNo = $sourceLineEndNo + 1;
+
+ // Adjust the indentation to match the next line of code
+ for ($indent = 0; $indent <= strlen($fileContent[$targetLineNo]); $indent++) {
+ if ($fileContent[$targetLineNo][$indent] !== ' ') {
+ break;
+ }
+ }
+
+ $replace = [];
+ for ($i = $sourceLineStartNo; $i < $sourceLineEndNo; $i++) {
+ $newLine = ltrim($fileContent[$i]);
+ // Fix codeblocks not starting with /**
+ if (substr($newLine, 0, 2) === '/*') {
+ $newLine = "/**\n";
+ }
+
+ $localIndent = $indent;
+ if ($newLine[0] === '*') {
+ $localIndent++;
+ }
+ $replace[] = str_repeat(' ', $localIndent) . $newLine;
+ }
+ array_unshift($replace, $fileContent[$sourceLineEndNo]);
+
+ array_splice($fileContent, $sourceLineStartNo, count($replace), $replace);
+
+ $file = implode('', $fileContent);
+
+ break;
+ }
+
+ file_put_contents($error['file'], $file);
+ $cleaned[] = $error['file'] . ' ' . $error['cleanup'];
+}
+
+file_put_contents($tmpDir . '/cleaned.log', implode("\n", $cleaned));
diff --git a/build/psr12/convert_pull_requests.php b/build/psr12/convert_pull_requests.php
new file mode 100644
index 0000000000000..415ff07886450
--- /dev/null
+++ b/build/psr12/convert_pull_requests.php
@@ -0,0 +1,275 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+// Set defaults
+$scriptRoot = __DIR__;
+$repoRoot = false;
+$prNumber = false;
+$createPullRequest = true;
+$php = 'php';
+$git = 'git';
+$gh = 'gh';
+$checkPath = false;
+$ghRepo = 'joomla/joomla-cms';
+$baseBranches = '4.2-dev'; // '4.1-dev,4.2-dev,4.3-dev'; We only check for 4.2-dev
+
+$script = array_shift($argv);
+
+if (empty($argv)) {
+ echo <<] --repo=
+
+ Description:
+ The converter converts all open pull requests on github
+ to the PSR12 standard and pushes the changes back to github.
+
+ Flow:
+ * Load all pull requests from Github for the repository
+ $ghRepo
+ * Checkout each open pull request with the base branch matching
+ $baseBranches
+ * Merge into the checked out branch up to the psr12anchor tag
+ which includes the conversion script.
+ * Run the psr12_converter.php with the task "BRANCH"
+ * Merge up to the psr12final tag with strategy "OURS"
+ * Push the changes back to the PR or create a new PR if we
+ don't have commit rights to the repository
+
+ --pr:
+ Only convert the given github pull request id.
+
+ --repo:
+ The path to the repository root.
+
+ TEXT;
+ die(1);
+}
+
+foreach ($argv as $arg) {
+ if (substr($arg, 0, 2) === '--') {
+ $argi = explode('=', $arg, 2);
+ switch ($argi[0]) {
+ case '--pr':
+ $prNumber = $argi[1];
+ break;
+ case '--repo':
+ $repoRoot = $argi[1];
+ break;
+ }
+ } else {
+ $checkPath = $arg;
+ break;
+ }
+}
+
+if (!$repoRoot) {
+ die('You have to set the repository root! (--repo)');
+}
+
+$cmd = $git . ' -C "' . $scriptRoot . '" rev-parse --show-toplevel';
+$output = [];
+$scriptInRepo = false;
+$repoScript = '';
+exec($cmd, $output, $result);
+if ($result === 0) {
+ $scriptInRepo = true;
+ $repoScript = $output[0];
+}
+
+$cmd = $git . ' -C "' . $repoRoot . '" rev-parse --show-toplevel';
+$output = [];
+exec($cmd, $output, $result);
+if ($result !== 0) {
+ die($repoRoot . ' is not a git repository.');
+}
+
+$repoRoot = $output[0];
+
+if ($scriptInRepo && $repoRoot === $repoScript) {
+ die($script . ' must be located outside of the git repository');
+}
+
+echo "Changing to working directory: " . $repoRoot . "\n";
+chdir($repoRoot);
+
+echo "Validate gh client...\n";
+$cmd = $gh;
+$output = [];
+exec($cmd, $output, $result);
+if ($result !== 0) {
+ die('Github cli client not found. Please install the client first (https://cli.github.com)');
+}
+
+echo "Validate gh authentication...\n";
+$cmd = $gh . ' auth status';
+passthru($cmd, $result);
+if ($result !== 0) {
+ die('Please login with the github cli client first. (gh auth login)');
+}
+
+$fieldList = [
+ "number",
+ "author",
+ "baseRefName",
+ "headRefName",
+ "headRepository",
+ "headRepositoryOwner",
+ "isCrossRepository",
+ "maintainerCanModify",
+ "mergeStateStatus",
+ "mergeable",
+ "state",
+ "title",
+ "url",
+ "labels",
+];
+
+$branches = 'base:' . implode(' base:', explode(',', $baseBranches));
+
+
+if (!empty($prNumber)) {
+ echo "Retrieving Pull Request " . $prNumber . "...\n";
+ $cmd = $gh . ' pr view ' . $prNumber . ' --json ' . implode(',', $fieldList);
+} else {
+ echo "Retrieving Pull Request list...\n";
+ $cmd = $gh . ' pr list --limit 1000 --json ' . implode(',', $fieldList) . ' --search "is:pr is:open -label:psr12 ' . $branches . '"';
+}
+
+$output = [];
+exec($cmd, $output, $result);
+if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to retrieve PR list.');
+}
+
+$json = $output[0];
+
+if (!empty($prNumber)) {
+ $json = '[' . $json . ']';
+}
+
+$list = json_decode($json, true);
+
+echo "\nFound " . count($list) . " pull request(s).\n";
+
+foreach ($list as $pr) {
+ echo "Checkout #" . $pr['number'] . "\n";
+
+ $cmd = $gh . ' pr checkout ' . $pr['url'] . ' --force -b psr12/merge/' . $pr['number'];
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to checkout pr #' . $pr['number']);
+ }
+
+ echo "Upmerge to psr12anchor\n";
+
+ $cmd = $git . ' merge psr12anchor';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ echo 'Unable to upmerge to psr12anchor pr #' . $pr['number'] . "\n";
+ echo "Abort merge...\n";
+ $cmd = $git . ' merge --abort';
+ $output = [];
+ exec($cmd, $output, $result);
+ continue;
+ }
+
+ echo "Run PSR-12 converter script\n";
+
+ $cmd = $php . ' ' . $scriptRoot . '/psr12_converter.php --task=branch --repo="' . $repoRoot . '"';
+
+ passthru($cmd, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $result]);
+ die('Unable to convert to psr-12 pr #' . $pr['number']);
+ }
+
+ echo "Upmerge to psr12final\n";
+
+ $cmd = $git . ' merge --strategy=ort --strategy-option=ours psr12final';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $result]);
+ die('Unable to upmerge to psr-12 pr #' . $pr['number']);
+ }
+
+ if (!$createPullRequest && $pr['maintainerCanModify'] === true) {
+ echo "Push directly to PR branch\n";
+
+ $cmd = $git . ' push git@github.com:' . $pr['headRepositoryOwner']['login'] . '/' . $pr['headRepository']['name'] . '.git '
+ . 'psr12/merge/' . $pr['number'] . ':' . $pr['headRefName'];
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to directly push for pr #' . $pr['number']);
+ }
+
+ $cmd = $gh . ' pr comment ' . $pr['url'] . ' --body "This pull requests has been automatically converted to the PSR-12 coding standard."';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to create a comment for pr #' . $pr['number']);
+ }
+ } else {
+ echo "Create pull request\n";
+
+ $cmd = $git . ' push --force -u github HEAD';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to push to github for pr #' . $pr['number']);
+ }
+
+ $cmd = $gh . ' pr create --title "PSR-12 conversion" --body "This pull requests converts the branch to the PSR-12 coding standard." '
+ . '-R ' . $pr['headRepositoryOwner']['login'] . '/' . $pr['headRepository']['name'] . ' -B ' . $pr['headRefName'];
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to create pull request for pr #' . $pr['number']);
+ }
+
+ $cmd = $gh . ' pr comment ' . $pr['url']
+ . ' --body "A new pull request has been created automatically to convert this PR to the PSR-12 coding standard.'
+ . ' The pr can be found at ' . $output[0] . '"';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to create a comment for pr #' . $pr['number']);
+ }
+ }
+
+ // Set label
+ echo "Set psr12 label\n";
+
+ $cmd = $gh . ' pr edit ' . $pr['url'] . ' --add-label psr12';
+ $output = [];
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ var_dump([$cmd, $output, $result]);
+ die('Unable to set psr12 label for pr #' . $pr['number']);
+ }
+}
diff --git a/build/psr12/phpcs.joomla.report.php b/build/psr12/phpcs.joomla.report.php
new file mode 100644
index 0000000000000..c058aec96beb9
--- /dev/null
+++ b/build/psr12/phpcs.joomla.report.php
@@ -0,0 +1,316 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Reports;
+
+use PHP_CodeSniffer\Config;
+use PHP_CodeSniffer\Files\File;
+
+use function array_keys;
+use function array_merge;
+use function array_values;
+use function file_exists;
+use function file_get_contents;
+use function file_put_contents;
+use function json_encode;
+use function str_replace;
+
+use const JSON_OBJECT_AS_ARRAY;
+use const JSON_PRETTY_PRINT;
+
+
+class Joomla implements \PHP_CodeSniffer\Reports\Report
+{
+ private $tmpDir = __DIR__ . '/../tmp/psr12';
+
+ private $html = '';
+
+ private $preProcessing = [];
+
+ /**
+ * Generate a partial report for a single processed file.
+ *
+ * Function should return TRUE if it printed or stored data about the file
+ * and FALSE if it ignored the file. Returning TRUE indicates that the file and
+ * its data should be counted in the grand totals.
+ *
+ * @param array $report Prepared report data.
+ * @param \PHP_CodeSniffer\File $phpcsFile The file being reported on.
+ * @param bool $showSources Show sources?
+ * @param int $width Maximum allowed line width.
+ *
+ * @return bool
+ */
+ public function generateFileReport($report, File $phpcsFile, $showSources = false, $width = 80)
+ {
+ if ($report['errors'] === 0 && $report['warnings'] === 0) {
+ return false;
+ }
+
+ $template = [
+ 'headline' => $report['filename'],
+ 'text' => 'Errors: ' . $report['errors'] . ' Warnings: ' . $report['warnings'] . ' Fixable: ' . $report['fixable'],
+ ];
+
+ $this->html = <<
+ {$template['headline']}
+ {$template['text']}
+ HTML;
+
+ foreach ($report['messages'] as $line => $lineErrors) {
+ foreach ($lineErrors as $column => $colErrors) {
+ foreach ($colErrors as $error) {
+ $error['type'] = strtolower($error['type']);
+ if ($phpcsFile->config->encoding !== 'utf-8') {
+ $error['message'] = iconv($phpcsFile->config->encoding, 'utf-8', $error['message']);
+ }
+
+ $error['fixable'] = $error['fixable'] === true ? 'Yes' : 'No';
+
+ $this->html .= <<
+ Line: $line
+ Column: $column
+ Fixable: {$error['fixable']}
+ Severity: {$error['severity']}
+ Rule: {$error['source']}
+ {$error['message']}
+
+ HTML;
+ $this->prepareProcessing($report['filename'],$phpcsFile, $line, $column, $error);
+ }
+ }
+ }
+
+ $this->html .= <<
+ HTML;
+
+ $this->writeFile();
+
+ return true;
+ }
+
+ private function prepareProcessing($file, $phpcsFile, $line, $column, $error) {
+
+ switch ($error['source']) {
+ case 'PSR1.Files.SideEffects.FoundWithSymbols':
+ $fileContent = file_get_contents($file);
+
+ if (
+ strpos($fileContent, "defined('_JEXEC')") !== false
+ || strpos($fileContent, "defined('JPATH_PLATFORM')") !== false
+ || strpos($fileContent, "defined('JPATH_BASE')") !== false
+ ) {
+ $this->preProcessing[] = [
+ 'file' => $file,
+ 'line' => $line,
+ 'column' => $column,
+ 'cleanup' => 'definedJEXEC'
+ ];
+ } else {
+ $targetFile = $this->tmpDir . '/' . $error['source'] . '.txt';
+ $fileContent = '';
+ if (file_exists($targetFile)) {
+ $fileContent = file_get_contents($targetFile);
+ }
+
+ static $replace = null;
+
+ if ($replace === null) {
+ $replace = [
+ "\\" => '/',
+ dirname(dirname(__DIR__)) . '/' => '',
+ '.' => '\.',
+ ];
+ }
+
+ $fileContent .= " " . str_replace(array_keys($replace), $replace, $file) . " \n";
+ file_put_contents($targetFile, $fileContent);
+ }
+ break;
+
+ case 'PSR1.Classes.ClassDeclaration.MissingNamespace':
+ $this->preProcessing[] = [
+ 'file' => $file,
+ 'line' => $line,
+ 'column' => $column,
+ 'cleanup' => 'MissingNamespace'
+ ];
+ break;
+
+ case 'Squiz.Classes.ValidClassName.NotCamelCaps':
+ if (
+ strpos($file, 'localise') !== false
+ || strpos($file, 'recaptcha_invisible') !== false
+ ) {
+ $this->preProcessing[] = [
+ 'file' => $file,
+ 'line' => $line,
+ 'column' => $column,
+ 'cleanup' => 'ValidClassNameNotCamelCaps'
+ ];
+ }
+ break;
+
+ case 'Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace':
+ $this->preProcessing[] = [
+ 'file' => $file,
+ 'line' => $line,
+ 'column' => $column,
+ 'cleanup' => 'SpaceAfterCloseBrace'
+ ];
+ break;
+
+ case 'PSR12.Properties.ConstantVisibility.NotFound':
+ $this->preProcessing[] = [
+ 'file' => $file,
+ 'line' => $line,
+ 'column' => $column,
+ 'cleanup' => 'ConstantVisibility'
+ ];
+ break;
+
+ case 'PSR2.Classes.PropertyDeclaration.Underscore':
+ case 'PSR2.Methods.MethodDeclaration.Underscore':
+ case 'PSR1.Classes.ClassDeclaration.MultipleClasses':
+ case 'PSR1.Methods.CamelCapsMethodName.NotCamelCaps':
+
+ $targetFile = $this->tmpDir . '/' . $error['source'] . '.txt';
+ $fileContent = '';
+ if (file_exists($targetFile)) {
+ $fileContent = file_get_contents($targetFile);
+ }
+
+ static $replace = null;
+
+ if ($replace === null) {
+ $replace = [
+ "\\" => '/',
+ dirname(dirname(__DIR__)) . '/' => '',
+ '.' => '\.',
+ ];
+ }
+
+ $fileContent .= " " . str_replace(array_keys($replace), $replace, $file) . " \n";
+ file_put_contents($targetFile, $fileContent);
+ break;
+ }
+ }
+
+ /**
+ * Prints all violations for processed files, in a proprietary XML format.
+ *
+ * @param string $cachedData Any partial report data that was returned from
+ * generateFileReport during the run.
+ * @param int $totalFiles Total number of files processed during the run.
+ * @param int $totalErrors Total number of errors found during the run.
+ * @param int $totalWarnings Total number of warnings found during the run.
+ * @param int $totalFixable Total number of problems that can be fixed.
+ * @param bool $showSources Show sources?
+ * @param int $width Maximum allowed line width.
+ * @param bool $interactive Are we running in interactive mode?
+ * @param bool $toScreen Is the report being printed to screen?
+ *
+ * @return void
+ */
+ public function generate(
+ $cachedData,
+ $totalFiles,
+ $totalErrors,
+ $totalWarnings,
+ $totalFixable,
+ $showSources = false,
+ $width = 80,
+ $interactive = false,
+ $toScreen = true
+ ) {
+
+ $preprocessing = [];
+ if (file_exists($this->tmpDir .'/cleanup.json')) {
+ $preprocessing = json_decode(file_get_contents($this->tmpDir .'/cleanup.json'), JSON_OBJECT_AS_ARRAY);
+ }
+
+ $preprocessing = array_merge($this->preProcessing, $preprocessing);
+ file_put_contents($this->tmpDir .'/cleanup.json', json_encode($preprocessing, JSON_PRETTY_PRINT));
+ }
+
+ private function getTemplate($section)
+ {
+ $sections = [
+ 'header' => <<
+
+ Report
+
+
+
+
+
+
+
+
+
Check
+ HTML,
+ 'footer' => <<
+
+
+
+
+ HTML,
+ 'line' => <<
+
%HEADLINE%
+
%TEXT%
+
+
+ HTML
+ ];
+
+ return $sections[$section];
+ }
+
+ private function htmlAddBlock($headline, $text, $error)
+ {
+ $line = $this->getTemplate('line');
+
+ $replace = [
+ '%HEADLINE%' => $headline,
+ '%TEXT%' => $text,
+ '%ERROR%' => $error,
+ ];
+
+ $this->html .= str_replace(array_keys($replace), $replace, $line);
+ }
+
+ private function writeFile()
+ {
+ $file = $this->tmpDir . '/result.html';
+
+ if (file_exists($file)) {
+ $html = file_get_contents($file);
+ } else {
+ $html = $this->getTemplate('header');
+ $html .= '%PHPCS_NEXT_BLOCK% ';
+ $html .= $this->getTemplate('footer');
+ }
+
+ $html = str_replace('%PHPCS_NEXT_BLOCK% ', $this->html . '%PHPCS_NEXT_BLOCK% ', $html);
+
+ file_put_contents($this->tmpDir . '/result.html', $html);
+ }
+}
diff --git a/build/psr12/psr12_converter.php b/build/psr12/psr12_converter.php
new file mode 100644
index 0000000000000..79a644081ff8e
--- /dev/null
+++ b/build/psr12/psr12_converter.php
@@ -0,0 +1,292 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+// Set defaults
+$root = dirname(dirname(__DIR__));
+$php = 'php';
+$git = 'git';
+$checkPath = false;
+$tasks = [
+ 'CBF' => false,
+ 'CS' => false,
+ 'CLEAN' => false,
+ 'CMS' => false,
+ 'BRANCH' => false,
+];
+
+$script = array_shift($argv);
+
+if (empty($argv)) {
+ echo << [path]
+
+ Description:
+ The converter has several tasks which can be run separately.
+ You can combine them seperated by a comma (,).
+
+ --tasks:
+ * CBF
+ This task executes the PHP Code Beautifier and Fixer. It
+ does the heavy lifting making the code PSR-12 compatible.
+ (beware this tasks modifies many files)
+ * CS
+ This task executes the PHP Code Sniffer. It collects all
+ issues and generates a HTML report in build/tmp/psr12.
+ Also it generates a collection of issues which can't be
+ fixed by CBF. This information is saved as json in
+ build/tmp/psr12/cleanup.json. If this option is activated
+ the tmp directory is cleaned before running.
+ * CLEAN
+ This tasks loads the cleanup.json generated by the CS task
+ and changes the cms specific files. After completing this
+ task it re-runs the CBF and CS task.
+ * CMS
+ This tasks activates all other tasks and automatically
+ commits the changes after both CBF runs. Usually only
+ needed for the first cms conversion.
+ * BRANCH
+ This tasks updates all files changed by the current
+ branch compared to the psr12anchor tag. This allows
+ to update a create pull request.
+
+ --repo:
+ The path to the repository root.
+
+ Path:
+ Providing a path will only check the directories or files
+ specified. It's possible to add multiple files and folder
+ seperated by a comma (,).
+
+
+ TEXT;
+ die(1);
+}
+
+foreach ($argv as $arg) {
+ if (substr($arg, 0, 2) === '--') {
+ $argi = explode('=', $arg, 2);
+ switch ($argi[0]) {
+ case '--task':
+ foreach ($tasks as $task => $value) {
+ if (stripos($argi[1], $task) !== false) {
+ $tasks[$task] = true;
+ }
+ }
+ break;
+ case '--repo':
+ $root = $argi[1];
+ break;
+ }
+ } else {
+ $checkPath = $arg;
+ break;
+ }
+}
+
+$tmpDir = $root . '/build/tmp/psr12';
+
+if ($tasks['CMS']) {
+ $tasks['CBF'] = true;
+ $tasks['CS'] = true;
+ $tasks['CLEAN'] = true;
+}
+
+if ($tasks['BRANCH']) {
+ $tasks['CMS'] = true;
+ $tasks['CBF'] = true;
+ $tasks['CS'] = true;
+ $tasks['CLEAN'] = true;
+
+ $cmd = $git . ' --no-pager diff --name-only psr12anchor..HEAD';
+ exec($cmd, $output, $result);
+ if ($result !== 0) {
+ die('Unable to find changes for this branch');
+ }
+
+ foreach($output as $k => $line) {
+ if (substr($line, -4) !== '.php') {
+ unset($output[$k]);
+ }
+ }
+
+ $checkPath = implode(',', $output);
+ if (empty($checkPath)) {
+ die(0);
+ }
+}
+
+$items = [];
+if ($checkPath) {
+ $items = explode(',', $checkPath);
+} else {
+ $items[] = 'index.php';
+ $items[] = 'administrator/index.php';
+
+ $baseFolders = [
+ 'administrator/components',
+ 'administrator/includes',
+ 'administrator/language',
+ 'administrator/modules',
+ 'administrator/templates',
+ 'api',
+ 'cli',
+ 'components',
+ 'includes',
+ 'installation',
+ 'language',
+ 'layouts',
+ 'libraries',
+ 'modules',
+ 'plugins',
+ 'templates',
+ ];
+
+ foreach ($baseFolders as $folder) {
+ $dir = dir($root . '/' . $folder);
+ while (false !== ($entry = $dir->read())) {
+ if (($entry === ".") || ($entry === "..")) {
+ continue;
+ }
+ if (!is_dir($dir->path . '/' . $entry)) {
+ if (substr($entry, -4) !== '.php') {
+ continue;
+ }
+ }
+ if (
+ $folder === 'libraries'
+ && (
+ $entry === 'php-encryption'
+ || $entry === 'phpass'
+ || $entry === 'vendor'
+ )
+ ) {
+ continue;
+ }
+ $items[] = str_replace($root . '/', '', $dir->path) . '/' . $entry;
+ }
+ $dir->close();
+ }
+}
+$executedTasks = implode(
+ ',',
+ array_keys(
+ array_filter($tasks, function ($task) {
+ return $task;
+ })
+ )
+);
+$executedPaths = implode("\n", $items);
+
+echo <<
+
+ The Joomla CMS PSR-12 exceptions.
+
+
+ build/*
+ cache/*
+ docs/*
+ logs/*
+ media/*
+ node_modules/*
+ tmp/*
+ tests/*
+
+
+ libraries/php-encryption/*
+ libraries/phpass/*
+ libraries/vendor/*
+
+ stubs.php
+ configuration.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+ administrator/components/com_banners/src/Controller/BannersController\.php
+
+
+
+ administrator/components/com_banners/src/Model/DownloadModel\.php
+ administrator/components/com_banners/src/Table/BannerTable\.php
+ administrator/components/com_banners/src/Table/ClientTable\.php
+ administrator/components/com_cache/src/Model/CacheModel\.php
+ administrator/components/com_contact/src/Table/ContactTable\.php
+ administrator/components/com_fields/src/Table/FieldTable\.php
+ administrator/components/com_fields/src/Table/GroupTable\.php
+ administrator/components/com_finder/src/Table/FilterTable\.php
+ administrator/components/com_finder/src/Table/LinkTable\.php
+ administrator/components/com_installer/src/Model/DatabaseModel\.php
+ administrator/components/com_installer/src/Model/InstallModel\.php
+ administrator/components/com_installer/src/Table/UpdatesiteTable\.php
+ administrator/components/com_mails/src/Table/TemplateTable\.php
+ administrator/components/com_menus/src/Helper/MenusHelper\.php
+ administrator/components/com_menus/src/Model/MenuModel\.php
+ administrator/components/com_newsfeeds/src/Table/NewsfeedTable\.php
+ administrator/components/com_plugins/src/Model/PluginModel\.php
+ administrator/components/com_privacy/src/Table/RequestTable\.php
+ administrator/components/com_scheduler/src/Table/TaskTable\.php
+ administrator/components/com_tags/src/Table/TagTable\.php
+ administrator/components/com_templates/src/Model/StyleModel\.php
+ administrator/components/com_users/src/Model/UserModel\.php
+ administrator/components/com_users/src/Table/NoteTable\.php
+ administrator/components/com_workflow/src/Table/StageTable\.php
+ administrator/components/com_workflow/src/Table/TransitionTable\.php
+ administrator/components/com_workflow/src/Table/WorkflowTable\.php
+ components/com_banners/src/Model/BannerModel\.php
+ components/com_contact/src/Model/CategoriesModel\.php
+ components/com_contact/src/Model/CategoryModel\.php
+ components/com_contact/src/Model/ContactModel\.php
+ components/com_content/src/Model/ArchiveModel\.php
+ components/com_content/src/Model/ArticleModel\.php
+ components/com_content/src/Model/CategoriesModel\.php
+ components/com_content/src/Model/CategoryModel\.php
+ components/com_content/src/Model/FeaturedModel\.php
+ components/com_newsfeeds/src/Model/CategoriesModel\.php
+ components/com_newsfeeds/src/Model/CategoryModel\.php
+ components/com_newsfeeds/src/Model/NewsfeedModel\.php
+ components/com_tags/src/Model/TagsModel\.php
+ installation/src/View/Preinstall/HtmlView\.php
+ libraries/src/Adapter/Adapter\.php
+ libraries/src/Application/ApplicationHelper\.php
+ libraries/src/Cache/Cache\.php
+ libraries/src/Cache/CacheStorage\.php
+ libraries/src/Cache/Controller/OutputController\.php
+ libraries/src/Cache/Controller/PageController\.php
+ libraries/src/Cache/Storage/FileStorage\.php
+ libraries/src/Cache/Storage/MemcachedStorage\.php
+ libraries/src/Cache/Storage/RedisStorage\.php
+ libraries/src/Categories/Categories\.php
+ libraries/src/Categories/CategoryNode\.php
+ libraries/src/Client/FtpClient\.php
+ libraries/src/Document/Document\.php
+ libraries/src/Document/DocumentRenderer\.php
+ libraries/src/Document/ErrorDocument\.php
+ libraries/src/Document/HtmlDocument\.php
+ libraries/src/Document/JsonDocument\.php
+ libraries/src/Document/OpensearchDocument\.php
+ libraries/src/Document/Renderer/Feed/AtomRenderer\.php
+ libraries/src/Document/Renderer/Feed/RssRenderer\.php
+ libraries/src/Editor/Editor\.php
+ libraries/src/Encrypt/Totp\.php
+ libraries/src/Form/Field/CaptchaField\.php
+ libraries/src/Input/Json\.php
+ libraries/src/MVC/Model/DatabaseAwareTrait\.php
+ libraries/src/MVC/Model/FormBehaviorTrait\.php
+ libraries/src/MVC/Model/ItemModel\.php
+ libraries/src/MVC/Model/StateBehaviorTrait\.php
+ libraries/src/MVC/View/AbstractView\.php
+ libraries/src/MVC/View/HtmlView\.php
+ libraries/src/MVC/View/JsonView\.php
+ libraries/src/Object/CMSObject\.php
+ libraries/src/Plugin/CMSPlugin\.php
+ libraries/src/Router/Route\.php
+ libraries/src/Table/Category\.php
+ libraries/src/Table/Content\.php
+ libraries/src/Table/CoreContent\.php
+ libraries/src/Table/Extension\.php
+ libraries/src/Table/Menu\.php
+ libraries/src/Table/Module\.php
+ libraries/src/Table/Nested\.php
+ libraries/src/Table/Table\.php
+ libraries/src/Table/Update\.php
+ libraries/src/Table/User\.php
+ libraries/src/Toolbar/Toolbar\.php
+ libraries/src/Tree/ImmutableNodeTrait\.php
+ libraries/src/Updater/UpdateAdapter\.php
+ libraries/src/User/User\.php
+ plugins/editors/tinymce/tinymce\.php
+ plugins/system/cache/cache\.php
+
+
+
+ administrator/components/com_content/src/Service/HTML/Icon\.php
+ administrator/components/com_installer/src/Controller/InstallController\.php
+ administrator/components/com_installer/src/Model/DiscoverModel\.php
+ administrator/components/com_installer/src/Model/WarningsModel\.php
+ administrator/components/com_users/src/Service/HTML/Users\.php
+ components/com_content/helpers/icon\.php
+ components/com_content/helpers/icon\.php
+ libraries/src/Filesystem/Stream\.php
+ libraries/src/Filesystem/Streams/StreamString\.php
+ libraries/src/Installer/Adapter/ComponentAdapter\.php
+ libraries/src/Installer/Adapter/LanguageAdapter\.php
+ libraries/src/Installer/Adapter/ModuleAdapter\.php
+ libraries/src/Installer/Installer\.php
+ libraries/src/Installer/InstallerAdapter\.php
+ libraries/src/Language/Transliterate\.php
+ libraries/src/Mail/Mail\.php
+ libraries/src/Pagination/Pagination\.php
+ libraries/src/Toolbar/ToolbarHelper\.php
+ libraries/src/Utility/BufferStreamHandler\.php
+
+
+
+
+ libraries/cms\.php
+ libraries/loader\.php
+
+
+ administrator/components/com_fields/src/Model/FieldsModel\.php
+ administrator/components/com_fields/src/Model/GroupsModel\.php
+ administrator/components/com_fields/src/Table/FieldTable\.php
+ administrator/components/com_fields/src/Table/GroupTable\.php
+ administrator/components/com_finder/src/Model/MapsModel\.php
+ administrator/components/com_installer/src/Model/InstallerModel\.php
+ administrator/components/com_installer/src/Model/InstallModel\.php
+ administrator/components/com_installer/src/Model/LanguagesModel\.php
+ administrator/components/com_installer/src/Model/UpdateModel\.php
+ administrator/components/com_login/src/Model/LoginModel\.php
+ administrator/components/com_modules/src/Model/ModulesModel\.php
+ administrator/components/com_plugins/src/Model/PluginsModel\.php
+ administrator/components/com_scheduler/src/Model/TasksModel\.php
+ administrator/components/com_scheduler/src/Table/TaskTable\.php
+ administrator/components/com_users/src/Model/UsersModel\.php
+ administrator/components/com_workflow/src/Table/StageTable\.php
+ administrator/components/com_workflow/src/Table/TransitionTable\.php
+ administrator/components/com_workflow/src/Table/WorkflowTable\.php
+ api/components/com_contact/src/Controller/ContactController\.php
+ components/com_config/src/View/Config/HtmlView\.php
+ components/com_config/src/View/Modules/HtmlView\.php
+ components/com_config/src/View/Templates/HtmlView\.php
+ components/com_contact/src/Controller/ContactController\.php
+ components/com_contact/src/View/Contact/HtmlView\.php
+ components/com_contact/src/View/Featured/HtmlView\.php
+ components/com_contact/src/View/Form/HtmlView\.php
+ components/com_content/src/Model/CategoryModel\.php
+ components/com_content/src/View/Archive/HtmlView\.php
+ components/com_content/src/View/Article/HtmlView\.php
+ components/com_content/src/View/Featured/HtmlView\.php
+ components/com_content/src/View/Form/HtmlView\.php
+ components/com_newsfeeds/src/View/Newsfeed/HtmlView\.php
+ components/com_tags/src/Helper/RouteHelper\.php
+ components/com_tags/src/View/Tag/HtmlView\.php
+ components/com_tags/src/View/Tags/HtmlView\.php
+ installation/src/Form/Field/Installation/LanguageField\.php
+ libraries/src/Cache/Cache\.php
+ libraries/src/Cache/CacheStorage\.php
+ libraries/src/Cache/Controller/CallbackController\.php
+ libraries/src/Cache/Controller/PageController\.php
+ libraries/src/Cache/Controller/ViewController\.php
+ libraries/src/Cache/Storage/FileStorage\.php
+ libraries/src/Cache/Storage/MemcachedStorage\.php
+ libraries/src/Captcha/Captcha\.php
+ libraries/src/Categories/Categories\.php
+ libraries/src/Client/FtpClient\.php
+ libraries/src/Document/Document\.php
+ libraries/src/Document/DocumentRenderer\.php
+ libraries/src/Document/HtmlDocument\.php
+ libraries/src/Editor/Editor\.php
+ libraries/src/Encrypt/Base32\.php
+ libraries/src/Environment/Browser\.php
+ libraries/src/Feed/FeedFactory\.php
+ libraries/src/Filesystem/Folder\.php
+ libraries/src/Filesystem/Stream\.php
+ libraries/src/Filesystem/Support/StringController\.php
+ libraries/src/HTML/Helpers/Grid\.php
+ libraries/src/Installer/Adapter/ComponentAdapter\.php
+ libraries/src/Installer/Adapter/LanguageAdapter\.php
+ libraries/src/Installer/Adapter/ModuleAdapter\.php
+ libraries/src/Installer/Adapter/PackageAdapter\.php
+ libraries/src/MVC/Model/BaseDatabaseModel\.php
+ libraries/src/MVC/Model/LegacyModelLoaderTrait\.php
+ libraries/src/MVC/Model/ListModel\.php
+ libraries/src/MVC/View/HtmlView\.php
+ libraries/src/Pagination/Pagination\.php
+ libraries/src/Table/Category\.php
+ libraries/src/Table/Category\.php
+ libraries/src/Table/Content\.php
+ libraries/src/Table/Language\.php
+ libraries/src/Table/MenuType\.php
+ libraries/src/Table/Module\.php
+ libraries/src/Table/Nested\.php
+ libraries/src/Table/Table\.php
+ libraries/src/Toolbar/Button/ConfirmButton\.php
+ libraries/src/Toolbar/Button/HelpButton\.php
+ libraries/src/Toolbar/Button/PopupButton\.php
+ libraries/src/Toolbar/Button/StandardButton\.php
+ libraries/src/Updater/Adapter/CollectionAdapter\.php
+ libraries/src/Updater/Adapter/ExtensionAdapter\.php
+ libraries/src/Updater/Update\.php
+ libraries/src/Updater/UpdateAdapter\.php
+ modules/mod_articles_category/src/Helper/ArticlesCategoryHelper\.php
+ plugins/content/emailcloak/emailcloak\.php
+ plugins/content/joomla/joomla\.php
+ plugins/content/loadmodule/loadmodule\.php
+ plugins/content/pagebreak/pagebreak\.php
+ plugins/editors/none/none\.php
+ plugins/user/joomla/joomla\.php
+
+
+
+
+ administrator/components/com_joomlaupdate/finalisation\.php
+
+
+
+
+ administrator/components/com_installer/src/Model/DatabaseModel\.php
+ libraries/src/Client/FtpClient\.php
+ libraries/src/Filesystem/Path\.php
+ libraries/src/Filesystem/Streams/StreamString\.php
+
+
+ index\.php
+ administrator/index\.php
+ administrator/components/com_joomlaupdate/extract\.php
+ administrator/components/com_joomlaupdate/restore_finalisation\.php
+ administrator/includes/app\.php
+ administrator/includes/defines\.php
+ administrator/includes/framework\.php
+ api/includes/app\.php
+ api/includes/defines\.php
+ api/includes/framework\.php
+ api/index\.php
+ cli/joomla\.php
+ includes/app\.php
+ includes/defines\.php
+ includes/framework\.php
+ installation/includes/app\.php
+ installation/includes/defines\.php
+ installation/index\.php
+ libraries/cms\.php
+ libraries/bootstrap\.php
+ libraries/import\.php
+ libraries/import\.legacy\.php
+ libraries/loader\.php
+
+
diff --git a/cli/joomla.php b/cli/joomla.php
index fb078e1265d01..6b5bea7d05b57 100644
--- a/cli/joomla.php
+++ b/cli/joomla.php
@@ -1,4 +1,5 @@
alias('session', 'session.cli')
- ->alias('JSession', 'session.cli')
- ->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
- ->alias(\Joomla\Session\Session::class, 'session.cli')
- ->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
+ ->alias('JSession', 'session.cli')
+ ->alias(\Joomla\CMS\Session\Session::class, 'session.cli')
+ ->alias(\Joomla\Session\Session::class, 'session.cli')
+ ->alias(\Joomla\Session\SessionInterface::class, 'session.cli');
$app = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Console\Application::class);
\Joomla\CMS\Factory::$application = $app;
diff --git a/components/com_ajax/ajax.php b/components/com_ajax/ajax.php
index 1b8fcc87b5718..20674d4be208e 100644
--- a/components/com_ajax/ajax.php
+++ b/components/com_ajax/ajax.php
@@ -1,4 +1,5 @@
get('module'))
-{
- $module = $input->get('module');
- $table = Table::getInstance('extension');
- $moduleId = $table->find(array('type' => 'module', 'element' => 'mod_' . $module));
-
- if ($moduleId && $table->load($moduleId) && $table->enabled)
- {
- $helperFile = JPATH_BASE . '/modules/mod_' . $module . '/helper.php';
-
- if (strpos($module, '_'))
- {
- $parts = explode('_', $module);
- }
- elseif (strpos($module, '-'))
- {
- $parts = explode('-', $module);
- }
-
- if ($parts)
- {
- $class = 'Mod';
-
- foreach ($parts as $part)
- {
- $class .= ucfirst($part);
- }
-
- $class .= 'Helper';
- }
- else
- {
- $class = 'Mod' . ucfirst($module) . 'Helper';
- }
-
- $method = $input->get('method') ?: 'get';
-
- $moduleInstance = $app->bootModule('mod_' . $module, $app->getName());
-
- if ($moduleInstance instanceof \Joomla\CMS\Helper\HelperFactoryInterface && $helper = $moduleInstance->getHelper(substr($class, 3)))
- {
- $results = method_exists($helper, $method . 'Ajax') ? $helper->{$method . 'Ajax'}() : null;
- }
-
- if ($results === null && is_file($helperFile))
- {
- JLoader::register($class, $helperFile);
-
- if (method_exists($class, $method . 'Ajax'))
- {
- // Load language file for module
- $basePath = JPATH_BASE;
- $lang = Factory::getLanguage();
- $lang->load('mod_' . $module, $basePath)
- || $lang->load('mod_' . $module, $basePath . '/modules/mod_' . $module);
-
- try
- {
- $results = call_user_func($class . '::' . $method . 'Ajax');
- }
- catch (Exception $e)
- {
- $results = $e;
- }
- }
- // Method does not exist
- else
- {
- $results = new LogicException(Text::sprintf('COM_AJAX_METHOD_NOT_EXISTS', $method . 'Ajax'), 404);
- }
- }
- // The helper file does not exist
- elseif ($results === null)
- {
- $results = new RuntimeException(Text::sprintf('COM_AJAX_FILE_NOT_EXISTS', 'mod_' . $module . '/helper.php'), 404);
- }
- }
- // Module is not published, you do not have access to it, or it is not assigned to the current menu item
- else
- {
- $results = new LogicException(Text::sprintf('COM_AJAX_MODULE_NOT_ACCESSIBLE', 'mod_' . $module), 404);
- }
-}
-/*
- * Plugin support by default is based on the "Ajax" plugin group.
- * An optional 'group' variable can be passed via the URL.
- *
- * The plugin event triggered is onAjaxFoo, where 'foo' is
- * the value of the 'plugin' variable passed via the URL
- * (i.e. index.php?option=com_ajax&plugin=foo)
- *
- */
-elseif ($input->get('plugin'))
-{
- $group = $input->get('group', 'ajax');
- PluginHelper::importPlugin($group);
- $plugin = ucfirst($input->get('plugin'));
-
- try
- {
- $results = Factory::getApplication()->triggerEvent('onAjax' . $plugin);
- }
- catch (Exception $e)
- {
- $results = $e;
- }
-}
-/*
- * Template support.
- *
- * tplFooHelper::getAjax() is called where 'foo' is the value
- * of the 'template' variable passed via the URL
- * (i.e. index.php?option=com_ajax&template=foo).
- *
- */
-elseif ($input->get('template'))
-{
- $template = $input->get('template');
- $table = Table::getInstance('extension');
- $templateId = $table->find(array('type' => 'template', 'element' => $template));
-
- if ($templateId && $table->load($templateId) && $table->enabled)
- {
- $basePath = ($table->client_id) ? JPATH_ADMINISTRATOR : JPATH_SITE;
- $helperFile = $basePath . '/templates/' . $template . '/helper.php';
-
- if (strpos($template, '_'))
- {
- $parts = explode('_', $template);
- }
- elseif (strpos($template, '-'))
- {
- $parts = explode('-', $template);
- }
-
- if ($parts)
- {
- $class = 'Tpl';
-
- foreach ($parts as $part)
- {
- $class .= ucfirst($part);
- }
-
- $class .= 'Helper';
- }
- else
- {
- $class = 'Tpl' . ucfirst($template) . 'Helper';
- }
-
- $method = $input->get('method') ?: 'get';
-
- if (is_file($helperFile))
- {
- JLoader::register($class, $helperFile);
-
- if (method_exists($class, $method . 'Ajax'))
- {
- // Load language file for template
- $lang = Factory::getLanguage();
- $lang->load('tpl_' . $template, $basePath)
- || $lang->load('tpl_' . $template, $basePath . '/templates/' . $template);
-
- try
- {
- $results = call_user_func($class . '::' . $method . 'Ajax');
- }
- catch (Exception $e)
- {
- $results = $e;
- }
- }
- // Method does not exist
- else
- {
- $results = new LogicException(Text::sprintf('COM_AJAX_METHOD_NOT_EXISTS', $method . 'Ajax'), 404);
- }
- }
- // The helper file does not exist
- else
- {
- $results = new RuntimeException(Text::sprintf('COM_AJAX_FILE_NOT_EXISTS', 'tpl_' . $template . '/helper.php'), 404);
- }
- }
- // Template is not assigned to the current menu item
- else
- {
- $results = new LogicException(Text::sprintf('COM_AJAX_TEMPLATE_NOT_ACCESSIBLE', 'tpl_' . $template), 404);
- }
+if (!$format) {
+ $results = new InvalidArgumentException(Text::_('COM_AJAX_SPECIFY_FORMAT'), 404);
+} elseif ($input->get('module')) {
+ /**
+ * Module support.
+ *
+ * modFooHelper::getAjax() is called where 'foo' is the value
+ * of the 'module' variable passed via the URL
+ * (i.e. index.php?option=com_ajax&module=foo).
+ *
+ */
+ $module = $input->get('module');
+ $table = Table::getInstance('extension');
+ $moduleId = $table->find(array('type' => 'module', 'element' => 'mod_' . $module));
+
+ if ($moduleId && $table->load($moduleId) && $table->enabled) {
+ $helperFile = JPATH_BASE . '/modules/mod_' . $module . '/helper.php';
+
+ if (strpos($module, '_')) {
+ $parts = explode('_', $module);
+ } elseif (strpos($module, '-')) {
+ $parts = explode('-', $module);
+ }
+
+ if ($parts) {
+ $class = 'Mod';
+
+ foreach ($parts as $part) {
+ $class .= ucfirst($part);
+ }
+
+ $class .= 'Helper';
+ } else {
+ $class = 'Mod' . ucfirst($module) . 'Helper';
+ }
+
+ $method = $input->get('method') ?: 'get';
+
+ $moduleInstance = $app->bootModule('mod_' . $module, $app->getName());
+
+ if ($moduleInstance instanceof \Joomla\CMS\Helper\HelperFactoryInterface && $helper = $moduleInstance->getHelper(substr($class, 3))) {
+ $results = method_exists($helper, $method . 'Ajax') ? $helper->{$method . 'Ajax'}() : null;
+ }
+
+ if ($results === null && is_file($helperFile)) {
+ JLoader::register($class, $helperFile);
+
+ if (method_exists($class, $method . 'Ajax')) {
+ // Load language file for module
+ $basePath = JPATH_BASE;
+ $lang = Factory::getLanguage();
+ $lang->load('mod_' . $module, $basePath)
+ || $lang->load('mod_' . $module, $basePath . '/modules/mod_' . $module);
+
+ try {
+ $results = call_user_func($class . '::' . $method . 'Ajax');
+ } catch (Exception $e) {
+ $results = $e;
+ }
+ } else {
+ // Method does not exist
+ $results = new LogicException(Text::sprintf('COM_AJAX_METHOD_NOT_EXISTS', $method . 'Ajax'), 404);
+ }
+ } elseif ($results === null) {
+ // The helper file does not exist
+ $results = new RuntimeException(Text::sprintf('COM_AJAX_FILE_NOT_EXISTS', 'mod_' . $module . '/helper.php'), 404);
+ }
+ } else {
+ // Module is not published, you do not have access to it, or it is not assigned to the current menu item
+ $results = new LogicException(Text::sprintf('COM_AJAX_MODULE_NOT_ACCESSIBLE', 'mod_' . $module), 404);
+ }
+} elseif ($input->get('plugin')) {
+ /**
+ * Plugin support by default is based on the "Ajax" plugin group.
+ * An optional 'group' variable can be passed via the URL.
+ *
+ * The plugin event triggered is onAjaxFoo, where 'foo' is
+ * the value of the 'plugin' variable passed via the URL
+ * (i.e. index.php?option=com_ajax&plugin=foo)
+ *
+ */
+ $group = $input->get('group', 'ajax');
+ PluginHelper::importPlugin($group);
+ $plugin = ucfirst($input->get('plugin'));
+
+ try {
+ $results = Factory::getApplication()->triggerEvent('onAjax' . $plugin);
+ } catch (Exception $e) {
+ $results = $e;
+ }
+} elseif ($input->get('template')) {
+ /**
+ * Template support.
+ *
+ * tplFooHelper::getAjax() is called where 'foo' is the value
+ * of the 'template' variable passed via the URL
+ * (i.e. index.php?option=com_ajax&template=foo).
+ *
+ */
+ $template = $input->get('template');
+ $table = Table::getInstance('extension');
+ $templateId = $table->find(array('type' => 'template', 'element' => $template));
+
+ if ($templateId && $table->load($templateId) && $table->enabled) {
+ $basePath = ($table->client_id) ? JPATH_ADMINISTRATOR : JPATH_SITE;
+ $helperFile = $basePath . '/templates/' . $template . '/helper.php';
+
+ if (strpos($template, '_')) {
+ $parts = explode('_', $template);
+ } elseif (strpos($template, '-')) {
+ $parts = explode('-', $template);
+ }
+
+ if ($parts) {
+ $class = 'Tpl';
+
+ foreach ($parts as $part) {
+ $class .= ucfirst($part);
+ }
+
+ $class .= 'Helper';
+ } else {
+ $class = 'Tpl' . ucfirst($template) . 'Helper';
+ }
+
+ $method = $input->get('method') ?: 'get';
+
+ if (is_file($helperFile)) {
+ JLoader::register($class, $helperFile);
+
+ if (method_exists($class, $method . 'Ajax')) {
+ // Load language file for template
+ $lang = Factory::getLanguage();
+ $lang->load('tpl_' . $template, $basePath)
+ || $lang->load('tpl_' . $template, $basePath . '/templates/' . $template);
+
+ try {
+ $results = call_user_func($class . '::' . $method . 'Ajax');
+ } catch (Exception $e) {
+ $results = $e;
+ }
+ } else {
+ // Method does not exist
+ $results = new LogicException(Text::sprintf('COM_AJAX_METHOD_NOT_EXISTS', $method . 'Ajax'), 404);
+ }
+ } else {
+ // The helper file does not exist
+ $results = new RuntimeException(Text::sprintf('COM_AJAX_FILE_NOT_EXISTS', 'tpl_' . $template . '/helper.php'), 404);
+ }
+ } else {
+ // Template is not assigned to the current menu item
+ $results = new LogicException(Text::sprintf('COM_AJAX_TEMPLATE_NOT_ACCESSIBLE', 'tpl_' . $template), 404);
+ }
}
// Return the results in the desired format
-switch ($format)
-{
- // JSONinzed
- case 'json':
- echo new JsonResponse($results, null, false, $input->get('ignoreMessages', true, 'bool'));
-
- break;
-
- // Handle as raw format
- default:
- // Output exception
- if ($results instanceof Exception)
- {
- // Log an error
- Log::add($results->getMessage(), Log::ERROR);
-
- // Set status header code
- $app->setHeader('status', $results->getCode(), true);
-
- // Echo exception type and message
- $out = get_class($results) . ': ' . $results->getMessage();
- }
- // Output string/ null
- elseif (is_scalar($results))
- {
- $out = (string) $results;
- }
- // Output array/ object
- else
- {
- $out = implode((array) $results);
- }
-
- echo $out;
-
- break;
+switch ($format) {
+ // JSONinzed
+ case 'json':
+ echo new JsonResponse($results, null, false, $input->get('ignoreMessages', true, 'bool'));
+
+ break;
+
+ // Handle as raw format
+ default:
+ // Output exception
+ if ($results instanceof Exception) {
+ // Log an error
+ Log::add($results->getMessage(), Log::ERROR);
+
+ // Set status header code
+ $app->setHeader('status', $results->getCode(), true);
+
+ // Echo exception type and message
+ $out = get_class($results) . ': ' . $results->getMessage();
+ } elseif (is_scalar($results)) {
+ // Output string/ null
+ $out = (string) $results;
+ } else {
+ // Output array/ object
+ $out = implode((array) $results);
+ }
+
+ echo $out;
+
+ break;
}
diff --git a/components/com_banners/src/Controller/DisplayController.php b/components/com_banners/src/Controller/DisplayController.php
index 75b3f4f667b9e..14c6e5a1ef32e 100644
--- a/components/com_banners/src/Controller/DisplayController.php
+++ b/components/com_banners/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
input->getInt('id', 0);
+ /**
+ * Method when a banner is clicked on.
+ *
+ * @return void
+ *
+ * @since 1.5
+ */
+ public function click()
+ {
+ $id = $this->input->getInt('id', 0);
- if ($id)
- {
- /** @var \Joomla\Component\Banners\Site\Model\BannerModel $model */
- $model = $this->getModel('Banner', 'Site', array('ignore_request' => true));
- $model->setState('banner.id', $id);
- $model->click();
- $this->setRedirect($model->getUrl());
- }
- }
+ if ($id) {
+ /** @var \Joomla\Component\Banners\Site\Model\BannerModel $model */
+ $model = $this->getModel('Banner', 'Site', array('ignore_request' => true));
+ $model->setState('banner.id', $id);
+ $model->click();
+ $this->setRedirect($model->getUrl());
+ }
+ }
}
diff --git a/components/com_banners/src/Helper/BannerHelper.php b/components/com_banners/src/Helper/BannerHelper.php
index 3476a5841c758..a34db03e989a4 100644
--- a/components/com_banners/src/Helper/BannerHelper.php
+++ b/components/com_banners/src/Helper/BannerHelper.php
@@ -1,4 +1,5 @@
getItem();
-
- if (empty($item))
- {
- throw new \Exception(Text::_('JERROR_PAGE_NOT_FOUND'), 404);
- }
-
- $id = (int) $this->getState('banner.id');
-
- // Update click count
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- $query->update($db->quoteName('#__banners'))
- ->set($db->quoteName('clicks') . ' = ' . $db->quoteName('clicks') . ' + 1')
- ->where($db->quoteName('id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
-
- // Track clicks
- $trackClicks = $item->track_clicks;
-
- if ($trackClicks < 0 && $item->cid)
- {
- $trackClicks = $item->client_track_clicks;
- }
-
- if ($trackClicks < 0)
- {
- $config = ComponentHelper::getParams('com_banners');
- $trackClicks = $config->get('track_clicks');
- }
-
- if ($trackClicks > 0)
- {
- $trackDate = Factory::getDate()->format('Y-m-d H:00:00');
- $trackDate = Factory::getDate($trackDate)->toSql();
-
- $query = $db->getQuery(true);
-
- $query->select($db->quoteName('count'))
- ->from($db->quoteName('#__banner_tracks'))
- ->where(
- [
- $db->quoteName('track_type') . ' = 2',
- $db->quoteName('banner_id') . ' = :id',
- $db->quoteName('track_date') . ' = :trackDate',
- ]
- )
- ->bind(':id', $id, ParameterType::INTEGER)
- ->bind(':trackDate', $trackDate);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
-
- $count = $db->loadResult();
-
- $query = $db->getQuery(true);
-
- if ($count)
- {
- // Update count
- $query->update($db->quoteName('#__banner_tracks'))
- ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1')
- ->where(
- [
- $db->quoteName('track_type') . ' = 2',
- $db->quoteName('banner_id') . ' = :id',
- $db->quoteName('track_date') . ' = :trackDate',
- ]
- )
- ->bind(':id', $id, ParameterType::INTEGER)
- ->bind(':trackDate', $trackDate);
- }
- else
- {
- // Insert new count
- $query->insert($db->quoteName('#__banner_tracks'))
- ->columns(
- [
- $db->quoteName('count'),
- $db->quoteName('track_type'),
- $db->quoteName('banner_id'),
- $db->quoteName('track_date'),
- ]
- )
- ->values('1, 2 , :id, :trackDate')
- ->bind(':id', $id, ParameterType::INTEGER)
- ->bind(':trackDate', $trackDate);
- }
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (\RuntimeException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
- }
- }
-
- /**
- * Get the data for a banner.
- *
- * @return object
- *
- * @since 1.6
- */
- public function &getItem()
- {
- if (!isset($this->_item))
- {
- /** @var \Joomla\CMS\Cache\Controller\CallbackController $cache */
- $cache = Factory::getCache('com_banners', 'callback');
-
- $id = (int) $this->getState('banner.id');
-
- // For PHP 5.3 compat we can't use $this in the lambda function below, so grab the database driver now to use it
- $db = $this->getDatabase();
-
- $loader = function ($id) use ($db)
- {
- $query = $db->getQuery(true);
-
- $query->select(
- [
- $db->quoteName('a.clickurl'),
- $db->quoteName('a.cid'),
- $db->quoteName('a.track_clicks'),
- $db->quoteName('cl.track_clicks', 'client_track_clicks'),
- ]
- )
- ->from($db->quoteName('#__banners', 'a'))
- ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'))
- ->where($db->quoteName('a.id') . ' = :id')
- ->bind(':id', $id, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- return $db->loadObject();
- };
-
- try
- {
- $this->_item = $cache->get($loader, array($id), md5(__METHOD__ . $id));
- }
- catch (CacheExceptionInterface $e)
- {
- $this->_item = $loader($id);
- }
- }
-
- return $this->_item;
- }
-
- /**
- * Get the URL for a banner
- *
- * @return string
- *
- * @since 1.5
- */
- public function getUrl()
- {
- $item = $this->getItem();
- $url = $item->clickurl;
-
- // Check for links
- if (!preg_match('#http[s]?://|index[2]?\.php#', $url))
- {
- $url = "http://$url";
- }
-
- return $url;
- }
+ /**
+ * Cached item object
+ *
+ * @var object
+ * @since 1.6
+ */
+ protected $_item;
+
+ /**
+ * Clicks the URL, incrementing the counter
+ *
+ * @return void
+ *
+ * @since 1.5
+ * @throws \Exception
+ */
+ public function click()
+ {
+ $item = $this->getItem();
+
+ if (empty($item)) {
+ throw new \Exception(Text::_('JERROR_PAGE_NOT_FOUND'), 404);
+ }
+
+ $id = (int) $this->getState('banner.id');
+
+ // Update click count
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ $query->update($db->quoteName('#__banners'))
+ ->set($db->quoteName('clicks') . ' = ' . $db->quoteName('clicks') . ' + 1')
+ ->where($db->quoteName('id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+
+ // Track clicks
+ $trackClicks = $item->track_clicks;
+
+ if ($trackClicks < 0 && $item->cid) {
+ $trackClicks = $item->client_track_clicks;
+ }
+
+ if ($trackClicks < 0) {
+ $config = ComponentHelper::getParams('com_banners');
+ $trackClicks = $config->get('track_clicks');
+ }
+
+ if ($trackClicks > 0) {
+ $trackDate = Factory::getDate()->format('Y-m-d H:00:00');
+ $trackDate = Factory::getDate($trackDate)->toSql();
+
+ $query = $db->getQuery(true);
+
+ $query->select($db->quoteName('count'))
+ ->from($db->quoteName('#__banner_tracks'))
+ ->where(
+ [
+ $db->quoteName('track_type') . ' = 2',
+ $db->quoteName('banner_id') . ' = :id',
+ $db->quoteName('track_date') . ' = :trackDate',
+ ]
+ )
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->bind(':trackDate', $trackDate);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+
+ $count = $db->loadResult();
+
+ $query = $db->getQuery(true);
+
+ if ($count) {
+ // Update count
+ $query->update($db->quoteName('#__banner_tracks'))
+ ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1')
+ ->where(
+ [
+ $db->quoteName('track_type') . ' = 2',
+ $db->quoteName('banner_id') . ' = :id',
+ $db->quoteName('track_date') . ' = :trackDate',
+ ]
+ )
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->bind(':trackDate', $trackDate);
+ } else {
+ // Insert new count
+ $query->insert($db->quoteName('#__banner_tracks'))
+ ->columns(
+ [
+ $db->quoteName('count'),
+ $db->quoteName('track_type'),
+ $db->quoteName('banner_id'),
+ $db->quoteName('track_date'),
+ ]
+ )
+ ->values('1, 2 , :id, :trackDate')
+ ->bind(':id', $id, ParameterType::INTEGER)
+ ->bind(':trackDate', $trackDate);
+ }
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (\RuntimeException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+ }
+ }
+
+ /**
+ * Get the data for a banner.
+ *
+ * @return object
+ *
+ * @since 1.6
+ */
+ public function &getItem()
+ {
+ if (!isset($this->_item)) {
+ /** @var \Joomla\CMS\Cache\Controller\CallbackController $cache */
+ $cache = Factory::getCache('com_banners', 'callback');
+
+ $id = (int) $this->getState('banner.id');
+
+ // For PHP 5.3 compat we can't use $this in the lambda function below, so grab the database driver now to use it
+ $db = $this->getDatabase();
+
+ $loader = function ($id) use ($db) {
+ $query = $db->getQuery(true);
+
+ $query->select(
+ [
+ $db->quoteName('a.clickurl'),
+ $db->quoteName('a.cid'),
+ $db->quoteName('a.track_clicks'),
+ $db->quoteName('cl.track_clicks', 'client_track_clicks'),
+ ]
+ )
+ ->from($db->quoteName('#__banners', 'a'))
+ ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'))
+ ->where($db->quoteName('a.id') . ' = :id')
+ ->bind(':id', $id, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ return $db->loadObject();
+ };
+
+ try {
+ $this->_item = $cache->get($loader, array($id), md5(__METHOD__ . $id));
+ } catch (CacheExceptionInterface $e) {
+ $this->_item = $loader($id);
+ }
+ }
+
+ return $this->_item;
+ }
+
+ /**
+ * Get the URL for a banner
+ *
+ * @return string
+ *
+ * @since 1.5
+ */
+ public function getUrl()
+ {
+ $item = $this->getItem();
+ $url = $item->clickurl;
+
+ // Check for links
+ if (!preg_match('#http[s]?://|index[2]?\.php#', $url)) {
+ $url = "http://$url";
+ }
+
+ return $url;
+ }
}
diff --git a/components/com_banners/src/Model/BannersModel.php b/components/com_banners/src/Model/BannersModel.php
index 9b5316aac5bee..5f97e8da74603 100644
--- a/components/com_banners/src/Model/BannersModel.php
+++ b/components/com_banners/src/Model/BannersModel.php
@@ -1,4 +1,5 @@
getState('filter.search');
- $id .= ':' . $this->getState('filter.tag_search');
- $id .= ':' . $this->getState('filter.client_id');
- $id .= ':' . serialize($this->getState('filter.category_id'));
- $id .= ':' . serialize($this->getState('filter.keywords'));
-
- return parent::getStoreId($id);
- }
-
- /**
- * Method to get a DatabaseQuery object for retrieving the data set from a database.
- *
- * @return DatabaseQuery A DatabaseQuery object to retrieve the data set.
- *
- * @since 1.6
- */
- protected function getListQuery()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $ordering = $this->getState('filter.ordering');
- $tagSearch = $this->getState('filter.tag_search');
- $cid = (int) $this->getState('filter.client_id');
- $categoryId = $this->getState('filter.category_id');
- $keywords = $this->getState('filter.keywords');
- $randomise = ($ordering === 'random');
- $nowDate = Factory::getDate()->toSql();
-
- $query->select(
- [
- $db->quoteName('a.id'),
- $db->quoteName('a.type'),
- $db->quoteName('a.name'),
- $db->quoteName('a.clickurl'),
- $db->quoteName('a.sticky'),
- $db->quoteName('a.cid'),
- $db->quoteName('a.description'),
- $db->quoteName('a.params'),
- $db->quoteName('a.custombannercode'),
- $db->quoteName('a.track_impressions'),
- $db->quoteName('cl.track_impressions', 'client_track_impressions'),
- ]
- )
- ->from($db->quoteName('#__banners', 'a'))
- ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'))
- ->where($db->quoteName('a.state') . ' = 1')
- ->extendWhere(
- 'AND',
- [
- $db->quoteName('a.publish_up') . ' IS NULL',
- $db->quoteName('a.publish_up') . ' <= :nowDate1',
- ],
- 'OR'
- )
- ->extendWhere(
- 'AND',
- [
- $db->quoteName('a.publish_down') . ' IS NULL',
- $db->quoteName('a.publish_down') . ' >= :nowDate2',
- ],
- 'OR'
- )
- ->extendWhere(
- 'AND',
- [
- $db->quoteName('a.imptotal') . ' = 0',
- $db->quoteName('a.impmade') . ' < ' . $db->quoteName('a.imptotal'),
- ],
- 'OR'
- )
- ->bind([':nowDate1', ':nowDate2'], $nowDate);
-
- if ($cid)
- {
- $query->where(
- [
- $db->quoteName('a.cid') . ' = :clientId',
- $db->quoteName('cl.state') . ' = 1',
- ]
- )
- ->bind(':clientId', $cid, ParameterType::INTEGER);
- }
-
- // Filter by a single or group of categories
- if (is_numeric($categoryId))
- {
- $categoryId = (int) $categoryId;
- $type = $this->getState('filter.category_id.include', true) ? ' = ' : ' <> ';
-
- // Add subcategory check
- if ($this->getState('filter.subcategories', false))
- {
- $levels = (int) $this->getState('filter.max_category_levels', '1');
-
- // Create a subquery for the subcategory list
- $subQuery = $db->getQuery(true);
- $subQuery->select($db->quoteName('sub.id'))
- ->from($db->quoteName('#__categories', 'sub'))
- ->join(
- 'INNER',
- $db->quoteName('#__categories', 'this'),
- $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft')
- . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt')
- )
- ->where(
- [
- $db->quoteName('this.id') . ' = :categoryId1',
- $db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :levels',
- ]
- );
-
- // Add the subquery to the main query
- $query->extendWhere(
- 'AND',
- [
- $db->quoteName('a.catid') . $type . ':categoryId2',
- $db->quoteName('a.catid') . ' IN (' . $subQuery . ')',
- ],
- 'OR'
- )
- ->bind([':categoryId1', ':categoryId2'], $categoryId, ParameterType::INTEGER)
- ->bind(':levels', $levels, ParameterType::INTEGER);
- }
- else
- {
- $query->where($db->quoteName('a.catid') . $type . ':categoryId')
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
- }
- }
- elseif (is_array($categoryId) && (count($categoryId) > 0))
- {
- $categoryId = ArrayHelper::toInteger($categoryId);
-
- if ($this->getState('filter.category_id.include', true))
- {
- $query->whereIn($db->quoteName('a.catid'), $categoryId);
- }
- else
- {
- $query->whereNotIn($db->quoteName('a.catid'), $categoryId);
- }
- }
-
- if ($tagSearch)
- {
- if (!$keywords)
- {
- // No keywords, select nothing.
- $query->where('0 != 0');
- }
- else
- {
- $temp = array();
- $config = ComponentHelper::getParams('com_banners');
- $prefix = $config->get('metakey_prefix');
-
- if ($categoryId)
- {
- $query->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('a.catid') . ' = ' . $db->quoteName('cat.id'));
- }
-
- foreach ($keywords as $key => $keyword)
- {
- $regexp = '[[:<:]]' . $keyword . '[[:>:]]';
- $valuesToBind = [$keyword, $keyword, $regexp];
-
- if ($cid)
- {
- $valuesToBind[] = $regexp;
- }
-
- if ($categoryId)
- {
- $valuesToBind[] = $regexp;
- }
-
- // Because values to $query->bind() are passed by reference, using $query->bindArray() here instead to prevent overwriting.
- $bounded = $query->bindArray($valuesToBind, ParameterType::STRING);
-
- $condition1 = $db->quoteName('a.own_prefix') . ' = 1'
- . ' AND ' . $db->quoteName('a.metakey_prefix')
- . ' = SUBSTRING(' . $bounded[0] . ',1,LENGTH(' . $db->quoteName('a.metakey_prefix') . '))'
- . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0'
- . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 1'
- . ' AND ' . $db->quoteName('cl.metakey_prefix')
- . ' = SUBSTRING(' . $bounded[1] . ',1,LENGTH(' . $db->quoteName('cl.metakey_prefix') . '))'
- . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0'
- . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 0'
- . ' AND ' . ($prefix == substr($keyword, 0, strlen($prefix)) ? '0 = 0' : '0 != 0');
-
- $condition2 = $db->quoteName('a.metakey') . ' ' . $query->regexp($bounded[2]);
-
- if ($cid)
- {
- $condition2 .= ' OR ' . $db->quoteName('cl.metakey') . ' ' . $query->regexp($bounded[3]) . ' ';
- }
-
- if ($categoryId)
- {
- $condition2 .= ' OR ' . $db->quoteName('cat.metakey') . ' ' . $query->regexp($bounded[4]) . ' ';
- }
-
- $temp[] = "($condition1) AND ($condition2)";
- }
-
- $query->where('(' . implode(' OR ', $temp) . ')');
- }
- }
-
- // Filter by language
- if ($this->getState('filter.language'))
- {
- $query->whereIn($db->quoteName('a.language'), [Factory::getLanguage()->getTag(), '*'], ParameterType::STRING);
- }
-
- $query->order($db->quoteName('a.sticky') . ' DESC, ' . ($randomise ? $query->rand() : $db->quoteName('a.ordering')));
-
- return $query;
- }
-
- /**
- * Get a list of banners.
- *
- * @return array
- *
- * @since 1.6
- */
- public function getItems()
- {
- if ($this->getState('filter.tag_search'))
- {
- // Filter out empty keywords.
- $keywords = array_values(array_filter(array_map('trim', $this->getState('filter.keywords')), 'strlen'));
-
- // Re-set state before running the query.
- $this->setState('filter.keywords', $keywords);
-
- // If no keywords are provided, avoid running the query.
- if (!$keywords)
- {
- $this->cache['items'] = array();
-
- return $this->cache['items'];
- }
- }
-
- if (!isset($this->cache['items']))
- {
- $this->cache['items'] = parent::getItems();
-
- foreach ($this->cache['items'] as &$item)
- {
- $item->params = new Registry($item->params);
- }
- }
-
- return $this->cache['items'];
- }
-
- /**
- * Makes impressions on a list of banners
- *
- * @return void
- *
- * @since 1.6
- * @throws \Exception
- */
- public function impress()
- {
- $trackDate = Factory::getDate()->format('Y-m-d H:00:00');
- $trackDate = Factory::getDate($trackDate)->toSql();
- $items = $this->getItems();
- $db = $this->getDatabase();
- $bid = [];
-
- if (!count($items))
- {
- return;
- }
-
- foreach ($items as $item)
- {
- $bid[] = (int) $item->id;
- }
-
- // Increment impression made
- $query = $db->getQuery(true);
- $query->update($db->quoteName('#__banners'))
- ->set($db->quoteName('impmade') . ' = ' . $db->quoteName('impmade') . ' + 1')
- ->whereIn($db->quoteName('id'), $bid);
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
-
- foreach ($items as $item)
- {
- // Track impressions
- $trackImpressions = $item->track_impressions;
-
- if ($trackImpressions < 0 && $item->cid)
- {
- $trackImpressions = $item->client_track_impressions;
- }
-
- if ($trackImpressions < 0)
- {
- $config = ComponentHelper::getParams('com_banners');
- $trackImpressions = $config->get('track_impressions');
- }
-
- if ($trackImpressions > 0)
- {
- // Is track already created?
- // Update count
- $query = $db->getQuery(true);
- $query->update($db->quoteName('#__banner_tracks'))
- ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1')
- ->where(
- [
- $db->quoteName('track_type') . ' = 1',
- $db->quoteName('banner_id') . ' = :id',
- $db->quoteName('track_date') . ' = :trackDate',
- ]
- )
- ->bind(':id', $item->id, ParameterType::INTEGER)
- ->bind(':trackDate', $trackDate);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
-
- if ($db->getAffectedRows() === 0)
- {
- // Insert new count
- $query = $db->getQuery(true);
- $query->insert($db->quoteName('#__banner_tracks'))
- ->columns(
- [
- $db->quoteName('count'),
- $db->quoteName('track_type'),
- $db->quoteName('banner_id'),
- $db->quoteName('track_date'),
- ]
- )
- ->values('1, 1, :id, :trackDate')
- ->bind(':id', $item->id, ParameterType::INTEGER)
- ->bind(':trackDate', $trackDate);
-
- $db->setQuery($query);
-
- try
- {
- $db->execute();
- }
- catch (ExecutionFailureException $e)
- {
- throw new \Exception($e->getMessage(), 500);
- }
- }
- }
- }
- }
+ /**
+ * Method to get a store id based on model configuration state.
+ *
+ * This is necessary because the model is used by the component and
+ * different modules that might need different sets of data or different
+ * ordering requirements.
+ *
+ * @param string $id A prefix for the store id.
+ *
+ * @return string A store id.
+ *
+ * @since 1.6
+ */
+ protected function getStoreId($id = '')
+ {
+ // Compile the store id.
+ $id .= ':' . $this->getState('filter.search');
+ $id .= ':' . $this->getState('filter.tag_search');
+ $id .= ':' . $this->getState('filter.client_id');
+ $id .= ':' . serialize($this->getState('filter.category_id'));
+ $id .= ':' . serialize($this->getState('filter.keywords'));
+
+ return parent::getStoreId($id);
+ }
+
+ /**
+ * Method to get a DatabaseQuery object for retrieving the data set from a database.
+ *
+ * @return DatabaseQuery A DatabaseQuery object to retrieve the data set.
+ *
+ * @since 1.6
+ */
+ protected function getListQuery()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $ordering = $this->getState('filter.ordering');
+ $tagSearch = $this->getState('filter.tag_search');
+ $cid = (int) $this->getState('filter.client_id');
+ $categoryId = $this->getState('filter.category_id');
+ $keywords = $this->getState('filter.keywords');
+ $randomise = ($ordering === 'random');
+ $nowDate = Factory::getDate()->toSql();
+
+ $query->select(
+ [
+ $db->quoteName('a.id'),
+ $db->quoteName('a.type'),
+ $db->quoteName('a.name'),
+ $db->quoteName('a.clickurl'),
+ $db->quoteName('a.sticky'),
+ $db->quoteName('a.cid'),
+ $db->quoteName('a.description'),
+ $db->quoteName('a.params'),
+ $db->quoteName('a.custombannercode'),
+ $db->quoteName('a.track_impressions'),
+ $db->quoteName('cl.track_impressions', 'client_track_impressions'),
+ ]
+ )
+ ->from($db->quoteName('#__banners', 'a'))
+ ->join('LEFT', $db->quoteName('#__banner_clients', 'cl'), $db->quoteName('cl.id') . ' = ' . $db->quoteName('a.cid'))
+ ->where($db->quoteName('a.state') . ' = 1')
+ ->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.publish_up') . ' IS NULL',
+ $db->quoteName('a.publish_up') . ' <= :nowDate1',
+ ],
+ 'OR'
+ )
+ ->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.publish_down') . ' IS NULL',
+ $db->quoteName('a.publish_down') . ' >= :nowDate2',
+ ],
+ 'OR'
+ )
+ ->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.imptotal') . ' = 0',
+ $db->quoteName('a.impmade') . ' < ' . $db->quoteName('a.imptotal'),
+ ],
+ 'OR'
+ )
+ ->bind([':nowDate1', ':nowDate2'], $nowDate);
+
+ if ($cid) {
+ $query->where(
+ [
+ $db->quoteName('a.cid') . ' = :clientId',
+ $db->quoteName('cl.state') . ' = 1',
+ ]
+ )
+ ->bind(':clientId', $cid, ParameterType::INTEGER);
+ }
+
+ // Filter by a single or group of categories
+ if (is_numeric($categoryId)) {
+ $categoryId = (int) $categoryId;
+ $type = $this->getState('filter.category_id.include', true) ? ' = ' : ' <> ';
+
+ // Add subcategory check
+ if ($this->getState('filter.subcategories', false)) {
+ $levels = (int) $this->getState('filter.max_category_levels', '1');
+
+ // Create a subquery for the subcategory list
+ $subQuery = $db->getQuery(true);
+ $subQuery->select($db->quoteName('sub.id'))
+ ->from($db->quoteName('#__categories', 'sub'))
+ ->join(
+ 'INNER',
+ $db->quoteName('#__categories', 'this'),
+ $db->quoteName('sub.lft') . ' > ' . $db->quoteName('this.lft')
+ . ' AND ' . $db->quoteName('sub.rgt') . ' < ' . $db->quoteName('this.rgt')
+ )
+ ->where(
+ [
+ $db->quoteName('this.id') . ' = :categoryId1',
+ $db->quoteName('sub.level') . ' <= ' . $db->quoteName('this.level') . ' + :levels',
+ ]
+ );
+
+ // Add the subquery to the main query
+ $query->extendWhere(
+ 'AND',
+ [
+ $db->quoteName('a.catid') . $type . ':categoryId2',
+ $db->quoteName('a.catid') . ' IN (' . $subQuery . ')',
+ ],
+ 'OR'
+ )
+ ->bind([':categoryId1', ':categoryId2'], $categoryId, ParameterType::INTEGER)
+ ->bind(':levels', $levels, ParameterType::INTEGER);
+ } else {
+ $query->where($db->quoteName('a.catid') . $type . ':categoryId')
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+ }
+ } elseif (is_array($categoryId) && (count($categoryId) > 0)) {
+ $categoryId = ArrayHelper::toInteger($categoryId);
+
+ if ($this->getState('filter.category_id.include', true)) {
+ $query->whereIn($db->quoteName('a.catid'), $categoryId);
+ } else {
+ $query->whereNotIn($db->quoteName('a.catid'), $categoryId);
+ }
+ }
+
+ if ($tagSearch) {
+ if (!$keywords) {
+ // No keywords, select nothing.
+ $query->where('0 != 0');
+ } else {
+ $temp = array();
+ $config = ComponentHelper::getParams('com_banners');
+ $prefix = $config->get('metakey_prefix');
+
+ if ($categoryId) {
+ $query->join('LEFT', $db->quoteName('#__categories', 'cat'), $db->quoteName('a.catid') . ' = ' . $db->quoteName('cat.id'));
+ }
+
+ foreach ($keywords as $key => $keyword) {
+ $regexp = '[[:<:]]' . $keyword . '[[:>:]]';
+ $valuesToBind = [$keyword, $keyword, $regexp];
+
+ if ($cid) {
+ $valuesToBind[] = $regexp;
+ }
+
+ if ($categoryId) {
+ $valuesToBind[] = $regexp;
+ }
+
+ // Because values to $query->bind() are passed by reference, using $query->bindArray() here instead to prevent overwriting.
+ $bounded = $query->bindArray($valuesToBind, ParameterType::STRING);
+
+ $condition1 = $db->quoteName('a.own_prefix') . ' = 1'
+ . ' AND ' . $db->quoteName('a.metakey_prefix')
+ . ' = SUBSTRING(' . $bounded[0] . ',1,LENGTH(' . $db->quoteName('a.metakey_prefix') . '))'
+ . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0'
+ . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 1'
+ . ' AND ' . $db->quoteName('cl.metakey_prefix')
+ . ' = SUBSTRING(' . $bounded[1] . ',1,LENGTH(' . $db->quoteName('cl.metakey_prefix') . '))'
+ . ' OR ' . $db->quoteName('a.own_prefix') . ' = 0'
+ . ' AND ' . $db->quoteName('cl.own_prefix') . ' = 0'
+ . ' AND ' . ($prefix == substr($keyword, 0, strlen($prefix)) ? '0 = 0' : '0 != 0');
+
+ $condition2 = $db->quoteName('a.metakey') . ' ' . $query->regexp($bounded[2]);
+
+ if ($cid) {
+ $condition2 .= ' OR ' . $db->quoteName('cl.metakey') . ' ' . $query->regexp($bounded[3]) . ' ';
+ }
+
+ if ($categoryId) {
+ $condition2 .= ' OR ' . $db->quoteName('cat.metakey') . ' ' . $query->regexp($bounded[4]) . ' ';
+ }
+
+ $temp[] = "($condition1) AND ($condition2)";
+ }
+
+ $query->where('(' . implode(' OR ', $temp) . ')');
+ }
+ }
+
+ // Filter by language
+ if ($this->getState('filter.language')) {
+ $query->whereIn($db->quoteName('a.language'), [Factory::getLanguage()->getTag(), '*'], ParameterType::STRING);
+ }
+
+ $query->order($db->quoteName('a.sticky') . ' DESC, ' . ($randomise ? $query->rand() : $db->quoteName('a.ordering')));
+
+ return $query;
+ }
+
+ /**
+ * Get a list of banners.
+ *
+ * @return array
+ *
+ * @since 1.6
+ */
+ public function getItems()
+ {
+ if ($this->getState('filter.tag_search')) {
+ // Filter out empty keywords.
+ $keywords = array_values(array_filter(array_map('trim', $this->getState('filter.keywords')), 'strlen'));
+
+ // Re-set state before running the query.
+ $this->setState('filter.keywords', $keywords);
+
+ // If no keywords are provided, avoid running the query.
+ if (!$keywords) {
+ $this->cache['items'] = array();
+
+ return $this->cache['items'];
+ }
+ }
+
+ if (!isset($this->cache['items'])) {
+ $this->cache['items'] = parent::getItems();
+
+ foreach ($this->cache['items'] as &$item) {
+ $item->params = new Registry($item->params);
+ }
+ }
+
+ return $this->cache['items'];
+ }
+
+ /**
+ * Makes impressions on a list of banners
+ *
+ * @return void
+ *
+ * @since 1.6
+ * @throws \Exception
+ */
+ public function impress()
+ {
+ $trackDate = Factory::getDate()->format('Y-m-d H:00:00');
+ $trackDate = Factory::getDate($trackDate)->toSql();
+ $items = $this->getItems();
+ $db = $this->getDatabase();
+ $bid = [];
+
+ if (!count($items)) {
+ return;
+ }
+
+ foreach ($items as $item) {
+ $bid[] = (int) $item->id;
+ }
+
+ // Increment impression made
+ $query = $db->getQuery(true);
+ $query->update($db->quoteName('#__banners'))
+ ->set($db->quoteName('impmade') . ' = ' . $db->quoteName('impmade') . ' + 1')
+ ->whereIn($db->quoteName('id'), $bid);
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+
+ foreach ($items as $item) {
+ // Track impressions
+ $trackImpressions = $item->track_impressions;
+
+ if ($trackImpressions < 0 && $item->cid) {
+ $trackImpressions = $item->client_track_impressions;
+ }
+
+ if ($trackImpressions < 0) {
+ $config = ComponentHelper::getParams('com_banners');
+ $trackImpressions = $config->get('track_impressions');
+ }
+
+ if ($trackImpressions > 0) {
+ // Is track already created?
+ // Update count
+ $query = $db->getQuery(true);
+ $query->update($db->quoteName('#__banner_tracks'))
+ ->set($db->quoteName('count') . ' = ' . $db->quoteName('count') . ' + 1')
+ ->where(
+ [
+ $db->quoteName('track_type') . ' = 1',
+ $db->quoteName('banner_id') . ' = :id',
+ $db->quoteName('track_date') . ' = :trackDate',
+ ]
+ )
+ ->bind(':id', $item->id, ParameterType::INTEGER)
+ ->bind(':trackDate', $trackDate);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+
+ if ($db->getAffectedRows() === 0) {
+ // Insert new count
+ $query = $db->getQuery(true);
+ $query->insert($db->quoteName('#__banner_tracks'))
+ ->columns(
+ [
+ $db->quoteName('count'),
+ $db->quoteName('track_type'),
+ $db->quoteName('banner_id'),
+ $db->quoteName('track_date'),
+ ]
+ )
+ ->values('1, 1, :id, :trackDate')
+ ->bind(':id', $item->id, ParameterType::INTEGER)
+ ->bind(':trackDate', $trackDate);
+
+ $db->setQuery($query);
+
+ try {
+ $db->execute();
+ } catch (ExecutionFailureException $e) {
+ throw new \Exception($e->getMessage(), 500);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/components/com_banners/src/Service/Category.php b/components/com_banners/src/Service/Category.php
index 7c02083006dcb..bc95db659903b 100644
--- a/components/com_banners/src/Service/Category.php
+++ b/components/com_banners/src/Service/Category.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- }
-
- /**
- * Method to handle cancel
- *
- * @return void
- *
- * @since 3.2
- */
- public function cancel()
- {
- // Redirect back to home(base) page
- $this->setRedirect(Uri::base());
- }
-
- /**
- * Method to save global configuration.
- *
- * @return boolean True on success.
- *
- * @since 3.2
- */
- public function save()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- // Check if the user is authorized to do this.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'));
- $this->app->redirect('index.php');
- }
-
- // Set FTP credentials, if given.
- ClientHelper::setCredentialsFromRequest('ftp');
-
- $model = $this->getModel();
-
- $form = $model->getForm();
- $data = $this->app->input->post->get('jform', array(), 'array');
-
- // Validate the posted data.
- $return = $model->validate($form, $data);
-
- // Check for validation errors.
- if ($return === false)
- {
- /*
- * The validate method enqueued all messages for us, so we just need to redirect back.
- */
-
- // Save the data in the session.
- $this->app->setUserState('com_config.config.global.data', $data);
-
- // Redirect back to the edit screen.
- $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
- }
-
- // Attempt to save the configuration.
- $data = $return;
-
- // Access backend com_config
- $saveClass = $this->factory->createController('Application', 'Administrator', [], $this->app, $this->input);
-
- // Get a document object
- $document = $this->app->getDocument();
-
- // Set backend required params
- $document->setType('json');
-
- // Execute backend controller
- $return = $saveClass->save();
-
- // Reset params back after requesting from service
- $document->setType('html');
-
- // Check the return value.
- if ($return === false)
- {
- /*
- * The save method enqueued all messages for us, so we just need to redirect back.
- */
-
- // Save the data in the session.
- $this->app->setUserState('com_config.config.global.data', $data);
-
- // Save failed, go back to the screen and display a notice.
- $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
- }
-
- // Redirect back to com_config display
- $this->app->enqueueMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'));
- $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
-
- return true;
- }
+ /**
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface|null $factory The factory.
+ * @param CMSApplication|null $app The JApplication for the dispatcher
+ * @param \Joomla\CMS\Input\Input|null $input The Input object for the request
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('apply', 'save');
+ }
+
+ /**
+ * Method to handle cancel
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function cancel()
+ {
+ // Redirect back to home(base) page
+ $this->setRedirect(Uri::base());
+ }
+
+ /**
+ * Method to save global configuration.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.2
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ // Check if the user is authorized to do this.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'));
+ $this->app->redirect('index.php');
+ }
+
+ // Set FTP credentials, if given.
+ ClientHelper::setCredentialsFromRequest('ftp');
+
+ $model = $this->getModel();
+
+ $form = $model->getForm();
+ $data = $this->app->input->post->get('jform', array(), 'array');
+
+ // Validate the posted data.
+ $return = $model->validate($form, $data);
+
+ // Check for validation errors.
+ if ($return === false) {
+ /*
+ * The validate method enqueued all messages for us, so we just need to redirect back.
+ */
+
+ // Save the data in the session.
+ $this->app->setUserState('com_config.config.global.data', $data);
+
+ // Redirect back to the edit screen.
+ $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
+ }
+
+ // Attempt to save the configuration.
+ $data = $return;
+
+ // Access backend com_config
+ $saveClass = $this->factory->createController('Application', 'Administrator', [], $this->app, $this->input);
+
+ // Get a document object
+ $document = $this->app->getDocument();
+
+ // Set backend required params
+ $document->setType('json');
+
+ // Execute backend controller
+ $return = $saveClass->save();
+
+ // Reset params back after requesting from service
+ $document->setType('html');
+
+ // Check the return value.
+ if ($return === false) {
+ /*
+ * The save method enqueued all messages for us, so we just need to redirect back.
+ */
+
+ // Save the data in the session.
+ $this->app->setUserState('com_config.config.global.data', $data);
+
+ // Save failed, go back to the screen and display a notice.
+ $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
+ }
+
+ // Redirect back to com_config display
+ $this->app->enqueueMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'));
+ $this->app->redirect(Route::_('index.php?option=com_config&view=config', false));
+
+ return true;
+ }
}
diff --git a/components/com_config/src/Controller/DisplayController.php b/components/com_config/src/Controller/DisplayController.php
index daadff242b4c9..8a26c4735fa55 100644
--- a/components/com_config/src/Controller/DisplayController.php
+++ b/components/com_config/src/Controller/DisplayController.php
@@ -1,4 +1,5 @@
setRedirect(Uri::base());
- }
+ /**
+ * Method to handle cancel
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function cancel()
+ {
+ // Redirect back to home(base) page
+ $this->setRedirect(Uri::base());
+ }
}
diff --git a/components/com_config/src/Controller/ModulesController.php b/components/com_config/src/Controller/ModulesController.php
index 5000e4cc91156..d6029eee61b28 100644
--- a/components/com_config/src/Controller/ModulesController.php
+++ b/components/com_config/src/Controller/ModulesController.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- }
-
- /**
- * Method to handle cancel
- *
- * @return void
- *
- * @since 3.2
- */
- public function cancel()
- {
- // Redirect back to home(base) page
- $this->setRedirect(Uri::base());
- }
-
- /**
- * Method to save module editing.
- *
- * @return void
- *
- * @since 3.2
- */
- public function save()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- // Check if the user is authorized to do this.
- $user = $this->app->getIdentity();
-
- if (!$user->authorise('module.edit.frontend', 'com_modules.module.' . $this->input->get('id')))
- {
- $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
- $this->app->redirect('index.php');
- }
-
- // Set FTP credentials, if given.
- ClientHelper::setCredentialsFromRequest('ftp');
-
- // Get submitted module id
- $moduleId = '&id=' . $this->input->getInt('id');
-
- // Get returnUri
- $returnUri = $this->input->post->get('return', null, 'base64');
- $redirect = '';
-
- if (!empty($returnUri))
- {
- $redirect = '&return=' . $returnUri;
- }
-
- /** @var AdministratorApplication $app */
- $app = Factory::getContainer()->get(AdministratorApplication::class);
-
- // Reset Uri cache.
- Uri::reset();
-
- // Get a document object
- $document = $this->app->getDocument();
-
- // Load application dependencies.
- $app->loadLanguage($this->app->getLanguage());
- $app->loadDocument($document);
- $app->loadIdentity($user);
-
- /** @var \Joomla\CMS\Dispatcher\ComponentDispatcher $dispatcher */
- $dispatcher = $app->bootComponent('com_modules')->getDispatcher($app);
-
- /** @var ModuleController $controllerClass */
- $controllerClass = $dispatcher->getController('Module');
-
- // Set backend required params
- $document->setType('json');
-
- // Execute backend controller
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/forms');
- $return = $controllerClass->save();
-
- // Reset params back after requesting from service
- $document->setType('html');
-
- // Check the return value.
- if ($return === false)
- {
- // Save the data in the session.
- $data = $this->input->post->get('jform', array(), 'array');
-
- $this->app->setUserState('com_config.modules.global.data', $data);
-
- // Save failed, go back to the screen and display a notice.
- $this->app->enqueueMessage(Text::_('JERROR_SAVE_FAILED'));
- $this->app->redirect(Route::_('index.php?option=com_config&view=modules' . $moduleId . $redirect, false));
- }
-
- // Redirect back to com_config display
- $this->app->enqueueMessage(Text::_('COM_CONFIG_MODULES_SAVE_SUCCESS'));
-
- // Set the redirect based on the task.
- switch ($this->input->getCmd('task'))
- {
- case 'apply':
- $this->app->redirect(Route::_('index.php?option=com_config&view=modules' . $moduleId . $redirect, false));
- break;
-
- case 'save':
- default:
-
- if (!empty($returnUri))
- {
- $redirect = base64_decode(urldecode($returnUri));
-
- // Don't redirect to an external URL.
- if (!Uri::isInternal($redirect))
- {
- $redirect = Uri::base();
- }
- }
- else
- {
- $redirect = Uri::base();
- }
-
- $this->setRedirect($redirect);
- break;
- }
- }
+ /**
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface|null $factory The factory.
+ * @param CMSApplication|null $app The Application for the dispatcher
+ * @param \Joomla\CMS\Input\Input|null $input The Input object for the request
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ $this->registerTask('apply', 'save');
+ }
+
+ /**
+ * Method to handle cancel
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function cancel()
+ {
+ // Redirect back to home(base) page
+ $this->setRedirect(Uri::base());
+ }
+
+ /**
+ * Method to save module editing.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ // Check if the user is authorized to do this.
+ $user = $this->app->getIdentity();
+
+ if (!$user->authorise('module.edit.frontend', 'com_modules.module.' . $this->input->get('id'))) {
+ $this->app->enqueueMessage(Text::_('JERROR_ALERTNOAUTHOR'), 'error');
+ $this->app->redirect('index.php');
+ }
+
+ // Set FTP credentials, if given.
+ ClientHelper::setCredentialsFromRequest('ftp');
+
+ // Get submitted module id
+ $moduleId = '&id=' . $this->input->getInt('id');
+
+ // Get returnUri
+ $returnUri = $this->input->post->get('return', null, 'base64');
+ $redirect = '';
+
+ if (!empty($returnUri)) {
+ $redirect = '&return=' . $returnUri;
+ }
+
+ /** @var AdministratorApplication $app */
+ $app = Factory::getContainer()->get(AdministratorApplication::class);
+
+ // Reset Uri cache.
+ Uri::reset();
+
+ // Get a document object
+ $document = $this->app->getDocument();
+
+ // Load application dependencies.
+ $app->loadLanguage($this->app->getLanguage());
+ $app->loadDocument($document);
+ $app->loadIdentity($user);
+
+ /** @var \Joomla\CMS\Dispatcher\ComponentDispatcher $dispatcher */
+ $dispatcher = $app->bootComponent('com_modules')->getDispatcher($app);
+
+ /** @var ModuleController $controllerClass */
+ $controllerClass = $dispatcher->getController('Module');
+
+ // Set backend required params
+ $document->setType('json');
+
+ // Execute backend controller
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_modules/forms');
+ $return = $controllerClass->save();
+
+ // Reset params back after requesting from service
+ $document->setType('html');
+
+ // Check the return value.
+ if ($return === false) {
+ // Save the data in the session.
+ $data = $this->input->post->get('jform', array(), 'array');
+
+ $this->app->setUserState('com_config.modules.global.data', $data);
+
+ // Save failed, go back to the screen and display a notice.
+ $this->app->enqueueMessage(Text::_('JERROR_SAVE_FAILED'));
+ $this->app->redirect(Route::_('index.php?option=com_config&view=modules' . $moduleId . $redirect, false));
+ }
+
+ // Redirect back to com_config display
+ $this->app->enqueueMessage(Text::_('COM_CONFIG_MODULES_SAVE_SUCCESS'));
+
+ // Set the redirect based on the task.
+ switch ($this->input->getCmd('task')) {
+ case 'apply':
+ $this->app->redirect(Route::_('index.php?option=com_config&view=modules' . $moduleId . $redirect, false));
+ break;
+
+ case 'save':
+ default:
+ if (!empty($returnUri)) {
+ $redirect = base64_decode(urldecode($returnUri));
+
+ // Don't redirect to an external URL.
+ if (!Uri::isInternal($redirect)) {
+ $redirect = Uri::base();
+ }
+ } else {
+ $redirect = Uri::base();
+ }
+
+ $this->setRedirect($redirect);
+ break;
+ }
+ }
}
diff --git a/components/com_config/src/Controller/TemplatesController.php b/components/com_config/src/Controller/TemplatesController.php
index 6cc36fcaad69a..1a02b72666a32 100644
--- a/components/com_config/src/Controller/TemplatesController.php
+++ b/components/com_config/src/Controller/TemplatesController.php
@@ -1,4 +1,5 @@
registerTask('apply', 'save');
- }
-
- /**
- * Method to handle cancel
- *
- * @return boolean True on success.
- *
- * @since 3.2
- */
- public function cancel()
- {
- // Redirect back to home(base) page
- $this->setRedirect(Uri::base());
- }
-
- /**
- * Method to save global configuration.
- *
- * @return boolean True on success.
- *
- * @since 3.2
- */
- public function save()
- {
- // Check for request forgeries.
- $this->checkToken();
-
- // Check if the user is authorized to do this.
- if (!$this->app->getIdentity()->authorise('core.admin'))
- {
- $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'));
-
- return false;
- }
-
- // Set FTP credentials, if given.
- ClientHelper::setCredentialsFromRequest('ftp');
-
- $app = $this->app;
-
- // Access backend com_templates
- $controllerClass = $app->bootComponent('com_templates')
- ->getMVCFactory()->createController('Style', 'Administrator', [], $app, $app->input);
-
- // Get a document object
- $document = $app->getDocument();
-
- // Set backend required params
- $document->setType('json');
- $this->input->set('id', $app->getTemplate(true)->id);
-
- // Execute backend controller
- $return = $controllerClass->save();
-
- // Reset params back after requesting from service
- $document->setType('html');
-
- // Check the return value.
- if ($return === false)
- {
- // Save failed, go back to the screen and display a notice.
- $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED'), 'error');
- $this->setRedirect(Route::_('index.php?option=com_config&view=templates', false));
-
- return false;
- }
-
- // Set the success message.
- $this->setMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'));
-
- // Redirect back to com_config display
- $this->setRedirect(Route::_('index.php?option=com_config&view=templates', false));
-
- return true;
- }
+ /**
+ * @param array $config An optional associative array of configuration settings.
+ * Recognized key values include 'name', 'default_task', 'model_path', and
+ * 'view_path' (this list is not meant to be comprehensive).
+ * @param MVCFactoryInterface|null $factory The factory.
+ * @param CMSApplication|null $app The Application for the dispatcher
+ * @param \Joomla\CMS\Input\Input|null $input The Input object for the request
+ *
+ * @since 1.6
+ */
+ public function __construct($config = array(), MVCFactoryInterface $factory = null, $app = null, $input = null)
+ {
+ parent::__construct($config, $factory, $app, $input);
+
+ // Apply, Save & New, and Save As copy should be standard on forms.
+ $this->registerTask('apply', 'save');
+ }
+
+ /**
+ * Method to handle cancel
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.2
+ */
+ public function cancel()
+ {
+ // Redirect back to home(base) page
+ $this->setRedirect(Uri::base());
+ }
+
+ /**
+ * Method to save global configuration.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.2
+ */
+ public function save()
+ {
+ // Check for request forgeries.
+ $this->checkToken();
+
+ // Check if the user is authorized to do this.
+ if (!$this->app->getIdentity()->authorise('core.admin')) {
+ $this->setRedirect('index.php', Text::_('JERROR_ALERTNOAUTHOR'));
+
+ return false;
+ }
+
+ // Set FTP credentials, if given.
+ ClientHelper::setCredentialsFromRequest('ftp');
+
+ $app = $this->app;
+
+ // Access backend com_templates
+ $controllerClass = $app->bootComponent('com_templates')
+ ->getMVCFactory()->createController('Style', 'Administrator', [], $app, $app->input);
+
+ // Get a document object
+ $document = $app->getDocument();
+
+ // Set backend required params
+ $document->setType('json');
+ $this->input->set('id', $app->getTemplate(true)->id);
+
+ // Execute backend controller
+ $return = $controllerClass->save();
+
+ // Reset params back after requesting from service
+ $document->setType('html');
+
+ // Check the return value.
+ if ($return === false) {
+ // Save failed, go back to the screen and display a notice.
+ $this->setMessage(Text::sprintf('JERROR_SAVE_FAILED'), 'error');
+ $this->setRedirect(Route::_('index.php?option=com_config&view=templates', false));
+
+ return false;
+ }
+
+ // Set the success message.
+ $this->setMessage(Text::_('COM_CONFIG_SAVE_SUCCESS'));
+
+ // Redirect back to com_config display
+ $this->setRedirect(Route::_('index.php?option=com_config&view=templates', false));
+
+ return true;
+ }
}
diff --git a/components/com_config/src/Dispatcher/Dispatcher.php b/components/com_config/src/Dispatcher/Dispatcher.php
index 336f520bad3c2..92570ffbd2603 100644
--- a/components/com_config/src/Dispatcher/Dispatcher.php
+++ b/components/com_config/src/Dispatcher/Dispatcher.php
@@ -1,4 +1,5 @@
input->getCmd('task', 'display');
- $view = $this->input->get('view');
- $user = $this->app->getIdentity();
+ $task = $this->input->getCmd('task', 'display');
+ $view = $this->input->get('view');
+ $user = $this->app->getIdentity();
- if (substr($task, 0, 8) === 'modules.' || $view === 'modules')
- {
- if (!$user->authorise('module.edit.frontend', 'com_modules.module.' . $this->input->get('id')))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
- elseif (!$user->authorise('core.admin'))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
+ if (substr($task, 0, 8) === 'modules.' || $view === 'modules') {
+ if (!$user->authorise('module.edit.frontend', 'com_modules.module.' . $this->input->get('id'))) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ } elseif (!$user->authorise('core.admin')) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
}
diff --git a/components/com_config/src/Model/ConfigModel.php b/components/com_config/src/Model/ConfigModel.php
index 381ff67a69fed..08fa3313ab953 100644
--- a/components/com_config/src/Model/ConfigModel.php
+++ b/components/com_config/src/Model/ConfigModel.php
@@ -1,4 +1,5 @@
loadForm('com_config.config', 'config', array('control' => 'jform', 'load_data' => $loadData));
+ /**
+ * Method to get a form object.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return mixed A JForm object on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_config.config', 'config', array('control' => 'jform', 'load_data' => $loadData));
- if (empty($form))
- {
- return false;
- }
+ if (empty($form)) {
+ return false;
+ }
- return $form;
- }
+ return $form;
+ }
}
diff --git a/components/com_config/src/Model/FormModel.php b/components/com_config/src/Model/FormModel.php
index ccdbf7116771d..992e45bcde129 100644
--- a/components/com_config/src/Model/FormModel.php
+++ b/components/com_config/src/Model/FormModel.php
@@ -1,4 +1,5 @@
getTable();
-
- if (!$table->load($pk))
- {
- throw new \RuntimeException($table->getError());
- }
-
- // Check if this is the user has previously checked out the row.
- if (!is_null($table->checked_out) && $table->checked_out != $user->get('id') && !$user->authorise('core.admin', 'com_checkin'))
- {
- throw new \RuntimeException($table->getError());
- }
-
- // Attempt to check the row in.
- if (!$table->checkIn($pk))
- {
- throw new \RuntimeException($table->getError());
- }
- }
-
- return true;
- }
-
- /**
- * Method to check-out a row for editing.
- *
- * @param integer $pk The numeric id of the primary key.
- *
- * @return boolean False on failure or error, true otherwise.
- *
- * @since 3.2
- */
- public function checkout($pk = null)
- {
- // Only attempt to check the row in if it exists.
- if ($pk)
- {
- $user = Factory::getUser();
-
- // Get an instance of the row to checkout.
- $table = $this->getTable();
-
- if (!$table->load($pk))
- {
- throw new \RuntimeException($table->getError());
- }
-
- // Check if this is the user having previously checked out the row.
- if (!is_null($table->checked_out) && $table->checked_out != $user->get('id'))
- {
- throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CHECKOUT_USER_MISMATCH'));
- }
-
- // Attempt to check the row out.
- if (!$table->checkOut($user->get('id'), $pk))
- {
- throw new \RuntimeException($table->getError());
- }
- }
-
- return true;
- }
-
- /**
- * Method to get a form object.
- *
- * @param string $name The name of the form.
- * @param string $source The form source. Can be XML string if file flag is set to false.
- * @param array $options Optional array of options for the form creation.
- * @param boolean $clear Optional argument to force load a new form.
- * @param string $xpath An optional xpath to search for the fields.
- *
- * @return mixed JForm object on success, False on error.
- *
- * @see JForm
- * @since 3.2
- */
- protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
- {
- // Handle the optional arguments.
- $options['control'] = ArrayHelper::getValue($options, 'control', false);
-
- // Create a signature hash.
- $hash = sha1($source . serialize($options));
-
- // Check if we can use a previously loaded form.
- if (isset($this->_forms[$hash]) && !$clear)
- {
- return $this->_forms[$hash];
- }
-
- // Register the paths for the form.
- Form::addFormPath(JPATH_SITE . '/components/com_config/forms');
- Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_config/forms');
-
- try
- {
- // Get the form.
- $form = Form::getInstance($name, $source, $options, false, $xpath);
-
- if (isset($options['load_data']) && $options['load_data'])
- {
- // Get the data for the form.
- $data = $this->loadFormData();
- }
- else
- {
- $data = array();
- }
-
- // Allow for additional modification of the form, and events to be triggered.
- // We pass the data because plugins may require it.
- $this->preprocessForm($form, $data);
-
- // Load the data into the form after the plugins have operated.
- $form->bind($data);
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage());
-
- return false;
- }
-
- // Store the form for later.
- $this->_forms[$hash] = $form;
-
- return $form;
- }
-
- /**
- * Method to get the data that should be injected in the form.
- *
- * @return array The default data is an empty array.
- *
- * @since 3.2
- */
- protected function loadFormData()
- {
- return array();
- }
-
- /**
- * Method to allow derived classes to preprocess the data.
- *
- * @param string $context The context identifier.
- * @param mixed &$data The data to be processed. It gets altered directly.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 3.2
- */
- protected function preprocessData($context, &$data, $group = 'content')
- {
- // Get the dispatcher and load the users plugins.
- PluginHelper::importPlugin('content');
-
- // Trigger the data preparation event.
- Factory::getApplication()->triggerEvent('onContentPrepareData', array($context, $data));
- }
-
- /**
- * Method to allow derived classes to preprocess the form.
- *
- * @param Form $form A Form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @see \Joomla\CMS\Form\FormField
- * @since 3.2
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- // Import the appropriate plugin group.
- PluginHelper::importPlugin($group);
-
- // Trigger the form preparation event.
- Factory::getApplication()->triggerEvent('onContentPrepareForm', array($form, $data));
- }
-
- /**
- * Method to validate the form data.
- *
- * @param Form $form The form to validate against.
- * @param array $data The data to validate.
- * @param string $group The name of the field group to validate.
- *
- * @return mixed Array of filtered data if valid, false otherwise.
- *
- * @see \Joomla\CMS\Form\FormRule
- * @see JFilterInput
- * @since 3.2
- */
- public function validate($form, $data, $group = null)
- {
- // Filter and validate the form data.
- $data = $form->filter($data);
- $return = $form->validate($data, $group);
-
- // Check for an error.
- if ($return instanceof \Exception)
- {
- Factory::getApplication()->enqueueMessage($return->getMessage(), 'error');
-
- return false;
- }
-
- // Check the validation results.
- if ($return === false)
- {
- // Get the validation messages from the form.
- foreach ($form->getErrors() as $message)
- {
- if ($message instanceof \Exception)
- {
- $message = $message->getMessage();
- }
-
- Factory::getApplication()->enqueueMessage($message, 'error');
- }
-
- return false;
- }
-
- return $data;
- }
+ /**
+ * Array of form objects.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $forms = array();
+
+ /**
+ * Method to checkin a row.
+ *
+ * @param integer $pk The numeric id of the primary key.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since 3.2
+ * @throws \RuntimeException
+ */
+ public function checkin($pk = null)
+ {
+ // Only attempt to check the row in if it exists.
+ if ($pk) {
+ $user = Factory::getUser();
+
+ // Get an instance of the row to checkin.
+ $table = $this->getTable();
+
+ if (!$table->load($pk)) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ // Check if this is the user has previously checked out the row.
+ if (!is_null($table->checked_out) && $table->checked_out != $user->get('id') && !$user->authorise('core.admin', 'com_checkin')) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ // Attempt to check the row in.
+ if (!$table->checkIn($pk)) {
+ throw new \RuntimeException($table->getError());
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to check-out a row for editing.
+ *
+ * @param integer $pk The numeric id of the primary key.
+ *
+ * @return boolean False on failure or error, true otherwise.
+ *
+ * @since 3.2
+ */
+ public function checkout($pk = null)
+ {
+ // Only attempt to check the row in if it exists.
+ if ($pk) {
+ $user = Factory::getUser();
+
+ // Get an instance of the row to checkout.
+ $table = $this->getTable();
+
+ if (!$table->load($pk)) {
+ throw new \RuntimeException($table->getError());
+ }
+
+ // Check if this is the user having previously checked out the row.
+ if (!is_null($table->checked_out) && $table->checked_out != $user->get('id')) {
+ throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_CHECKOUT_USER_MISMATCH'));
+ }
+
+ // Attempt to check the row out.
+ if (!$table->checkOut($user->get('id'), $pk)) {
+ throw new \RuntimeException($table->getError());
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to get a form object.
+ *
+ * @param string $name The name of the form.
+ * @param string $source The form source. Can be XML string if file flag is set to false.
+ * @param array $options Optional array of options for the form creation.
+ * @param boolean $clear Optional argument to force load a new form.
+ * @param string $xpath An optional xpath to search for the fields.
+ *
+ * @return mixed JForm object on success, False on error.
+ *
+ * @see JForm
+ * @since 3.2
+ */
+ protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
+ {
+ // Handle the optional arguments.
+ $options['control'] = ArrayHelper::getValue($options, 'control', false);
+
+ // Create a signature hash.
+ $hash = sha1($source . serialize($options));
+
+ // Check if we can use a previously loaded form.
+ if (isset($this->_forms[$hash]) && !$clear) {
+ return $this->_forms[$hash];
+ }
+
+ // Register the paths for the form.
+ Form::addFormPath(JPATH_SITE . '/components/com_config/forms');
+ Form::addFormPath(JPATH_ADMINISTRATOR . '/components/com_config/forms');
+
+ try {
+ // Get the form.
+ $form = Form::getInstance($name, $source, $options, false, $xpath);
+
+ if (isset($options['load_data']) && $options['load_data']) {
+ // Get the data for the form.
+ $data = $this->loadFormData();
+ } else {
+ $data = array();
+ }
+
+ // Allow for additional modification of the form, and events to be triggered.
+ // We pass the data because plugins may require it.
+ $this->preprocessForm($form, $data);
+
+ // Load the data into the form after the plugins have operated.
+ $form->bind($data);
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage());
+
+ return false;
+ }
+
+ // Store the form for later.
+ $this->_forms[$hash] = $form;
+
+ return $form;
+ }
+
+ /**
+ * Method to get the data that should be injected in the form.
+ *
+ * @return array The default data is an empty array.
+ *
+ * @since 3.2
+ */
+ protected function loadFormData()
+ {
+ return array();
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the data.
+ *
+ * @param string $context The context identifier.
+ * @param mixed &$data The data to be processed. It gets altered directly.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function preprocessData($context, &$data, $group = 'content')
+ {
+ // Get the dispatcher and load the users plugins.
+ PluginHelper::importPlugin('content');
+
+ // Trigger the data preparation event.
+ Factory::getApplication()->triggerEvent('onContentPrepareData', array($context, $data));
+ }
+
+ /**
+ * Method to allow derived classes to preprocess the form.
+ *
+ * @param Form $form A Form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @see \Joomla\CMS\Form\FormField
+ * @since 3.2
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ // Import the appropriate plugin group.
+ PluginHelper::importPlugin($group);
+
+ // Trigger the form preparation event.
+ Factory::getApplication()->triggerEvent('onContentPrepareForm', array($form, $data));
+ }
+
+ /**
+ * Method to validate the form data.
+ *
+ * @param Form $form The form to validate against.
+ * @param array $data The data to validate.
+ * @param string $group The name of the field group to validate.
+ *
+ * @return mixed Array of filtered data if valid, false otherwise.
+ *
+ * @see \Joomla\CMS\Form\FormRule
+ * @see JFilterInput
+ * @since 3.2
+ */
+ public function validate($form, $data, $group = null)
+ {
+ // Filter and validate the form data.
+ $data = $form->filter($data);
+ $return = $form->validate($data, $group);
+
+ // Check for an error.
+ if ($return instanceof \Exception) {
+ Factory::getApplication()->enqueueMessage($return->getMessage(), 'error');
+
+ return false;
+ }
+
+ // Check the validation results.
+ if ($return === false) {
+ // Get the validation messages from the form.
+ foreach ($form->getErrors() as $message) {
+ if ($message instanceof \Exception) {
+ $message = $message->getMessage();
+ }
+
+ Factory::getApplication()->enqueueMessage($message, 'error');
+ }
+
+ return false;
+ }
+
+ return $data;
+ }
}
diff --git a/components/com_config/src/Model/ModulesModel.php b/components/com_config/src/Model/ModulesModel.php
index 9c8871187b0f7..5a79c609bca8d 100644
--- a/components/com_config/src/Model/ModulesModel.php
+++ b/components/com_config/src/Model/ModulesModel.php
@@ -1,4 +1,5 @@
input->getInt('id');
-
- $this->setState('module.id', $pk);
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data Data for the form.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form A Form object on success, false on failure
- *
- * @since 3.2
- */
- public function getForm($data = array(), $loadData = true)
- {
- // Get the form.
- $form = $this->loadForm('com_config.modules', 'modules', array('control' => 'jform', 'load_data' => $loadData));
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to preprocess the form
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group The name of the plugin group to import (defaults to "content").
- *
- * @return void
- *
- * @since 3.2
- * @throws \Exception if there is an error loading the form.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $lang = Factory::getLanguage();
- $module = $this->getState()->get('module.name');
- $basePath = JPATH_BASE;
-
- $formFile = Path::clean($basePath . '/modules/' . $module . '/' . $module . '.xml');
-
- // Load the core and/or local language file(s).
- $lang->load($module, $basePath)
- || $lang->load($module, $basePath . '/modules/' . $module);
-
- if (file_exists($formFile))
- {
- // Get the module form.
- if (!$form->loadFile($formFile, false, '//config'))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
- }
-
- // Load the default advanced params
- Form::addFormPath(JPATH_BASE . '/components/com_config/model/form');
- $form->loadFile('modules_advanced', false);
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
-
- /**
- * Method to get list of module positions in current template
- *
- * @return array
- *
- * @since 3.2
- */
- public function getPositions()
- {
- $lang = Factory::getLanguage();
- $templateName = Factory::getApplication()->getTemplate();
-
- // Load templateDetails.xml file
- $path = Path::clean(JPATH_BASE . '/templates/' . $templateName . '/templateDetails.xml');
- $currentTemplatePositions = array();
-
- if (file_exists($path))
- {
- $xml = simplexml_load_file($path);
-
- if (isset($xml->positions[0]))
- {
- // Load language files
- $lang->load('tpl_' . $templateName . '.sys', JPATH_BASE)
- || $lang->load('tpl_' . $templateName . '.sys', JPATH_BASE . '/templates/' . $templateName);
-
- foreach ($xml->positions[0] as $position)
- {
- $value = (string) $position;
- $text = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'TPL_' . strtoupper($templateName) . '_POSITION_' . strtoupper($value));
-
- // Construct list of positions
- $currentTemplatePositions[] = self::createOption($value, Text::_($text) . ' [' . $value . ']');
- }
- }
- }
-
- $templateGroups = array();
-
- // Add an empty value to be able to deselect a module position
- $option = self::createOption();
- $templateGroups[''] = self::createOptionGroup('', array($option));
-
- $templateGroups[$templateName] = self::createOptionGroup($templateName, $currentTemplatePositions);
-
- // Add custom position to options
- $customGroupText = Text::_('COM_MODULES_CUSTOM_POSITION');
-
- $editPositions = true;
- $customPositions = self::getActivePositions(0, $editPositions);
- $templateGroups[$customGroupText] = self::createOptionGroup($customGroupText, $customPositions);
-
- return $templateGroups;
- }
-
- /**
- * Get a list of modules positions
- *
- * @param integer $clientId Client ID
- * @param boolean $editPositions Allow to edit the positions
- *
- * @return array A list of positions
- *
- * @since 3.6.3
- */
- public static function getActivePositions($clientId, $editPositions = false)
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select('DISTINCT position')
- ->from($db->quoteName('#__modules'))
- ->where($db->quoteName('client_id') . ' = ' . (int) $clientId)
- ->order($db->quoteName('position'));
-
- $db->setQuery($query);
-
- try
- {
- $positions = $db->loadColumn();
- $positions = is_array($positions) ? $positions : array();
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return;
- }
-
- // Build the list
- $options = array();
-
- foreach ($positions as $position)
- {
- if (!$position && !$editPositions)
- {
- $options[] = HTMLHelper::_('select.option', 'none', ':: ' . Text::_('JNONE') . ' ::');
- }
- else
- {
- $options[] = HTMLHelper::_('select.option', $position, $position);
- }
- }
-
- return $options;
- }
-
- /**
- * Create and return a new Option
- *
- * @param string $value The option value [optional]
- * @param string $text The option text [optional]
- *
- * @return object The option as an object (stdClass instance)
- *
- * @since 3.6.3
- */
- private static function createOption($value = '', $text = '')
- {
- if (empty($text))
- {
- $text = $value;
- }
-
- $option = new \stdClass;
- $option->value = $value;
- $option->text = $text;
-
- return $option;
- }
-
- /**
- * Create and return a new Option Group
- *
- * @param string $label Value and label for group [optional]
- * @param array $options Array of options to insert into group [optional]
- *
- * @return array Return the new group as an array
- *
- * @since 3.6.3
- */
- private static function createOptionGroup($label = '', $options = array())
- {
- $group = array();
- $group['value'] = $label;
- $group['text'] = $label;
- $group['items'] = $options;
-
- return $group;
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ protected function populateState()
+ {
+ $app = Factory::getApplication();
+
+ // Load the User state.
+ $pk = $app->input->getInt('id');
+
+ $this->setState('module.id', $pk);
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data Data for the form.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form A Form object on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ // Get the form.
+ $form = $this->loadForm('com_config.modules', 'modules', array('control' => 'jform', 'load_data' => $loadData));
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to preprocess the form
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group The name of the plugin group to import (defaults to "content").
+ *
+ * @return void
+ *
+ * @since 3.2
+ * @throws \Exception if there is an error loading the form.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $lang = Factory::getLanguage();
+ $module = $this->getState()->get('module.name');
+ $basePath = JPATH_BASE;
+
+ $formFile = Path::clean($basePath . '/modules/' . $module . '/' . $module . '.xml');
+
+ // Load the core and/or local language file(s).
+ $lang->load($module, $basePath)
+ || $lang->load($module, $basePath . '/modules/' . $module);
+
+ if (file_exists($formFile)) {
+ // Get the module form.
+ if (!$form->loadFile($formFile, false, '//config')) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+ }
+
+ // Load the default advanced params
+ Form::addFormPath(JPATH_BASE . '/components/com_config/model/form');
+ $form->loadFile('modules_advanced', false);
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
+
+ /**
+ * Method to get list of module positions in current template
+ *
+ * @return array
+ *
+ * @since 3.2
+ */
+ public function getPositions()
+ {
+ $lang = Factory::getLanguage();
+ $templateName = Factory::getApplication()->getTemplate();
+
+ // Load templateDetails.xml file
+ $path = Path::clean(JPATH_BASE . '/templates/' . $templateName . '/templateDetails.xml');
+ $currentTemplatePositions = array();
+
+ if (file_exists($path)) {
+ $xml = simplexml_load_file($path);
+
+ if (isset($xml->positions[0])) {
+ // Load language files
+ $lang->load('tpl_' . $templateName . '.sys', JPATH_BASE)
+ || $lang->load('tpl_' . $templateName . '.sys', JPATH_BASE . '/templates/' . $templateName);
+
+ foreach ($xml->positions[0] as $position) {
+ $value = (string) $position;
+ $text = preg_replace('/[^a-zA-Z0-9_\-]/', '_', 'TPL_' . strtoupper($templateName) . '_POSITION_' . strtoupper($value));
+
+ // Construct list of positions
+ $currentTemplatePositions[] = self::createOption($value, Text::_($text) . ' [' . $value . ']');
+ }
+ }
+ }
+
+ $templateGroups = array();
+
+ // Add an empty value to be able to deselect a module position
+ $option = self::createOption();
+ $templateGroups[''] = self::createOptionGroup('', array($option));
+
+ $templateGroups[$templateName] = self::createOptionGroup($templateName, $currentTemplatePositions);
+
+ // Add custom position to options
+ $customGroupText = Text::_('COM_MODULES_CUSTOM_POSITION');
+
+ $editPositions = true;
+ $customPositions = self::getActivePositions(0, $editPositions);
+ $templateGroups[$customGroupText] = self::createOptionGroup($customGroupText, $customPositions);
+
+ return $templateGroups;
+ }
+
+ /**
+ * Get a list of modules positions
+ *
+ * @param integer $clientId Client ID
+ * @param boolean $editPositions Allow to edit the positions
+ *
+ * @return array A list of positions
+ *
+ * @since 3.6.3
+ */
+ public static function getActivePositions($clientId, $editPositions = false)
+ {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('DISTINCT position')
+ ->from($db->quoteName('#__modules'))
+ ->where($db->quoteName('client_id') . ' = ' . (int) $clientId)
+ ->order($db->quoteName('position'));
+
+ $db->setQuery($query);
+
+ try {
+ $positions = $db->loadColumn();
+ $positions = is_array($positions) ? $positions : array();
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return;
+ }
+
+ // Build the list
+ $options = array();
+
+ foreach ($positions as $position) {
+ if (!$position && !$editPositions) {
+ $options[] = HTMLHelper::_('select.option', 'none', ':: ' . Text::_('JNONE') . ' ::');
+ } else {
+ $options[] = HTMLHelper::_('select.option', $position, $position);
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Create and return a new Option
+ *
+ * @param string $value The option value [optional]
+ * @param string $text The option text [optional]
+ *
+ * @return object The option as an object (stdClass instance)
+ *
+ * @since 3.6.3
+ */
+ private static function createOption($value = '', $text = '')
+ {
+ if (empty($text)) {
+ $text = $value;
+ }
+
+ $option = new \stdClass();
+ $option->value = $value;
+ $option->text = $text;
+
+ return $option;
+ }
+
+ /**
+ * Create and return a new Option Group
+ *
+ * @param string $label Value and label for group [optional]
+ * @param array $options Array of options to insert into group [optional]
+ *
+ * @return array Return the new group as an array
+ *
+ * @since 3.6.3
+ */
+ private static function createOptionGroup($label = '', $options = array())
+ {
+ $group = array();
+ $group['value'] = $label;
+ $group['text'] = $label;
+ $group['items'] = $options;
+
+ return $group;
+ }
}
diff --git a/components/com_config/src/Model/TemplatesModel.php b/components/com_config/src/Model/TemplatesModel.php
index f9f901679f054..9f3793b8b51dc 100644
--- a/components/com_config/src/Model/TemplatesModel.php
+++ b/components/com_config/src/Model/TemplatesModel.php
@@ -1,4 +1,5 @@
setState('params', ComponentHelper::getParams('com_templates'));
- }
-
- /**
- * Method to get the record form.
- *
- * @param array $data An optional array of data for the form to interrogate.
- * @param boolean $loadData True if the form is to load its own data (default case), false if not.
- *
- * @return Form|bool A JForm object on success, false on failure
- *
- * @since 3.2
- */
- public function getForm($data = array(), $loadData = true)
- {
- try
- {
- // Get the form.
- $form = $this->loadForm('com_config.templates', 'templates', array('load_data' => $loadData));
-
- $data = array();
- $this->preprocessForm($form, $data);
-
- // Load the data into the form
- $form->bind($data);
- }
- catch (\Exception $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage());
-
- return false;
- }
-
- if (empty($form))
- {
- return false;
- }
-
- return $form;
- }
-
- /**
- * Method to preprocess the form
- *
- * @param Form $form A form object.
- * @param mixed $data The data expected for the form.
- * @param string $group Plugin group to load
- *
- * @return void
- *
- * @since 3.2
- * @throws \Exception if there is an error in the form event.
- */
- protected function preprocessForm(Form $form, $data, $group = 'content')
- {
- $lang = Factory::getLanguage();
-
- $template = Factory::getApplication()->getTemplate();
-
- // Load the core and/or local language file(s).
- $lang->load('tpl_' . $template, JPATH_BASE)
- || $lang->load('tpl_' . $template, JPATH_BASE . '/templates/' . $template);
-
- // Look for com_config.xml, which contains fields to display
- $formFile = Path::clean(JPATH_BASE . '/templates/' . $template . '/com_config.xml');
-
- if (!file_exists($formFile))
- {
- // If com_config.xml not found, fall back to templateDetails.xml
- $formFile = Path::clean(JPATH_BASE . '/templates/' . $template . '/templateDetails.xml');
- }
-
- // Get the template form.
- if (file_exists($formFile) && !$form->loadFile($formFile, false, '//config'))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Attempt to load the xml file.
- if (!$xml = simplexml_load_file($formFile))
- {
- throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
- }
-
- // Trigger the default form events.
- parent::preprocessForm($form, $data, $group);
- }
+ /**
+ * Method to auto-populate the model state.
+ *
+ * Note. Calling getState in this method will result in recursion.
+ *
+ * @return null
+ *
+ * @since 3.2
+ */
+ protected function populateState()
+ {
+ parent::populateState();
+
+ $this->setState('params', ComponentHelper::getParams('com_templates'));
+ }
+
+ /**
+ * Method to get the record form.
+ *
+ * @param array $data An optional array of data for the form to interrogate.
+ * @param boolean $loadData True if the form is to load its own data (default case), false if not.
+ *
+ * @return Form|bool A JForm object on success, false on failure
+ *
+ * @since 3.2
+ */
+ public function getForm($data = array(), $loadData = true)
+ {
+ try {
+ // Get the form.
+ $form = $this->loadForm('com_config.templates', 'templates', array('load_data' => $loadData));
+
+ $data = array();
+ $this->preprocessForm($form, $data);
+
+ // Load the data into the form
+ $form->bind($data);
+ } catch (\Exception $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage());
+
+ return false;
+ }
+
+ if (empty($form)) {
+ return false;
+ }
+
+ return $form;
+ }
+
+ /**
+ * Method to preprocess the form
+ *
+ * @param Form $form A form object.
+ * @param mixed $data The data expected for the form.
+ * @param string $group Plugin group to load
+ *
+ * @return void
+ *
+ * @since 3.2
+ * @throws \Exception if there is an error in the form event.
+ */
+ protected function preprocessForm(Form $form, $data, $group = 'content')
+ {
+ $lang = Factory::getLanguage();
+
+ $template = Factory::getApplication()->getTemplate();
+
+ // Load the core and/or local language file(s).
+ $lang->load('tpl_' . $template, JPATH_BASE)
+ || $lang->load('tpl_' . $template, JPATH_BASE . '/templates/' . $template);
+
+ // Look for com_config.xml, which contains fields to display
+ $formFile = Path::clean(JPATH_BASE . '/templates/' . $template . '/com_config.xml');
+
+ if (!file_exists($formFile)) {
+ // If com_config.xml not found, fall back to templateDetails.xml
+ $formFile = Path::clean(JPATH_BASE . '/templates/' . $template . '/templateDetails.xml');
+ }
+
+ // Get the template form.
+ if (file_exists($formFile) && !$form->loadFile($formFile, false, '//config')) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Attempt to load the xml file.
+ if (!$xml = simplexml_load_file($formFile)) {
+ throw new \Exception(Text::_('JERROR_LOADFILE_FAILED'));
+ }
+
+ // Trigger the default form events.
+ parent::preprocessForm($form, $data, $group);
+ }
}
diff --git a/components/com_config/src/Service/Router.php b/components/com_config/src/Service/Router.php
index a2935a9425925..0346fc65425da 100644
--- a/components/com_config/src/Service/Router.php
+++ b/components/com_config/src/Service/Router.php
@@ -1,4 +1,5 @@
registerView(new RouterViewConfiguration('config'));
- $this->registerView(new RouterViewConfiguration('templates'));
+ /**
+ * Config Component router constructor
+ *
+ * @param SiteApplication $app The application object
+ * @param AbstractMenu $menu The menu object to work with
+ */
+ public function __construct(SiteApplication $app, AbstractMenu $menu)
+ {
+ $this->registerView(new RouterViewConfiguration('config'));
+ $this->registerView(new RouterViewConfiguration('templates'));
- parent::__construct($app, $menu);
+ parent::__construct($app, $menu);
- $this->attachRule(new MenuRules($this));
- $this->attachRule(new StandardRules($this));
- $this->attachRule(new NomenuRules($this));
- }
+ $this->attachRule(new MenuRules($this));
+ $this->attachRule(new StandardRules($this));
+ $this->attachRule(new NomenuRules($this));
+ }
}
diff --git a/components/com_config/src/View/Config/HtmlView.php b/components/com_config/src/View/Config/HtmlView.php
index 6cb0b6ff1636e..eef98b1ee4a5c 100644
--- a/components/com_config/src/View/Config/HtmlView.php
+++ b/components/com_config/src/View/Config/HtmlView.php
@@ -1,4 +1,5 @@
getCurrentUser();
- $this->userIsSuperAdmin = $user->authorise('core.admin');
-
- // Access backend com_config
- $requestController = new RequestController;
-
- // Execute backend controller
- $serviceData = json_decode($requestController->getJson(), true);
-
- $form = $this->getForm();
-
- if ($form)
- {
- $form->bind($serviceData);
- }
-
- $this->form = $form;
- $this->data = $serviceData;
-
- $this->_prepareDocument();
-
- parent::display($tpl);
- }
-
- /**
- * Prepares the document.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function _prepareDocument()
- {
- $params = Factory::getApplication()->getParams();
-
- // Because the application sets a default page title, we need to get it
- // right from the menu item itself
-
- $this->setDocumentTitle($params->get('page_title', ''));
-
- if ($params->get('menu-meta_description'))
- {
- $this->document->setDescription($params->get('menu-meta_description'));
- }
-
- if ($params->get('robots'))
- {
- $this->document->setMetaData('robots', $params->get('robots'));
- }
-
- // Escape strings for HTML output
- $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx', ''));
- $this->params = &$params;
- }
+ /**
+ * The form object
+ *
+ * @var \Joomla\CMS\Form\Form
+ *
+ * @since 3.2
+ */
+ public $form;
+
+ /**
+ * The data to be displayed in the form
+ *
+ * @var array
+ *
+ * @since 3.2
+ */
+ public $data;
+
+ /**
+ * Is the current user a super administrator?
+ *
+ * @var boolean
+ *
+ * @since 3.2
+ */
+ protected $userIsSuperAdmin;
+
+ /**
+ * The page class suffix
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ protected $pageclass_sfx = '';
+
+ /**
+ * The page parameters
+ *
+ * @var \Joomla\Registry\Registry|null
+ *
+ * @since 4.0.0
+ */
+ protected $params = null;
+
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $user = $this->getCurrentUser();
+ $this->userIsSuperAdmin = $user->authorise('core.admin');
+
+ // Access backend com_config
+ $requestController = new RequestController();
+
+ // Execute backend controller
+ $serviceData = json_decode($requestController->getJson(), true);
+
+ $form = $this->getForm();
+
+ if ($form) {
+ $form->bind($serviceData);
+ }
+
+ $this->form = $form;
+ $this->data = $serviceData;
+
+ $this->_prepareDocument();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Prepares the document.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function _prepareDocument()
+ {
+ $params = Factory::getApplication()->getParams();
+
+ // Because the application sets a default page title, we need to get it
+ // right from the menu item itself
+
+ $this->setDocumentTitle($params->get('page_title', ''));
+
+ if ($params->get('menu-meta_description')) {
+ $this->document->setDescription($params->get('menu-meta_description'));
+ }
+
+ if ($params->get('robots')) {
+ $this->document->setMetaData('robots', $params->get('robots'));
+ }
+
+ // Escape strings for HTML output
+ $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx', ''));
+ $this->params = &$params;
+ }
}
diff --git a/components/com_config/src/View/Modules/HtmlView.php b/components/com_config/src/View/Modules/HtmlView.php
index e5e3f5be92925..660254dedf553 100644
--- a/components/com_config/src/View/Modules/HtmlView.php
+++ b/components/com_config/src/View/Modules/HtmlView.php
@@ -1,4 +1,5 @@
getLanguage();
- $lang->load('', JPATH_ADMINISTRATOR, $lang->getTag());
- $lang->load('com_modules', JPATH_ADMINISTRATOR, $lang->getTag());
+ /**
+ * Execute and display a template script.
+ *
+ * @param string $tpl The name of the template file to parse; automatically searches through the template paths.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $lang = Factory::getApplication()->getLanguage();
+ $lang->load('', JPATH_ADMINISTRATOR, $lang->getTag());
+ $lang->load('com_modules', JPATH_ADMINISTRATOR, $lang->getTag());
- // @todo Move and clean up
- $module = (new \Joomla\Component\Modules\Administrator\Model\ModuleModel)->getItem(Factory::getApplication()->input->getInt('id'));
+ // @todo Move and clean up
+ $module = (new \Joomla\Component\Modules\Administrator\Model\ModuleModel())->getItem(Factory::getApplication()->input->getInt('id'));
- $moduleData = $module->getProperties();
- unset($moduleData['xml']);
+ $moduleData = $module->getProperties();
+ unset($moduleData['xml']);
- /** @var \Joomla\Component\Config\Site\Model\ModulesModel $model */
- $model = $this->getModel();
+ /** @var \Joomla\Component\Config\Site\Model\ModulesModel $model */
+ $model = $this->getModel();
- // Need to add module name to the state of model
- $model->getState()->set('module.name', $moduleData['module']);
+ // Need to add module name to the state of model
+ $model->getState()->set('module.name', $moduleData['module']);
- /** @var Form form */
- $this->form = $this->get('form');
- $this->positions = $this->get('positions');
- $this->item = $moduleData;
+ /** @var Form form */
+ $this->form = $this->get('form');
+ $this->positions = $this->get('positions');
+ $this->item = $moduleData;
- if ($this->form)
- {
- $this->form->bind($moduleData);
- }
+ if ($this->form) {
+ $this->form->bind($moduleData);
+ }
- $this->_prepareDocument();
+ $this->_prepareDocument();
- parent::display($tpl);
- }
+ parent::display($tpl);
+ }
- /**
- * Prepares the document.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function _prepareDocument()
- {
- // There is no menu item for this so we have to use the title from the component
- $this->setDocumentTitle(Text::_('COM_CONFIG_MODULES_SETTINGS_TITLE'));
- }
+ /**
+ * Prepares the document.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function _prepareDocument()
+ {
+ // There is no menu item for this so we have to use the title from the component
+ $this->setDocumentTitle(Text::_('COM_CONFIG_MODULES_SETTINGS_TITLE'));
+ }
}
diff --git a/components/com_config/src/View/Templates/HtmlView.php b/components/com_config/src/View/Templates/HtmlView.php
index 39478d9b5d734..3d0175b2b6fc9 100644
--- a/components/com_config/src/View/Templates/HtmlView.php
+++ b/components/com_config/src/View/Templates/HtmlView.php
@@ -1,4 +1,5 @@
getCurrentUser();
- $this->userIsSuperAdmin = $user->authorise('core.admin');
-
- $app = Factory::getApplication();
-
- $app->input->set('id', $app->getTemplate(true)->id);
-
- /** @var MVCFactory $factory */
- $factory = $app->bootComponent('com_templates')->getMVCFactory();
-
- $view = $factory->createView('Style', 'Administrator', 'Json');
- $view->setModel($factory->createModel('Style', 'Administrator'), true);
-
- $view->document = $this->document;
-
- $json = $view->display();
-
- // Execute backend controller
- $serviceData = json_decode($json, true);
-
- // Access backend com_config
- $requestController = new RequestController;
-
- // Execute backend controller
- $configData = json_decode($requestController->getJson(), true);
-
- $data = array_merge($configData, $serviceData);
-
- /** @var Form $form */
- $form = $this->getForm();
-
- if ($form)
- {
- $form->bind($data);
- }
-
- $this->form = $form;
-
- $this->data = $serviceData;
-
- $this->_prepareDocument();
-
- parent::display($tpl);
- }
-
- /**
- * Prepares the document.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function _prepareDocument()
- {
- $params = Factory::getApplication()->getParams();
-
- // Because the application sets a default page title, we need to get it
- // right from the menu item itself
- $this->setDocumentTitle($params->get('page_title', ''));
-
- if ($params->get('menu-meta_description'))
- {
- $this->document->setDescription($params->get('menu-meta_description'));
- }
-
- if ($params->get('robots'))
- {
- $this->document->setMetaData('robots', $params->get('robots'));
- }
-
- // Escape strings for HTML output
- $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx', ''));
- $this->params = &$params;
- }
+ /**
+ * The data to be displayed in the form
+ *
+ * @var array
+ *
+ * @since 3.2
+ */
+ public $item;
+
+ /**
+ * The form object
+ *
+ * @var Form
+ *
+ * @since 3.2
+ */
+ public $form;
+
+ /**
+ * Is the current user a super administrator?
+ *
+ * @var boolean
+ *
+ * @since 3.2
+ */
+ protected $userIsSuperAdmin;
+
+ /**
+ * The page class suffix
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ protected $pageclass_sfx = '';
+
+ /**
+ * The page parameters
+ *
+ * @var \Joomla\Registry\Registry|null
+ *
+ * @since 4.0.0
+ */
+ protected $params = null;
+
+ /**
+ * Method to render the view.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function display($tpl = null)
+ {
+ $user = $this->getCurrentUser();
+ $this->userIsSuperAdmin = $user->authorise('core.admin');
+
+ $app = Factory::getApplication();
+
+ $app->input->set('id', $app->getTemplate(true)->id);
+
+ /** @var MVCFactory $factory */
+ $factory = $app->bootComponent('com_templates')->getMVCFactory();
+
+ $view = $factory->createView('Style', 'Administrator', 'Json');
+ $view->setModel($factory->createModel('Style', 'Administrator'), true);
+
+ $view->document = $this->document;
+
+ $json = $view->display();
+
+ // Execute backend controller
+ $serviceData = json_decode($json, true);
+
+ // Access backend com_config
+ $requestController = new RequestController();
+
+ // Execute backend controller
+ $configData = json_decode($requestController->getJson(), true);
+
+ $data = array_merge($configData, $serviceData);
+
+ /** @var Form $form */
+ $form = $this->getForm();
+
+ if ($form) {
+ $form->bind($data);
+ }
+
+ $this->form = $form;
+
+ $this->data = $serviceData;
+
+ $this->_prepareDocument();
+
+ parent::display($tpl);
+ }
+
+ /**
+ * Prepares the document.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function _prepareDocument()
+ {
+ $params = Factory::getApplication()->getParams();
+
+ // Because the application sets a default page title, we need to get it
+ // right from the menu item itself
+ $this->setDocumentTitle($params->get('page_title', ''));
+
+ if ($params->get('menu-meta_description')) {
+ $this->document->setDescription($params->get('menu-meta_description'));
+ }
+
+ if ($params->get('robots')) {
+ $this->document->setMetaData('robots', $params->get('robots'));
+ }
+
+ // Escape strings for HTML output
+ $this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx', ''));
+ $this->params = &$params;
+ }
}
diff --git a/components/com_config/tmpl/config/default.php b/components/com_config/tmpl/config/default.php
index 95398edb0da8b..f7f88c3e1e1c0 100644
--- a/components/com_config/tmpl/config/default.php
+++ b/components/com_config/tmpl/config/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_config.config')
- ->useScript('inlinehelp');
+ ->useScript('form.validate')
+ ->useScript('com_config.config')
+ ->useScript('inlinehelp');
?>
params->get('show_page_heading')) : ?>
-
+
diff --git a/components/com_config/tmpl/config/default_metadata.php b/components/com_config/tmpl/config/default_metadata.php
index 4daf35c503e09..f361d59281319 100644
--- a/components/com_config/tmpl/config/default_metadata.php
+++ b/components/com_config/tmpl/config/default_metadata.php
@@ -1,4 +1,5 @@
-
+
- form->getFieldset('metadata') as $field) : ?>
-
- label; ?>
- input; ?>
- description): ?>
-
- description) ?>
-
-
-
-
+ form->getFieldset('metadata') as $field) : ?>
+
+ label; ?>
+ input; ?>
+ description) : ?>
+
+ description) ?>
+
+
+
+
diff --git a/components/com_config/tmpl/config/default_seo.php b/components/com_config/tmpl/config/default_seo.php
index 0c26e03d712d3..911390985f711 100644
--- a/components/com_config/tmpl/config/default_seo.php
+++ b/components/com_config/tmpl/config/default_seo.php
@@ -1,4 +1,5 @@
-
+
- form->getFieldset('seo') as $field) : ?>
-
- label; ?>
- input; ?>
- description): ?>
-
- description) ?>
-
-
-
-
+ form->getFieldset('seo') as $field) : ?>
+
+ label; ?>
+ input; ?>
+ description) : ?>
+
+ description) ?>
+
+
+
+
diff --git a/components/com_config/tmpl/config/default_site.php b/components/com_config/tmpl/config/default_site.php
index 1e13994ea8c9e..571b7de73cb3b 100644
--- a/components/com_config/tmpl/config/default_site.php
+++ b/components/com_config/tmpl/config/default_site.php
@@ -1,4 +1,5 @@
-
+
- form->getFieldset('site') as $field) : ?>
-
- label; ?>
- input; ?>
- description): ?>
-
- description) ?>
-
-
-
-
+ form->getFieldset('site') as $field) : ?>
+
+ label; ?>
+ input; ?>
+ description) : ?>
+
+ description) ?>
+
+
+
+
diff --git a/components/com_config/tmpl/modules/default.php b/components/com_config/tmpl/modules/default.php
index fe76134ce3394..6fc48f5163366 100644
--- a/components/com_config/tmpl/modules/default.php
+++ b/components/com_config/tmpl/modules/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_config.modules');
+ ->useScript('form.validate')
+ ->useScript('com_config.modules');
$editorText = false;
$moduleXml = JPATH_SITE . '/modules/' . $this->item['module'] . '/' . $this->item['module'] . '.xml';
-if (File::exists($moduleXml))
-{
- $xml = simplexml_load_file($moduleXml);
+if (File::exists($moduleXml)) {
+ $xml = simplexml_load_file($moduleXml);
- if (isset($xml->customContent))
- {
- $editorText = true;
- }
+ if (isset($xml->customContent)) {
+ $editorText = true;
+ }
}
// If multi-language site, make language read-only
-if (Multilanguage::isEnabled())
-{
- $this->form->setFieldAttribute('language', 'readonly', 'true');
+if (Multilanguage::isEnabled()) {
+ $this->form->setFieldAttribute('language', 'readonly', 'true');
}
?>
diff --git a/components/com_config/tmpl/modules/default_options.php b/components/com_config/tmpl/modules/default_options.php
index 28c4c73007070..e22325152df33 100644
--- a/components/com_config/tmpl/modules/default_options.php
+++ b/components/com_config/tmpl/modules/default_options.php
@@ -1,4 +1,5 @@
$fieldSet) :
-
-$label = !empty($fieldSet->label) ? $fieldSet->label : 'COM_MODULES_' . strtoupper($name) . '_FIELDSET_LABEL';
-$class = isset($fieldSet->class) && !empty($fieldSet->class) ? $fieldSet->class : '';
+ $label = !empty($fieldSet->label) ? $fieldSet->label : 'COM_MODULES_' . strtoupper($name) . '_FIELDSET_LABEL';
+ $class = isset($fieldSet->class) && !empty($fieldSet->class) ? $fieldSet->class : '';
-if (isset($fieldSet->description) && trim($fieldSet->description)) :
-echo '' . $this->escape(Text::_($fieldSet->description)) . '
';
-endif;
-?>
-
+ if (isset($fieldSet->description) && trim($fieldSet->description)) :
+ echo '' . $this->escape(Text::_($fieldSet->description)) . '
';
+ endif;
+ ?>
+
-form->getFieldset($name) as $field) : ?>
-
-
-
- item['module'] === 'mod_menu' && $field->getAttribute('name') === 'menutype') : ?>
- readonly = true; ?>
-
- renderField(); ?>
-
-
-
+ form->getFieldset($name) as $field) : ?>
+
+
+ item['module'] === 'mod_menu' && $field->getAttribute('name') === 'menutype') : ?>
+ readonly = true; ?>
+
+ renderField(); ?>
+
+
+
-
+
diff --git a/components/com_config/tmpl/templates/default.php b/components/com_config/tmpl/templates/default.php
index 2c0a86135de7e..c3531496379c3 100644
--- a/components/com_config/tmpl/templates/default.php
+++ b/components/com_config/tmpl/templates/default.php
@@ -1,4 +1,5 @@
document->getWebAssetManager();
$wa->useScript('keepalive')
- ->useScript('form.validate')
- ->useScript('com_config.templates');
+ ->useScript('form.validate')
+ ->useScript('com_config.templates');
?>
params->get('show_page_heading')) : ?>
-
+
will check for Joomla updates
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'core:check-updates';
+
+ /**
+ * Stores the Update Information
+ *
+ * @var UpdateModel
+ * @since 4.0.0
+ */
+ private $updateInfo;
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% will check for Joomla updates
\nUsage: php %command.full_name% ";
- $this->setDescription('Check for Joomla updates');
- $this->setHelp($help);
- }
-
- /**
- * Retrieves Update Information
- *
- * @return mixed
- *
- * @since 4.0.0
- */
- private function getUpdateInformationFromModel()
- {
- $app = $this->getApplication();
- $updatemodel = $app->bootComponent('com_joomlaupdate')->getMVCFactory($app)->createModel('Update', 'Administrator');
- $updatemodel->purge();
- $updatemodel->refreshUpdates(true);
-
- return $updatemodel;
- }
-
- /**
- * Gets the Update Information
- *
- * @return mixed
- *
- * @since 4.0.0
- */
- public function getUpdateInfo()
- {
- if (!$this->updateInfo)
- {
- $this->setUpdateInfo();
- }
-
- return $this->updateInfo;
- }
-
- /**
- * Sets the Update Information
- *
- * @param null $info stores update Information
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setUpdateInfo($info = null): void
- {
- if (!$info)
- {
- $this->updateInfo = $this->getUpdateInformationFromModel();
- }
- else
- {
- $this->updateInfo = $info;
- }
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $symfonyStyle = new SymfonyStyle($input, $output);
-
- $model = $this->getUpdateInfo();
- $data = $model->getUpdateInformation();
- $symfonyStyle->title('Joomla! Updates');
-
- if (!$data['hasUpdate'])
- {
- $symfonyStyle->success('You already have the latest Joomla version ' . $data['latest']);
-
- return Command::SUCCESS;
- }
-
- $symfonyStyle->note('New Joomla Version ' . $data['latest'] . ' is available.');
-
- if (!isset($data['object']->downloadurl->_data))
- {
- $symfonyStyle->warning('We cannot find an update URL');
- }
-
- return Command::SUCCESS;
- }
+ $this->setDescription('Check for Joomla updates');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Retrieves Update Information
+ *
+ * @return mixed
+ *
+ * @since 4.0.0
+ */
+ private function getUpdateInformationFromModel()
+ {
+ $app = $this->getApplication();
+ $updatemodel = $app->bootComponent('com_joomlaupdate')->getMVCFactory($app)->createModel('Update', 'Administrator');
+ $updatemodel->purge();
+ $updatemodel->refreshUpdates(true);
+
+ return $updatemodel;
+ }
+
+ /**
+ * Gets the Update Information
+ *
+ * @return mixed
+ *
+ * @since 4.0.0
+ */
+ public function getUpdateInfo()
+ {
+ if (!$this->updateInfo) {
+ $this->setUpdateInfo();
+ }
+
+ return $this->updateInfo;
+ }
+
+ /**
+ * Sets the Update Information
+ *
+ * @param null $info stores update Information
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setUpdateInfo($info = null): void
+ {
+ if (!$info) {
+ $this->updateInfo = $this->getUpdateInformationFromModel();
+ } else {
+ $this->updateInfo = $info;
+ }
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $model = $this->getUpdateInfo();
+ $data = $model->getUpdateInformation();
+ $symfonyStyle->title('Joomla! Updates');
+
+ if (!$data['hasUpdate']) {
+ $symfonyStyle->success('You already have the latest Joomla version ' . $data['latest']);
+
+ return Command::SUCCESS;
+ }
+
+ $symfonyStyle->note('New Joomla Version ' . $data['latest'] . ' is available.');
+
+ if (!isset($data['object']->downloadurl->_data)) {
+ $symfonyStyle->warning('We cannot find an update URL');
+ }
+
+ return Command::SUCCESS;
+ }
}
diff --git a/libraries/src/Console/CheckUpdatesCommand.php b/libraries/src/Console/CheckUpdatesCommand.php
index a1948642eb4d8..f68b9054f4e60 100644
--- a/libraries/src/Console/CheckUpdatesCommand.php
+++ b/libraries/src/Console/CheckUpdatesCommand.php
@@ -1,4 +1,5 @@
title('Fetching Extension Updates');
+ $symfonyStyle->title('Fetching Extension Updates');
- // Get the update cache time
- $component = ComponentHelper::getComponent('com_installer');
+ // Get the update cache time
+ $component = ComponentHelper::getComponent('com_installer');
- $cache_timeout = 3600 * (int) $component->getParams()->get('cachetimeout', 6);
+ $cache_timeout = 3600 * (int) $component->getParams()->get('cachetimeout', 6);
- // Find all updates
- $ret = Updater::getInstance()->findUpdates(0, $cache_timeout);
+ // Find all updates
+ $ret = Updater::getInstance()->findUpdates(0, $cache_timeout);
- if ($ret)
- {
- $symfonyStyle->note('There are available updates to apply');
- $symfonyStyle->success('Check complete.');
- }
- else
- {
- $symfonyStyle->success('There are no available updates');
- }
+ if ($ret) {
+ $symfonyStyle->note('There are available updates to apply');
+ $symfonyStyle->success('Check complete.');
+ } else {
+ $symfonyStyle->success('There are no available updates');
+ }
- return Command::SUCCESS;
- }
+ return Command::SUCCESS;
+ }
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% command checks for pending extension updates
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% command checks for pending extension updates
\nUsage: php %command.full_name% ";
- $this->setDescription('Check for pending extension updates');
- $this->setHelp($help);
- }
+ $this->setDescription('Check for pending extension updates');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/CleanCacheCommand.php b/libraries/src/Console/CleanCacheCommand.php
index 55fd1e3aac439..9777356c5a42d 100644
--- a/libraries/src/Console/CleanCacheCommand.php
+++ b/libraries/src/Console/CleanCacheCommand.php
@@ -1,4 +1,5 @@
title('Cleaning System Cache');
-
- $cache = $this->getApplication()->bootComponent('com_cache')->getMVCFactory();
- /** @var Joomla\Component\Cache\Administrator\Model\CacheModel $model */
- $model = $cache->createModel('Cache', 'Administrator', ['ignore_request' => true]);
-
- if ($input->getArgument('expired'))
- {
- if (!$model->purge())
- {
- $symfonyStyle->error('Expired Cache not cleaned');
-
- return Command::FAILURE;
- }
-
- $symfonyStyle->success('Expired Cache cleaned');
-
- return Command::SUCCESS;
- }
-
- if (!$model->clean())
- {
- $symfonyStyle->error('Cache not cleaned');
-
- return Command::FAILURE;
- }
-
- $symfonyStyle->success('Cache cleaned');
-
- return Command::SUCCESS;
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% will clear entries from the system cache
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'cache:clean';
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $symfonyStyle->title('Cleaning System Cache');
+
+ $cache = $this->getApplication()->bootComponent('com_cache')->getMVCFactory();
+ /** @var Joomla\Component\Cache\Administrator\Model\CacheModel $model */
+ $model = $cache->createModel('Cache', 'Administrator', ['ignore_request' => true]);
+
+ if ($input->getArgument('expired')) {
+ if (!$model->purge()) {
+ $symfonyStyle->error('Expired Cache not cleaned');
+
+ return Command::FAILURE;
+ }
+
+ $symfonyStyle->success('Expired Cache cleaned');
+
+ return Command::SUCCESS;
+ }
+
+ if (!$model->clean()) {
+ $symfonyStyle->error('Cache not cleaned');
+
+ return Command::FAILURE;
+ }
+
+ $symfonyStyle->success('Cache cleaned');
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% will clear entries from the system cache
\nUsage: php %command.full_name% ";
- $this->addArgument('expired', InputArgument::OPTIONAL, 'will clear expired entries from the system cache');
- $this->setDescription('Clean cache entries');
- $this->setHelp($help);
- }
+ $this->addArgument('expired', InputArgument::OPTIONAL, 'will clear expired entries from the system cache');
+ $this->setDescription('Clean cache entries');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/DeleteUserCommand.php b/libraries/src/Console/DeleteUserCommand.php
index 704a3b299e7a6..cf6c72d5891ad 100644
--- a/libraries/src/Console/DeleteUserCommand.php
+++ b/libraries/src/Console/DeleteUserCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $this->ioStyle->title('Delete users');
-
- $this->username = $this->getStringFromOption('username', 'Please enter a username');
-
- $userId = UserHelper::getUserId($this->username);
- $db = $this->getDatabase();
-
- if (empty($userId))
- {
- $this->ioStyle->error($this->username . ' does not exist!');
-
- return Command::FAILURE;
- }
-
- if ($input->isInteractive() && !$this->ioStyle->confirm('Are you sure you want to delete this user?', false))
- {
- $this->ioStyle->note('User not deleted');
-
- return Command::SUCCESS;
- }
-
- $groups = UserHelper::getUserGroups($userId);
- $user = User::getInstance($userId);
-
- if ($user->block == 0)
- {
- foreach ($groups as $groupId)
- {
- if (Access::checkGroup($groupId, 'core.admin'))
- {
- $queryUser = $db->getQuery(true);
- $queryUser->select('COUNT(*)')
- ->from($db->quoteName('#__users', 'u'))
- ->leftJoin(
- $db->quoteName('#__user_usergroup_map', 'g'),
- '(' . $db->quoteName('u.id') . ' = ' . $db->quoteName('g.user_id') . ')'
- )
- ->where($db->quoteName('g.group_id') . " = :groupId")
- ->where($db->quoteName('u.block') . " = 0")
- ->bind(':groupId', $groupId, ParameterType::INTEGER);
-
- $db->setQuery($queryUser);
- $activeSuperUser = $db->loadResult();
-
- if ($activeSuperUser < 2)
- {
- $this->ioStyle->error("You can't delete the last active Super User");
-
- return Command::FAILURE;
- }
- }
- }
- }
-
- // Trigger delete of user
- $result = $user->delete();
-
- if (!$result)
- {
- $this->ioStyle->error("Can't remove " . $this->username . ' from usertable');
-
- return Command::FAILURE;
- }
-
- $this->ioStyle->success('User ' . $this->username . ' deleted!');
-
- return Command::SUCCESS;
- }
-
- /**
- * Method to get a value from option
- *
- * @param string $option set the option name
- *
- * @param string $question set the question if user enters no value to option
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function getStringFromOption($option, $question): string
- {
- $answer = (string) $this->getApplication()->getConsoleInput()->getOption($option);
-
- while (!$answer)
- {
- $answer = (string) $this->ioStyle->ask($question);
- }
-
- return $answer;
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% deletes a user
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'user:delete';
+
+ /**
+ * SymfonyStyle Object
+ * @var object
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Stores the Input Object
+ * @var object
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * The username
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ private $username;
+
+ /**
+ * Command constructor.
+ *
+ * @param DatabaseInterface $db The database
+ *
+ * @since 4.2.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $this->ioStyle->title('Delete users');
+
+ $this->username = $this->getStringFromOption('username', 'Please enter a username');
+
+ $userId = UserHelper::getUserId($this->username);
+ $db = $this->getDatabase();
+
+ if (empty($userId)) {
+ $this->ioStyle->error($this->username . ' does not exist!');
+
+ return Command::FAILURE;
+ }
+
+ if ($input->isInteractive() && !$this->ioStyle->confirm('Are you sure you want to delete this user?', false)) {
+ $this->ioStyle->note('User not deleted');
+
+ return Command::SUCCESS;
+ }
+
+ $groups = UserHelper::getUserGroups($userId);
+ $user = User::getInstance($userId);
+
+ if ($user->block == 0) {
+ foreach ($groups as $groupId) {
+ if (Access::checkGroup($groupId, 'core.admin')) {
+ $queryUser = $db->getQuery(true);
+ $queryUser->select('COUNT(*)')
+ ->from($db->quoteName('#__users', 'u'))
+ ->leftJoin(
+ $db->quoteName('#__user_usergroup_map', 'g'),
+ '(' . $db->quoteName('u.id') . ' = ' . $db->quoteName('g.user_id') . ')'
+ )
+ ->where($db->quoteName('g.group_id') . " = :groupId")
+ ->where($db->quoteName('u.block') . " = 0")
+ ->bind(':groupId', $groupId, ParameterType::INTEGER);
+
+ $db->setQuery($queryUser);
+ $activeSuperUser = $db->loadResult();
+
+ if ($activeSuperUser < 2) {
+ $this->ioStyle->error("You can't delete the last active Super User");
+
+ return Command::FAILURE;
+ }
+ }
+ }
+ }
+
+ // Trigger delete of user
+ $result = $user->delete();
+
+ if (!$result) {
+ $this->ioStyle->error("Can't remove " . $this->username . ' from usertable');
+
+ return Command::FAILURE;
+ }
+
+ $this->ioStyle->success('User ' . $this->username . ' deleted!');
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Method to get a value from option
+ *
+ * @param string $option set the option name
+ *
+ * @param string $question set the question if user enters no value to option
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function getStringFromOption($option, $question): string
+ {
+ $answer = (string) $this->getApplication()->getConsoleInput()->getOption($option);
+
+ while (!$answer) {
+ $answer = (string) $this->ioStyle->ask($question);
+ }
+
+ return $answer;
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% deletes a user
\nUsage: php %command.full_name% ";
- $this->setDescription('Delete a user');
- $this->addOption('username', null, InputOption::VALUE_OPTIONAL, 'username');
- $this->setHelp($help);
- }
+ $this->setDescription('Delete a user');
+ $this->addOption('username', null, InputOption::VALUE_OPTIONAL, 'username');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/ExtensionDiscoverCommand.php b/libraries/src/Console/ExtensionDiscoverCommand.php
index 61f488b4653b5..76f323074a128 100644
--- a/libraries/src/Console/ExtensionDiscoverCommand.php
+++ b/libraries/src/Console/ExtensionDiscoverCommand.php
@@ -1,4 +1,5 @@
cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% is used to discover extensions
+ /**
+ * The default command name
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:discover';
+
+ /**
+ * Stores the Input Object
+ *
+ * @var InputInterface
+ *
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ *
+ * @var SymfonyStyle
+ *
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% is used to discover extensions
\nUsage:
\n php %command.full_name% ";
- $this->setDescription('Discover extensions');
- $this->setHelp($help);
- }
-
- /**
- * Used for discovering extensions
- *
- * @return integer The count of discovered extensions
- *
- * @throws \Exception
- *
- * @since 4.0.0
- */
- public function processDiscover(): int
- {
- $app = $this->getApplication();
-
- $mvcFactory = $app->bootComponent('com_installer')->getMVCFactory();
-
- $model = $mvcFactory->createModel('Discover', 'Administrator');
-
- return $model->discover();
- }
-
- /**
- * Used for finding the text for the note
- *
- * @param int $count The count of installed Extensions
- *
- * @return string The text for the note
- *
- * @since 4.0.0
- */
- public function getNote(int $count): string
- {
- if ($count < 1)
- {
- return 'No extensions were discovered.';
- }
- elseif ($count === 1)
- {
- return $count . ' extension has been discovered.';
- }
- else
- {
- return $count . ' extensions have been discovered.';
- }
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $count = $this->processDiscover();
-
- $this->ioStyle->note($this->getNote($count));
-
- return Command::SUCCESS;
- }
+ $this->setDescription('Discover extensions');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Used for discovering extensions
+ *
+ * @return integer The count of discovered extensions
+ *
+ * @throws \Exception
+ *
+ * @since 4.0.0
+ */
+ public function processDiscover(): int
+ {
+ $app = $this->getApplication();
+
+ $mvcFactory = $app->bootComponent('com_installer')->getMVCFactory();
+
+ $model = $mvcFactory->createModel('Discover', 'Administrator');
+
+ return $model->discover();
+ }
+
+ /**
+ * Used for finding the text for the note
+ *
+ * @param int $count The count of installed Extensions
+ *
+ * @return string The text for the note
+ *
+ * @since 4.0.0
+ */
+ public function getNote(int $count): string
+ {
+ if ($count < 1) {
+ return 'No extensions were discovered.';
+ } elseif ($count === 1) {
+ return $count . ' extension has been discovered.';
+ } else {
+ return $count . ' extensions have been discovered.';
+ }
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $count = $this->processDiscover();
+
+ $this->ioStyle->note($this->getNote($count));
+
+ return Command::SUCCESS;
+ }
}
diff --git a/libraries/src/Console/ExtensionDiscoverInstallCommand.php b/libraries/src/Console/ExtensionDiscoverInstallCommand.php
index 7d03ef5944c4f..6b7551f94dc4f 100644
--- a/libraries/src/Console/ExtensionDiscoverInstallCommand.php
+++ b/libraries/src/Console/ExtensionDiscoverInstallCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- private function configureIO(InputInterface $input, OutputInterface $output): void
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $this->addOption('eid', null, InputOption::VALUE_REQUIRED, 'The ID of the extension to discover');
-
- $help = "%command.name% is used to discover extensions
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:discover:install';
+
+ /**
+ * Stores the Input Object
+ *
+ * @var InputInterface
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ *
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Instantiate the command.
+ *
+ * @param DatabaseInterface $db Database connector
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $this->addOption('eid', null, InputOption::VALUE_REQUIRED, 'The ID of the extension to discover');
+
+ $help = "%command.name% is used to discover extensions
\nYou can provide the following option to the command:
\n --eid: The ID of the extension
\n If you do not provide a ID all discovered extensions are installed.
\nUsage:
\n php %command.full_name% --eid= ";
- $this->setDescription('Install discovered extensions');
- $this->setHelp($help);
- }
-
- /**
- * Used for discovering extensions
- *
- * @param string $eid Id of the extension
- *
- * @return integer The count of installed extensions
- *
- * @throws \Exception
- * @since 4.0.0
- */
- public function processDiscover($eid): int
- {
- $jInstaller = new Installer;
- $jInstaller->setDatabase($this->db);
- $count = 0;
-
- if ($eid === -1)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName(['extension_id']))
- ->from($db->quoteName('#__extensions'))
- ->where($db->quoteName('state') . ' = -1');
- $db->setQuery($query);
- $eidsToDiscover = $db->loadObjectList();
-
- foreach ($eidsToDiscover as $eidToDiscover)
- {
- if (!$jInstaller->discover_install($eidToDiscover->extension_id))
- {
- return -1;
- }
-
- $count++;
- }
-
- if (empty($eidsToDiscover))
- {
- return 0;
- }
- }
- else
- {
- if ($jInstaller->discover_install($eid))
- {
- return 1;
- }
- else
- {
- return -1;
- }
- }
-
- return $count;
- }
-
- /**
- * Used for finding the text for the note
- *
- * @param int $count Number of extensions to install
- * @param int $eid ID of the extension or -1 if no special
- *
- * @return string The text for the note
- *
- * @since 4.0.0
- */
- public function getNote(int $count, int $eid): string
- {
- if ($count < 0 && $eid >= 0)
- {
- return 'Unable to install the extension with ID ' . $eid;
- }
- elseif ($count < 0 && $eid < 0)
- {
- return 'Unable to install discovered extensions.';
- }
- elseif ($count === 0)
- {
- return 'There are no pending discovered extensions for install. Perhaps you need to run extension:discover first?';
- }
- elseif ($count === 1 && $eid > 0)
- {
- return 'Extension with ID ' . $eid . ' installed successfully.';
- }
- elseif ($count === 1 && $eid < 0)
- {
- return $count . ' discovered extension has been installed.';
- }
- elseif ($count > 1 && $eid < 0)
- {
- return $count . ' discovered extensions have been installed.';
- }
- else
- {
- return 'The return value is not possible and has to be checked.';
- }
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- if ($eid = $this->cliInput->getOption('eid'))
- {
- $result = $this->processDiscover($eid);
-
- if ($result === -1)
- {
- $this->ioStyle->error($this->getNote($result, $eid));
-
- return Command::FAILURE;
- }
- else
- {
- $this->ioStyle->success($this->getNote($result, $eid));
-
- return Command::SUCCESS;
- }
- }
- else
- {
- $result = $this->processDiscover(-1);
-
- if ($result < 0)
- {
- $this->ioStyle->error($this->getNote($result, -1));
-
- return Command::FAILURE;
- }
- elseif ($result === 0)
- {
- $this->ioStyle->note($this->getNote($result, -1));
-
- return Command::SUCCESS;
- }
-
- else
- {
- $this->ioStyle->note($this->getNote($result, -1));
-
- return Command::SUCCESS;
- }
- }
- }
+ $this->setDescription('Install discovered extensions');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Used for discovering extensions
+ *
+ * @param string $eid Id of the extension
+ *
+ * @return integer The count of installed extensions
+ *
+ * @throws \Exception
+ * @since 4.0.0
+ */
+ public function processDiscover($eid): int
+ {
+ $jInstaller = new Installer();
+ $jInstaller->setDatabase($this->db);
+ $count = 0;
+
+ if ($eid === -1) {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName(['extension_id']))
+ ->from($db->quoteName('#__extensions'))
+ ->where($db->quoteName('state') . ' = -1');
+ $db->setQuery($query);
+ $eidsToDiscover = $db->loadObjectList();
+
+ foreach ($eidsToDiscover as $eidToDiscover) {
+ if (!$jInstaller->discover_install($eidToDiscover->extension_id)) {
+ return -1;
+ }
+
+ $count++;
+ }
+
+ if (empty($eidsToDiscover)) {
+ return 0;
+ }
+ } else {
+ if ($jInstaller->discover_install($eid)) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ return $count;
+ }
+
+ /**
+ * Used for finding the text for the note
+ *
+ * @param int $count Number of extensions to install
+ * @param int $eid ID of the extension or -1 if no special
+ *
+ * @return string The text for the note
+ *
+ * @since 4.0.0
+ */
+ public function getNote(int $count, int $eid): string
+ {
+ if ($count < 0 && $eid >= 0) {
+ return 'Unable to install the extension with ID ' . $eid;
+ } elseif ($count < 0 && $eid < 0) {
+ return 'Unable to install discovered extensions.';
+ } elseif ($count === 0) {
+ return 'There are no pending discovered extensions for install. Perhaps you need to run extension:discover first?';
+ } elseif ($count === 1 && $eid > 0) {
+ return 'Extension with ID ' . $eid . ' installed successfully.';
+ } elseif ($count === 1 && $eid < 0) {
+ return $count . ' discovered extension has been installed.';
+ } elseif ($count > 1 && $eid < 0) {
+ return $count . ' discovered extensions have been installed.';
+ } else {
+ return 'The return value is not possible and has to be checked.';
+ }
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ if ($eid = $this->cliInput->getOption('eid')) {
+ $result = $this->processDiscover($eid);
+
+ if ($result === -1) {
+ $this->ioStyle->error($this->getNote($result, $eid));
+
+ return Command::FAILURE;
+ } else {
+ $this->ioStyle->success($this->getNote($result, $eid));
+
+ return Command::SUCCESS;
+ }
+ } else {
+ $result = $this->processDiscover(-1);
+
+ if ($result < 0) {
+ $this->ioStyle->error($this->getNote($result, -1));
+
+ return Command::FAILURE;
+ } elseif ($result === 0) {
+ $this->ioStyle->note($this->getNote($result, -1));
+
+ return Command::SUCCESS;
+ } else {
+ $this->ioStyle->note($this->getNote($result, -1));
+
+ return Command::SUCCESS;
+ }
+ }
+ }
}
diff --git a/libraries/src/Console/ExtensionDiscoverListCommand.php b/libraries/src/Console/ExtensionDiscoverListCommand.php
index 9c5edaecdaebe..b2974e5584107 100644
--- a/libraries/src/Console/ExtensionDiscoverListCommand.php
+++ b/libraries/src/Console/ExtensionDiscoverListCommand.php
@@ -1,4 +1,5 @@
%command.name% is used to list all extensions that could be installed via discoverinstall
+ /**
+ * The default command name
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:discover:list';
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% is used to list all extensions that could be installed via discoverinstall
\nUsage:
\n php %command.full_name% ";
- $this->setDescription('List discovered extensions');
- $this->setHelp($help);
- }
-
- /**
- * Filters the extension state
- *
- * @param array $extensions The Extensions
- * @param string $state The Extension state
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function filterExtensionsBasedOnState($extensions, $state): array
- {
- $filteredExtensions = [];
-
- foreach ($extensions as $key => $extension)
- {
- if ($extension['state'] === $state)
- {
- $filteredExtensions[] = $extension;
- }
- }
-
- return $filteredExtensions;
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $extensions = $this->getExtensions();
- $state = -1;
-
- $discovered_extensions = $this->filterExtensionsBasedOnState($extensions, $state);
-
- if (empty($discovered_extensions))
- {
- $this->ioStyle->note("There are no pending discovered extensions to install. Perhaps you need to run extension:discover first?");
-
- return Command::SUCCESS;
- }
-
- $discovered_extensions = $this->getExtensionsNameAndId($discovered_extensions);
-
- $this->ioStyle->title('Discovered extensions.');
- $this->ioStyle->table(['Name', 'Extension ID', 'Version', 'Type', 'Active'], $discovered_extensions);
-
- return Command::SUCCESS;
- }
+ $this->setDescription('List discovered extensions');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Filters the extension state
+ *
+ * @param array $extensions The Extensions
+ * @param string $state The Extension state
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function filterExtensionsBasedOnState($extensions, $state): array
+ {
+ $filteredExtensions = [];
+
+ foreach ($extensions as $key => $extension) {
+ if ($extension['state'] === $state) {
+ $filteredExtensions[] = $extension;
+ }
+ }
+
+ return $filteredExtensions;
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $extensions = $this->getExtensions();
+ $state = -1;
+
+ $discovered_extensions = $this->filterExtensionsBasedOnState($extensions, $state);
+
+ if (empty($discovered_extensions)) {
+ $this->ioStyle->note("There are no pending discovered extensions to install. Perhaps you need to run extension:discover first?");
+
+ return Command::SUCCESS;
+ }
+
+ $discovered_extensions = $this->getExtensionsNameAndId($discovered_extensions);
+
+ $this->ioStyle->title('Discovered extensions.');
+ $this->ioStyle->table(['Name', 'Extension ID', 'Version', 'Type', 'Active'], $discovered_extensions);
+
+ return Command::SUCCESS;
+ }
}
diff --git a/libraries/src/Console/ExtensionInstallCommand.php b/libraries/src/Console/ExtensionInstallCommand.php
index cf3758ecdfc53..457185f0d4978 100644
--- a/libraries/src/Console/ExtensionInstallCommand.php
+++ b/libraries/src/Console/ExtensionInstallCommand.php
@@ -1,4 +1,5 @@
cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path to the extension');
- $this->addOption('url', null, InputOption::VALUE_REQUIRED, 'The url to the extension');
-
- $help = "%command.name% is used to install extensions
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:install';
+
+ /**
+ * Stores the Input Object
+ * @var InputInterface
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Exit Code For installation failure
+ * @since 4.0.0
+ */
+ public const INSTALLATION_FAILED = 1;
+
+ /**
+ * Exit Code For installation Success
+ * @since 4.0.0
+ */
+ public const INSTALLATION_SUCCESSFUL = 0;
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $this->addOption('path', null, InputOption::VALUE_REQUIRED, 'The path to the extension');
+ $this->addOption('url', null, InputOption::VALUE_REQUIRED, 'The url to the extension');
+
+ $help = "%command.name% is used to install extensions
\nYou must provide one of the following options to the command:
\n --path: The path on your local filesystem to the install package
\n --url: The URL from where the install package should be downloaded
@@ -96,127 +95,119 @@ protected function configure(): void
\n php %command.full_name% --path=
\n php %command.full_name% --url= ";
- $this->setDescription('Install an extension from a URL or from a path');
- $this->setHelp($help);
- }
-
- /**
- * Used for installing extension from a path
- *
- * @param string $path Path to the extension zip file
- *
- * @return boolean
- *
- * @since 4.0.0
- *
- * @throws \Exception
- */
- public function processPathInstallation($path): bool
- {
- if (!file_exists($path))
- {
- $this->ioStyle->warning('The file path specified does not exist.');
-
- return false;
- }
-
- $tmpPath = $this->getApplication()->get('tmp_path');
- $tmpPath = $tmpPath . '/' . basename($path);
- $package = InstallerHelper::unpack($path, true);
-
- if ($package['type'] === false)
- {
- return false;
- }
-
- $jInstaller = Installer::getInstance();
- $result = $jInstaller->install($package['extractdir']);
- InstallerHelper::cleanupInstall($tmpPath, $package['extractdir']);
-
- return $result;
- }
-
-
- /**
- * Used for installing extension from a URL
- *
- * @param string $url URL to the extension zip file
- *
- * @return boolean
- *
- * @since 4.0.0
- *
- * @throws \Exception
- */
- public function processUrlInstallation($url): bool
- {
- $filename = InstallerHelper::downloadPackage($url);
-
- $tmpPath = $this->getApplication()->get('tmp_path');
-
- $path = $tmpPath . '/' . basename($filename);
- $package = InstallerHelper::unpack($path, true);
-
- if ($package['type'] === false)
- {
- return false;
- }
-
- $jInstaller = new Installer;
- $result = $jInstaller->install($package['extractdir']);
- InstallerHelper::cleanupInstall($path, $package['extractdir']);
-
- return $result;
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @throws \Exception
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- if ($path = $this->cliInput->getOption('path'))
- {
- $result = $this->processPathInstallation($path);
-
- if (!$result)
- {
- $this->ioStyle->error('Unable to install extension');
-
- return self::INSTALLATION_FAILED;
- }
-
- $this->ioStyle->success('Extension installed successfully.');
-
- return self::INSTALLATION_SUCCESSFUL;
- }
- elseif ($url = $this->cliInput->getOption('url'))
- {
- $result = $this->processUrlInstallation($url);
-
- if (!$result)
- {
- $this->ioStyle->error('Unable to install extension');
-
- return self::INSTALLATION_FAILED;
- }
-
- $this->ioStyle->success('Extension installed successfully.');
-
- return self::INSTALLATION_SUCCESSFUL;
- }
-
- $this->ioStyle->error('Invalid argument supplied for command.');
-
- return self::INSTALLATION_FAILED;
- }
+ $this->setDescription('Install an extension from a URL or from a path');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Used for installing extension from a path
+ *
+ * @param string $path Path to the extension zip file
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ *
+ * @throws \Exception
+ */
+ public function processPathInstallation($path): bool
+ {
+ if (!file_exists($path)) {
+ $this->ioStyle->warning('The file path specified does not exist.');
+
+ return false;
+ }
+
+ $tmpPath = $this->getApplication()->get('tmp_path');
+ $tmpPath = $tmpPath . '/' . basename($path);
+ $package = InstallerHelper::unpack($path, true);
+
+ if ($package['type'] === false) {
+ return false;
+ }
+
+ $jInstaller = Installer::getInstance();
+ $result = $jInstaller->install($package['extractdir']);
+ InstallerHelper::cleanupInstall($tmpPath, $package['extractdir']);
+
+ return $result;
+ }
+
+
+ /**
+ * Used for installing extension from a URL
+ *
+ * @param string $url URL to the extension zip file
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ *
+ * @throws \Exception
+ */
+ public function processUrlInstallation($url): bool
+ {
+ $filename = InstallerHelper::downloadPackage($url);
+
+ $tmpPath = $this->getApplication()->get('tmp_path');
+
+ $path = $tmpPath . '/' . basename($filename);
+ $package = InstallerHelper::unpack($path, true);
+
+ if ($package['type'] === false) {
+ return false;
+ }
+
+ $jInstaller = new Installer();
+ $result = $jInstaller->install($package['extractdir']);
+ InstallerHelper::cleanupInstall($path, $package['extractdir']);
+
+ return $result;
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @throws \Exception
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ if ($path = $this->cliInput->getOption('path')) {
+ $result = $this->processPathInstallation($path);
+
+ if (!$result) {
+ $this->ioStyle->error('Unable to install extension');
+
+ return self::INSTALLATION_FAILED;
+ }
+
+ $this->ioStyle->success('Extension installed successfully.');
+
+ return self::INSTALLATION_SUCCESSFUL;
+ } elseif ($url = $this->cliInput->getOption('url')) {
+ $result = $this->processUrlInstallation($url);
+
+ if (!$result) {
+ $this->ioStyle->error('Unable to install extension');
+
+ return self::INSTALLATION_FAILED;
+ }
+
+ $this->ioStyle->success('Extension installed successfully.');
+
+ return self::INSTALLATION_SUCCESSFUL;
+ }
+
+ $this->ioStyle->error('Invalid argument supplied for command.');
+
+ return self::INSTALLATION_FAILED;
+ }
}
diff --git a/libraries/src/Console/ExtensionRemoveCommand.php b/libraries/src/Console/ExtensionRemoveCommand.php
index 3b0dfe088cb11..8244c6f2335e2 100644
--- a/libraries/src/Console/ExtensionRemoveCommand.php
+++ b/libraries/src/Console/ExtensionRemoveCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- private function configureIO(InputInterface $input, OutputInterface $output): void
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- $language = Factory::getLanguage();
- $language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
- $language->load('', JPATH_ADMINISTRATOR, null, true);
- $language->load('com_installer', JPATH_ADMINISTRATOR, null, false, false)||
- $language->load('com_installer', JPATH_ADMINISTRATOR, null, true);
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $this->addArgument(
- 'extensionId',
- InputArgument::REQUIRED,
- 'ID of extension to be removed (run extension:list command to check)'
- );
-
- $help = "%command.name% is used to uninstall extensions.
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:remove';
+
+ /**
+ * @var InputInterface
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Exit Code for extensions remove abort
+ * @since 4.0.0
+ */
+ public const REMOVE_ABORT = 3;
+
+ /**
+ * Exit Code for extensions remove failure
+ * @since 4.0.0
+ */
+ public const REMOVE_FAILED = 1;
+
+ /**
+ * Exit Code for invalid response
+ * @since 4.0.0
+ */
+ public const REMOVE_INVALID_RESPONSE = 5;
+
+ /**
+ * Exit Code for invalid type
+ * @since 4.0.0
+ */
+ public const REMOVE_INVALID_TYPE = 6;
+
+ /**
+ * Exit Code for extensions locked remove failure
+ * @since 4.0.0
+ */
+ public const REMOVE_LOCKED = 4;
+
+ /**
+ * Exit Code for extensions not found
+ * @since 4.0.0
+ */
+ public const REMOVE_NOT_FOUND = 2;
+
+ /**
+ * Exit Code for extensions remove success
+ * @since 4.0.0
+ */
+ public const REMOVE_SUCCESSFUL = 0;
+
+ /**
+ * Command constructor.
+ *
+ * @param DatabaseInterface $db The database
+ *
+ * @since 4.2.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ $language = Factory::getLanguage();
+ $language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
+ $language->load('', JPATH_ADMINISTRATOR, null, true);
+ $language->load('com_installer', JPATH_ADMINISTRATOR, null, false, false) ||
+ $language->load('com_installer', JPATH_ADMINISTRATOR, null, true);
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $this->addArgument(
+ 'extensionId',
+ InputArgument::REQUIRED,
+ 'ID of extension to be removed (run extension:list command to check)'
+ );
+
+ $help = "%command.name% is used to uninstall extensions.
\nThe command requires one argument, the ID of the extension to uninstall.
\nYou may find this ID by running the extension:list command.
\nUsage: php %command.full_name% ";
- $this->setDescription('Remove an extension');
- $this->setHelp($help);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
- $extensionId = $this->cliInput->getArgument('extensionId');
-
- $response = $this->ioStyle->ask('Are you sure you want to remove this extension?', 'yes/no');
-
- if (strtolower($response) === 'yes')
- {
- // Get an installer object for the extension type
- $installer = Installer::getInstance();
- $row = new Extension($this->getDatabase());
-
- if ((int) $extensionId === 0 || !$row->load($extensionId))
- {
- $this->ioStyle->error("Extension with ID of $extensionId not found.");
-
- return self::REMOVE_NOT_FOUND;
- }
-
- // Do not allow to uninstall locked extensions.
- if ((int) $row->locked === 1)
- {
- $this->ioStyle->error(Text::sprintf('COM_INSTALLER_UNINSTALL_ERROR_LOCKED_EXTENSION', $row->name, $extensionId));
-
- return self::REMOVE_LOCKED;
- }
-
- if ($row->type)
- {
- if (!$installer->uninstall($row->type, $extensionId))
- {
- $this->ioStyle->error('Extension not removed.');
-
- return self::REMOVE_FAILED;
- }
-
- $this->ioStyle->success('Extension removed!');
-
- return self::REMOVE_SUCCESSFUL;
- }
-
- return self::REMOVE_INVALID_TYPE;
- }
- elseif (strtolower($response) === 'no')
- {
- $this->ioStyle->note('Extension not removed.');
-
- return self::REMOVE_ABORT;
- }
-
- $this->ioStyle->warning('Invalid response');
-
- return self::REMOVE_INVALID_RESPONSE;
- }
+ $this->setDescription('Remove an extension');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+ $extensionId = $this->cliInput->getArgument('extensionId');
+
+ $response = $this->ioStyle->ask('Are you sure you want to remove this extension?', 'yes/no');
+
+ if (strtolower($response) === 'yes') {
+ // Get an installer object for the extension type
+ $installer = Installer::getInstance();
+ $row = new Extension($this->getDatabase());
+
+ if ((int) $extensionId === 0 || !$row->load($extensionId)) {
+ $this->ioStyle->error("Extension with ID of $extensionId not found.");
+
+ return self::REMOVE_NOT_FOUND;
+ }
+
+ // Do not allow to uninstall locked extensions.
+ if ((int) $row->locked === 1) {
+ $this->ioStyle->error(Text::sprintf('COM_INSTALLER_UNINSTALL_ERROR_LOCKED_EXTENSION', $row->name, $extensionId));
+
+ return self::REMOVE_LOCKED;
+ }
+
+ if ($row->type) {
+ if (!$installer->uninstall($row->type, $extensionId)) {
+ $this->ioStyle->error('Extension not removed.');
+
+ return self::REMOVE_FAILED;
+ }
+
+ $this->ioStyle->success('Extension removed!');
+
+ return self::REMOVE_SUCCESSFUL;
+ }
+
+ return self::REMOVE_INVALID_TYPE;
+ } elseif (strtolower($response) === 'no') {
+ $this->ioStyle->note('Extension not removed.');
+
+ return self::REMOVE_ABORT;
+ }
+
+ $this->ioStyle->warning('Invalid response');
+
+ return self::REMOVE_INVALID_RESPONSE;
+ }
}
diff --git a/libraries/src/Console/ExtensionsListCommand.php b/libraries/src/Console/ExtensionsListCommand.php
index 51427c6935c59..b86cd863a0844 100644
--- a/libraries/src/Console/ExtensionsListCommand.php
+++ b/libraries/src/Console/ExtensionsListCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- protected function configureIO(InputInterface $input, OutputInterface $output): void
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
-
- $this->addOption('type', null, InputOption::VALUE_REQUIRED, 'Type of the extension');
-
- $help = "%command.name% lists all installed extensions
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'extension:list';
+
+ /**
+ * Stores the installed Extensions
+ * @var array
+ * @since 4.0.0
+ */
+ protected $extensions;
+
+ /**
+ * Stores the Input Object
+ * @var InputInterface
+ * @since 4.0.0
+ */
+ protected $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ protected $ioStyle;
+
+ /**
+ * Instantiate the command.
+ *
+ * @param DatabaseInterface $db Database connector
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ protected function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+
+ $this->addOption('type', null, InputOption::VALUE_REQUIRED, 'Type of the extension');
+
+ $help = "%command.name% lists all installed extensions
\nUsage: php %command.full_name%
\nYou may filter on the type of extension (component, module, plugin, etc.) using the --type option:
\n php %command.full_name% --type= ";
- $this->setDescription('List installed extensions');
- $this->setHelp($help);
- }
-
- /**
- * Retrieves all extensions
- *
- * @return mixed
- *
- * @since 4.0.0
- */
- public function getExtensions()
- {
- if (!$this->extensions)
- {
- $this->setExtensions();
- }
-
- return $this->extensions;
- }
-
- /**
- * Retrieves the extension from the model and sets the class variable
- *
- * @param null $extensions Array of extensions
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setExtensions($extensions = null): void
- {
- if (!$extensions)
- {
- $this->extensions = $this->getAllExtensionsFromDB();
- }
- else
- {
- $this->extensions = $extensions;
- }
- }
-
- /**
- * Retrieves extension list from DB
- *
- * @return array
- *
- * @since 4.0.0
- */
- private function getAllExtensionsFromDB(): array
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select('*')
- ->from('#__extensions');
- $db->setQuery($query);
- $extensions = $db->loadAssocList('extension_id');
-
- return $extensions;
- }
-
- /**
- * Transforms extension arrays into required form
- *
- * @param array $extensions Array of extensions
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getExtensionsNameAndId($extensions): array
- {
- $extInfo = [];
-
- foreach ($extensions as $key => $extension)
- {
- $manifest = json_decode($extension['manifest_cache']);
- $extInfo[] = [
- $extension['name'],
- $extension['extension_id'],
- $manifest ? $manifest->version : '--',
- $extension['type'],
- $extension['enabled'] == 1 ? 'Yes' : 'No',
- ];
- }
-
- return $extInfo;
- }
-
- /**
- * Filters the extension type
- *
- * @param string $type Extension type
- *
- * @return array
- *
- * @since 4.0.0
- */
- private function filterExtensionsBasedOn($type): array
- {
- $extensions = [];
-
- foreach ($this->extensions as $key => $extension)
- {
- if ($extension['type'] == $type)
- {
- $extensions[] = $extension;
- }
- }
-
- return $extensions;
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
- $extensions = $this->getExtensions();
- $type = $this->cliInput->getOption('type');
-
- if ($type)
- {
- $extensions = $this->filterExtensionsBasedOn($type);
- }
-
- if (empty($extensions))
- {
- $this->ioStyle->error("Cannot find extensions of the type '$type' specified.");
-
- return Command::SUCCESS;
- }
-
- $extensions = $this->getExtensionsNameAndId($extensions);
-
- $this->ioStyle->title('Installed extensions.');
- $this->ioStyle->table(['Name', 'Extension ID', 'Version', 'Type', 'Active'], $extensions);
-
- return Command::SUCCESS;
- }
+ $this->setDescription('List installed extensions');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Retrieves all extensions
+ *
+ * @return mixed
+ *
+ * @since 4.0.0
+ */
+ public function getExtensions()
+ {
+ if (!$this->extensions) {
+ $this->setExtensions();
+ }
+
+ return $this->extensions;
+ }
+
+ /**
+ * Retrieves the extension from the model and sets the class variable
+ *
+ * @param null $extensions Array of extensions
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setExtensions($extensions = null): void
+ {
+ if (!$extensions) {
+ $this->extensions = $this->getAllExtensionsFromDB();
+ } else {
+ $this->extensions = $extensions;
+ }
+ }
+
+ /**
+ * Retrieves extension list from DB
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ private function getAllExtensionsFromDB(): array
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select('*')
+ ->from('#__extensions');
+ $db->setQuery($query);
+ $extensions = $db->loadAssocList('extension_id');
+
+ return $extensions;
+ }
+
+ /**
+ * Transforms extension arrays into required form
+ *
+ * @param array $extensions Array of extensions
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getExtensionsNameAndId($extensions): array
+ {
+ $extInfo = [];
+
+ foreach ($extensions as $key => $extension) {
+ $manifest = json_decode($extension['manifest_cache']);
+ $extInfo[] = [
+ $extension['name'],
+ $extension['extension_id'],
+ $manifest ? $manifest->version : '--',
+ $extension['type'],
+ $extension['enabled'] == 1 ? 'Yes' : 'No',
+ ];
+ }
+
+ return $extInfo;
+ }
+
+ /**
+ * Filters the extension type
+ *
+ * @param string $type Extension type
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ private function filterExtensionsBasedOn($type): array
+ {
+ $extensions = [];
+
+ foreach ($this->extensions as $key => $extension) {
+ if ($extension['type'] == $type) {
+ $extensions[] = $extension;
+ }
+ }
+
+ return $extensions;
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+ $extensions = $this->getExtensions();
+ $type = $this->cliInput->getOption('type');
+
+ if ($type) {
+ $extensions = $this->filterExtensionsBasedOn($type);
+ }
+
+ if (empty($extensions)) {
+ $this->ioStyle->error("Cannot find extensions of the type '$type' specified.");
+
+ return Command::SUCCESS;
+ }
+
+ $extensions = $this->getExtensionsNameAndId($extensions);
+
+ $this->ioStyle->title('Installed extensions.');
+ $this->ioStyle->table(['Name', 'Extension ID', 'Version', 'Type', 'Active'], $extensions);
+
+ return Command::SUCCESS;
+ }
}
diff --git a/libraries/src/Console/FinderIndexCommand.php b/libraries/src/Console/FinderIndexCommand.php
index c15103288ffc6..54ad4c4753893 100644
--- a/libraries/src/Console/FinderIndexCommand.php
+++ b/libraries/src/Console/FinderIndexCommand.php
@@ -1,4 +1,5 @@
db = $db;
- parent::__construct();
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $this->addArgument('purge', InputArgument::OPTIONAL, 'Purge the index and rebuilds');
- $this->addOption('minproctime', null, InputOption::VALUE_REQUIRED, 'Minimum processing time in seconds, in order to apply a pause', 1);
- $this->addOption('pause', null, InputOption::VALUE_REQUIRED, 'Pausing type or defined pause time in seconds', 'division');
- $this->addOption('divisor', null, InputOption::VALUE_REQUIRED, 'The divisor of the division: batch-processing time / divisor', 5);
- $help = <<<'EOF'
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'finder:index';
+
+ /**
+ * Stores the Input Object
+ *
+ * @var InputInterface
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ *
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Database connector
+ *
+ * @var DatabaseInterface
+ * @since 4.0.0
+ */
+ private $db;
+
+ /**
+ * Start time for the index process
+ *
+ * @var string
+ * @since 2.5
+ */
+ private $time;
+
+ /**
+ * Start time for each batch
+ *
+ * @var string
+ * @since 2.5
+ */
+ private $qtime;
+
+ /**
+ * Static filters information.
+ *
+ * @var array
+ * @since 3.3
+ */
+ private $filters = array();
+
+ /**
+ * Pausing type or defined pause time in seconds.
+ * One pausing type is implemented: 'division' for dynamic calculation of pauses
+ *
+ * Defaults to 'division'
+ *
+ * @var string|integer
+ * @since 3.9.12
+ */
+ private $pause = 'division';
+
+ /**
+ * The divisor of the division: batch-processing time / divisor.
+ * This is used together with --pause=division in order to pause dynamically
+ * in relation to the processing time
+ * Defaults to 5
+ *
+ * @var integer
+ * @since 3.9.12
+ */
+ private $divisor = 5;
+
+ /**
+ * Minimum processing time in seconds, in order to apply a pause
+ * Defaults to 1
+ *
+ * @var integer
+ * @since 3.9.12
+ */
+ private $minimumBatchProcessingTime = 1;
+
+ /**
+ * Instantiate the command.
+ *
+ * @param DatabaseInterface $db Database connector
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ $this->db = $db;
+ parent::__construct();
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $this->addArgument('purge', InputArgument::OPTIONAL, 'Purge the index and rebuilds');
+ $this->addOption('minproctime', null, InputOption::VALUE_REQUIRED, 'Minimum processing time in seconds, in order to apply a pause', 1);
+ $this->addOption('pause', null, InputOption::VALUE_REQUIRED, 'Pausing type or defined pause time in seconds', 'division');
+ $this->addOption('divisor', null, InputOption::VALUE_REQUIRED, 'The divisor of the division: batch-processing time / divisor', 5);
+ $help = <<<'EOF'
The %command.name% Purges and rebuilds the index (search filters are preserved).
php %command.full_name%
EOF;
- $this->setDescription('Purges and rebuild the index');
- $this->setHelp($help);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
-
- // Initialize the time value.
- $this->time = microtime(true);
- $this->configureIO($input, $output);
-
- $this->ioStyle->writeln(
- [
- 'Finder Indexer>',
- '==========================>',
- '',
- ]
- );
-
- if ($this->cliInput->getOption('minproctime'))
- {
- $this->minimumBatchProcessingTime = $this->cliInput->getOption('minproctime');
- }
-
- if ($this->cliInput->getOption('pause'))
- {
- $this->pause = $this->cliInput->getOption('pause');
- }
-
- if ($this->cliInput->getOption('divisor'))
- {
- $this->divisor = $this->cliInput->getOption('divisor');
- }
-
- if ($this->cliInput->getArgument('purge'))
- {
- // Taxonomy ids will change following a purge/index, so save filter information first.
- $this->getFilters();
-
- // Purge the index.
- $this->purge();
-
- // Run the indexer.
- $this->index();
-
- // Restore the filters again.
- $this->putFilters();
- }
- else
- {
- $this->index();
- }
-
- $this->ioStyle->newLine(1);
-
- // Total reporting.
- $this->ioStyle->writeln(
- [
- '' . Text::sprintf('FINDER_CLI_PROCESS_COMPLETE', round(microtime(true) - $this->time, 3)) . '>',
- '' . Text::sprintf('FINDER_CLI_PEAK_MEMORY_USAGE', number_format(memory_get_peak_usage(true))) . '>',
- ]
- );
-
- $this->ioStyle->newLine(1);
-
- return Command::SUCCESS;
- }
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- private function configureIO(InputInterface $input, OutputInterface $output): void
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- $language = Factory::getLanguage();
- $language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
- $language->load('', JPATH_ADMINISTRATOR, null, true);
- $language->load('finder_cli', JPATH_SITE, null, false, false)||
- $language->load('finder_cli', JPATH_SITE, null, true);
- }
-
- /**
- * Save static filters.
- *
- * Since a purge/index cycle will cause all the taxonomy ids to change,
- * the static filters need to be updated with the new taxonomy ids.
- * The static filter information is saved prior to the purge/index
- * so that it can later be used to update the filters with new ids.
- *
- * @return void
- *
- * @since 4.0.0
- */
- private function getFilters(): void
- {
- $this->ioStyle->text(Text::_('FINDER_CLI_SAVE_FILTERS'));
-
- // Get the taxonomy ids used by the filters.
- $db = $this->db;
- $query = $db->getQuery(true);
- $query
- ->select('filter_id, title, data')
- ->from($db->quoteName('#__finder_filters'));
- $filters = $db->setQuery($query)->loadObjectList();
-
- // Get the name of each taxonomy and the name of its parent.
- foreach ($filters as $filter)
- {
- // Skip empty filters.
- if ($filter->data === '')
- {
- continue;
- }
-
- // Get taxonomy records.
- $query = $db->getQuery(true);
- $query
- ->select('t.title, p.title AS parent')
- ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
- ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
- ->where($db->quoteName('t.id') . ' IN (' . $filter->data . ')');
- $taxonomies = $db->setQuery($query)->loadObjectList();
-
- // Construct a temporary data structure to hold the filter information.
- foreach ($taxonomies as $taxonomy)
- {
- $this->filters[$filter->filter_id][] = array(
- 'filter' => $filter->title,
- 'title' => $taxonomy->title,
- 'parent' => $taxonomy->parent,
- );
- }
- }
-
- $this->ioStyle->text(Text::sprintf('FINDER_CLI_SAVE_FILTER_COMPLETED', count($filters)));
- }
-
- /**
- * Purge the index.
- *
- * @return void
- *
- * @since 3.3
- */
- private function purge()
- {
- $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE'));
-
- // Load the model.
- $app = $this->getApplication();
- $model = $app->bootComponent('com_finder')->getMVCFactory($app)->createModel('Index', 'Administrator');
-
- // Attempt to purge the index.
- $return = $model->purge();
-
- // If unsuccessful then abort.
- if (!$return)
- {
- $message = Text::_('FINDER_CLI_INDEX_PURGE_FAILED', $model->getError());
- $this->ioStyle->error($message);
- exit();
- }
-
- $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE_SUCCESS'));
- }
-
- /**
- * Run the indexer.
- *
- * @return void
- *
- * @since 2.5
- */
- private function index()
- {
-
- // Disable caching.
- $app = $this->getApplication();
- $app->set('caching', 0);
- $app->set('cache_handler', 'file');
-
- // Reset the indexer state.
- Indexer::resetState();
-
- // Import the plugins.
- PluginHelper::importPlugin('system');
- PluginHelper::importPlugin('finder');
-
- // Starting Indexer.
- $this->ioStyle->text(Text::_('FINDER_CLI_STARTING_INDEXER'));
-
- // Trigger the onStartIndex event.
- $app->triggerEvent('onStartIndex');
-
- // Remove the script time limit.
- @set_time_limit(0);
-
- // Get the indexer state.
- $state = Indexer::getState();
-
- // Setting up plugins.
- $this->ioStyle->text(Text::_('FINDER_CLI_SETTING_UP_PLUGINS'));
-
- // Trigger the onBeforeIndex event.
- $app->triggerEvent('onBeforeIndex');
-
- // Startup reporting.
- $this->ioStyle->text(Text::sprintf('FINDER_CLI_SETUP_ITEMS', $state->totalItems, round(microtime(true) - $this->time, 3)));
-
- // Get the number of batches.
- $t = (int) $state->totalItems;
- $c = (int) ceil($t / $state->batchSize);
- $c = $c === 0 ? 1 : $c;
-
- try
- {
- // Process the batches.
- for ($i = 0; $i < $c; $i++)
- {
- // Set the batch start time.
- $this->qtime = microtime(true);
-
- // Reset the batch offset.
- $state->batchOffset = 0;
-
- // Trigger the onBuildIndex event.
- Factory::getApplication()->triggerEvent('onBuildIndex');
-
- // Batch reporting.
- $text = Text::sprintf('FINDER_CLI_BATCH_COMPLETE', $i + 1, $processingTime = round(microtime(true) - $this->qtime, 3));
- $this->ioStyle->text($text);
-
- if ($this->pause !== 0)
- {
- // Pausing Section
- $skip = !($processingTime >= $this->minimumBatchProcessingTime);
- $pause = 0;
-
- if ($this->pause === 'division' && $this->divisor > 0)
- {
- if (!$skip)
- {
- $pause = round($processingTime / $this->divisor);
- }
- else
- {
- $pause = 1;
- }
- }
- elseif ($this->pause > 0)
- {
- $pause = $this->pause;
- }
-
- if ($pause > 0 && !$skip)
- {
- $this->ioStyle->text(Text::sprintf('FINDER_CLI_BATCH_PAUSING', $pause));
- sleep($pause);
- $this->ioStyle->text(Text::_('FINDER_CLI_BATCH_CONTINUING'));
- }
-
- if ($skip)
- {
- $this->ioStyle->text(
- Text::sprintf(
- 'FINDER_CLI_SKIPPING_PAUSE_LOW_BATCH_PROCESSING_TIME',
- $processingTime,
- $this->minimumBatchProcessingTime
- )
- );
- }
-
- // End of Pausing Section
- }
- }
- }
- catch (Exception $e)
- {
- // Display the error
- $this->ioStyle->error($e->getMessage());
-
- // Reset the indexer state.
- Indexer::resetState();
-
- // Close the app
- $app->close($e->getCode());
- }
-
- // Reset the indexer state.
- Indexer::resetState();
- }
-
- /**
- * Restore static filters.
- *
- * Using the saved filter information, update the filter records
- * with the new taxonomy ids.
- *
- * @return void
- *
- * @since 3.3
- */
- private function putFilters()
- {
- $this->ioStyle->text(Text::_('FINDER_CLI_RESTORE_FILTERS'));
-
- $db = $this->db;
-
- // Use the temporary filter information to update the filter taxonomy ids.
- foreach ($this->filters as $filter_id => $filter)
- {
- $tids = array();
-
- foreach ($filter as $element)
- {
- // Look for the old taxonomy in the new taxonomy table.
- $query = $db->getQuery(true);
- $query
- ->select('t.id')
- ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
- ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
- ->where($db->quoteName('t.title') . ' = ' . $db->quote($element['title']))
- ->where($db->quoteName('p.title') . ' = ' . $db->quote($element['parent']));
- $taxonomy = $db->setQuery($query)->loadResult();
-
- // If we found it then add it to the list.
- if ($taxonomy)
- {
- $tids[] = $taxonomy;
- }
- else
- {
- $text = Text::sprintf('FINDER_CLI_FILTER_RESTORE_WARNING', $element['parent'], $element['title'], $element['filter']);
- $this->ioStyle->text($text);
- }
- }
-
- // Construct a comma-separated string from the taxonomy ids.
- $taxonomyIds = empty($tids) ? '' : implode(',', $tids);
-
- // Update the filter with the new taxonomy ids.
- $query = $db->getQuery(true);
- $query
- ->update($db->quoteName('#__finder_filters'))
- ->set($db->quoteName('data') . ' = ' . $db->quote($taxonomyIds))
- ->where($db->quoteName('filter_id') . ' = ' . (int) $filter_id);
- $db->setQuery($query)->execute();
- }
-
- $this->ioStyle->text(Text::sprintf('FINDER_CLI_RESTORE_FILTER_COMPLETED', count($this->filters)));
- }
-
+ $this->setDescription('Purges and rebuild the index');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+
+ // Initialize the time value.
+ $this->time = microtime(true);
+ $this->configureIO($input, $output);
+
+ $this->ioStyle->writeln(
+ [
+ 'Finder Indexer>',
+ '==========================>',
+ '',
+ ]
+ );
+
+ if ($this->cliInput->getOption('minproctime')) {
+ $this->minimumBatchProcessingTime = $this->cliInput->getOption('minproctime');
+ }
+
+ if ($this->cliInput->getOption('pause')) {
+ $this->pause = $this->cliInput->getOption('pause');
+ }
+
+ if ($this->cliInput->getOption('divisor')) {
+ $this->divisor = $this->cliInput->getOption('divisor');
+ }
+
+ if ($this->cliInput->getArgument('purge')) {
+ // Taxonomy ids will change following a purge/index, so save filter information first.
+ $this->getFilters();
+
+ // Purge the index.
+ $this->purge();
+
+ // Run the indexer.
+ $this->index();
+
+ // Restore the filters again.
+ $this->putFilters();
+ } else {
+ $this->index();
+ }
+
+ $this->ioStyle->newLine(1);
+
+ // Total reporting.
+ $this->ioStyle->writeln(
+ [
+ '' . Text::sprintf('FINDER_CLI_PROCESS_COMPLETE', round(microtime(true) - $this->time, 3)) . '>',
+ '' . Text::sprintf('FINDER_CLI_PEAK_MEMORY_USAGE', number_format(memory_get_peak_usage(true))) . '>',
+ ]
+ );
+
+ $this->ioStyle->newLine(1);
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ $language = Factory::getLanguage();
+ $language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
+ $language->load('', JPATH_ADMINISTRATOR, null, true);
+ $language->load('finder_cli', JPATH_SITE, null, false, false) ||
+ $language->load('finder_cli', JPATH_SITE, null, true);
+ }
+
+ /**
+ * Save static filters.
+ *
+ * Since a purge/index cycle will cause all the taxonomy ids to change,
+ * the static filters need to be updated with the new taxonomy ids.
+ * The static filter information is saved prior to the purge/index
+ * so that it can later be used to update the filters with new ids.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function getFilters(): void
+ {
+ $this->ioStyle->text(Text::_('FINDER_CLI_SAVE_FILTERS'));
+
+ // Get the taxonomy ids used by the filters.
+ $db = $this->db;
+ $query = $db->getQuery(true);
+ $query
+ ->select('filter_id, title, data')
+ ->from($db->quoteName('#__finder_filters'));
+ $filters = $db->setQuery($query)->loadObjectList();
+
+ // Get the name of each taxonomy and the name of its parent.
+ foreach ($filters as $filter) {
+ // Skip empty filters.
+ if ($filter->data === '') {
+ continue;
+ }
+
+ // Get taxonomy records.
+ $query = $db->getQuery(true);
+ $query
+ ->select('t.title, p.title AS parent')
+ ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
+ ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
+ ->where($db->quoteName('t.id') . ' IN (' . $filter->data . ')');
+ $taxonomies = $db->setQuery($query)->loadObjectList();
+
+ // Construct a temporary data structure to hold the filter information.
+ foreach ($taxonomies as $taxonomy) {
+ $this->filters[$filter->filter_id][] = array(
+ 'filter' => $filter->title,
+ 'title' => $taxonomy->title,
+ 'parent' => $taxonomy->parent,
+ );
+ }
+ }
+
+ $this->ioStyle->text(Text::sprintf('FINDER_CLI_SAVE_FILTER_COMPLETED', count($filters)));
+ }
+
+ /**
+ * Purge the index.
+ *
+ * @return void
+ *
+ * @since 3.3
+ */
+ private function purge()
+ {
+ $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE'));
+
+ // Load the model.
+ $app = $this->getApplication();
+ $model = $app->bootComponent('com_finder')->getMVCFactory($app)->createModel('Index', 'Administrator');
+
+ // Attempt to purge the index.
+ $return = $model->purge();
+
+ // If unsuccessful then abort.
+ if (!$return) {
+ $message = Text::_('FINDER_CLI_INDEX_PURGE_FAILED', $model->getError());
+ $this->ioStyle->error($message);
+ exit();
+ }
+
+ $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE_SUCCESS'));
+ }
+
+ /**
+ * Run the indexer.
+ *
+ * @return void
+ *
+ * @since 2.5
+ */
+ private function index()
+ {
+
+ // Disable caching.
+ $app = $this->getApplication();
+ $app->set('caching', 0);
+ $app->set('cache_handler', 'file');
+
+ // Reset the indexer state.
+ Indexer::resetState();
+
+ // Import the plugins.
+ PluginHelper::importPlugin('system');
+ PluginHelper::importPlugin('finder');
+
+ // Starting Indexer.
+ $this->ioStyle->text(Text::_('FINDER_CLI_STARTING_INDEXER'));
+
+ // Trigger the onStartIndex event.
+ $app->triggerEvent('onStartIndex');
+
+ // Remove the script time limit.
+ @set_time_limit(0);
+
+ // Get the indexer state.
+ $state = Indexer::getState();
+
+ // Setting up plugins.
+ $this->ioStyle->text(Text::_('FINDER_CLI_SETTING_UP_PLUGINS'));
+
+ // Trigger the onBeforeIndex event.
+ $app->triggerEvent('onBeforeIndex');
+
+ // Startup reporting.
+ $this->ioStyle->text(Text::sprintf('FINDER_CLI_SETUP_ITEMS', $state->totalItems, round(microtime(true) - $this->time, 3)));
+
+ // Get the number of batches.
+ $t = (int) $state->totalItems;
+ $c = (int) ceil($t / $state->batchSize);
+ $c = $c === 0 ? 1 : $c;
+
+ try {
+ // Process the batches.
+ for ($i = 0; $i < $c; $i++) {
+ // Set the batch start time.
+ $this->qtime = microtime(true);
+
+ // Reset the batch offset.
+ $state->batchOffset = 0;
+
+ // Trigger the onBuildIndex event.
+ Factory::getApplication()->triggerEvent('onBuildIndex');
+
+ // Batch reporting.
+ $text = Text::sprintf('FINDER_CLI_BATCH_COMPLETE', $i + 1, $processingTime = round(microtime(true) - $this->qtime, 3));
+ $this->ioStyle->text($text);
+
+ if ($this->pause !== 0) {
+ // Pausing Section
+ $skip = !($processingTime >= $this->minimumBatchProcessingTime);
+ $pause = 0;
+
+ if ($this->pause === 'division' && $this->divisor > 0) {
+ if (!$skip) {
+ $pause = round($processingTime / $this->divisor);
+ } else {
+ $pause = 1;
+ }
+ } elseif ($this->pause > 0) {
+ $pause = $this->pause;
+ }
+
+ if ($pause > 0 && !$skip) {
+ $this->ioStyle->text(Text::sprintf('FINDER_CLI_BATCH_PAUSING', $pause));
+ sleep($pause);
+ $this->ioStyle->text(Text::_('FINDER_CLI_BATCH_CONTINUING'));
+ }
+
+ if ($skip) {
+ $this->ioStyle->text(
+ Text::sprintf(
+ 'FINDER_CLI_SKIPPING_PAUSE_LOW_BATCH_PROCESSING_TIME',
+ $processingTime,
+ $this->minimumBatchProcessingTime
+ )
+ );
+ }
+
+ // End of Pausing Section
+ }
+ }
+ } catch (Exception $e) {
+ // Display the error
+ $this->ioStyle->error($e->getMessage());
+
+ // Reset the indexer state.
+ Indexer::resetState();
+
+ // Close the app
+ $app->close($e->getCode());
+ }
+
+ // Reset the indexer state.
+ Indexer::resetState();
+ }
+
+ /**
+ * Restore static filters.
+ *
+ * Using the saved filter information, update the filter records
+ * with the new taxonomy ids.
+ *
+ * @return void
+ *
+ * @since 3.3
+ */
+ private function putFilters()
+ {
+ $this->ioStyle->text(Text::_('FINDER_CLI_RESTORE_FILTERS'));
+
+ $db = $this->db;
+
+ // Use the temporary filter information to update the filter taxonomy ids.
+ foreach ($this->filters as $filter_id => $filter) {
+ $tids = array();
+
+ foreach ($filter as $element) {
+ // Look for the old taxonomy in the new taxonomy table.
+ $query = $db->getQuery(true);
+ $query
+ ->select('t.id')
+ ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
+ ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
+ ->where($db->quoteName('t.title') . ' = ' . $db->quote($element['title']))
+ ->where($db->quoteName('p.title') . ' = ' . $db->quote($element['parent']));
+ $taxonomy = $db->setQuery($query)->loadResult();
+
+ // If we found it then add it to the list.
+ if ($taxonomy) {
+ $tids[] = $taxonomy;
+ } else {
+ $text = Text::sprintf('FINDER_CLI_FILTER_RESTORE_WARNING', $element['parent'], $element['title'], $element['filter']);
+ $this->ioStyle->text($text);
+ }
+ }
+
+ // Construct a comma-separated string from the taxonomy ids.
+ $taxonomyIds = empty($tids) ? '' : implode(',', $tids);
+
+ // Update the filter with the new taxonomy ids.
+ $query = $db->getQuery(true);
+ $query
+ ->update($db->quoteName('#__finder_filters'))
+ ->set($db->quoteName('data') . ' = ' . $db->quote($taxonomyIds))
+ ->where($db->quoteName('filter_id') . ' = ' . (int) $filter_id);
+ $db->setQuery($query)->execute();
+ }
+
+ $this->ioStyle->text(Text::sprintf('FINDER_CLI_RESTORE_FILTER_COMPLETED', count($this->filters)));
+ }
}
diff --git a/libraries/src/Console/GetConfigurationCommand.php b/libraries/src/Console/GetConfigurationCommand.php
index 4b1f14befa16c..43c9f8a3a51ac 100644
--- a/libraries/src/Console/GetConfigurationCommand.php
+++ b/libraries/src/Console/GetConfigurationCommand.php
@@ -1,4 +1,5 @@
'db',
- 'options' => [
- 'dbtype',
- 'host',
- 'user',
- 'password',
- 'dbprefix',
- 'db',
- 'dbencryption',
- 'dbsslverifyservercert',
- 'dbsslkey',
- 'dbsslcert',
- 'dbsslca',
- 'dbsslcipher'
- ]
- ];
-
- /**
- * Constant defining the Session option group
- * @var array
- * @since 4.0.0
- */
- public const SESSION_GROUP = [
- 'name' => 'session',
- 'options' => [
- 'session_handler',
- 'shared_session',
- 'session_metadata'
- ]
- ];
-
- /**
- * Constant defining the Mail option group
- * @var array
- * @since 4.0.0
- */
- public const MAIL_GROUP = [
- 'name' => 'mail',
- 'options' => [
- 'mailonline',
- 'mailer',
- 'mailfrom',
- 'fromname',
- 'sendmail',
- 'smtpauth',
- 'smtpuser',
- 'smtppass',
- 'smtphost',
- 'smtpsecure',
- 'smtpport'
- ]
- ];
-
- /**
- * Return code if configuration is get successfully
- * @since 4.0.0
- */
- public const CONFIG_GET_SUCCESSFUL = 0;
-
- /**
- * Return code if configuration group option is not found
- * @since 4.0.0
- */
- public const CONFIG_GET_GROUP_NOT_FOUND = 1;
-
- /**
- * Return code if configuration option is not found
- * @since 4.0.0
- */
- public const CONFIG_GET_OPTION_NOT_FOUND = 2;
-
- /**
- * Return code if the command has been invoked with wrong options
- * @since 4.0.0
- */
- public const CONFIG_GET_OPTION_FAILED = 3;
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
-
- /**
- * Displays logically grouped options
- *
- * @param string $group The group to be processed
- *
- * @return integer
- *
- * @since 4.0.0
- */
- public function processGroupOptions($group): int
- {
- $configs = $this->getApplication()->getConfig()->toArray();
- $configs = $this->formatConfig($configs);
-
- $groups = $this->getGroups();
-
- $foundGroup = false;
-
- foreach ($groups as $key => $value)
- {
- if ($value['name'] === $group)
- {
- $foundGroup = true;
- $options = [];
-
- foreach ($value['options'] as $option)
- {
- $options[] = [$option, $configs[$option]];
- }
-
- $this->ioStyle->table(['Option', 'Value'], $options);
- }
- }
-
- if (!$foundGroup)
- {
- $this->ioStyle->error("Group *$group* not found");
-
- return self::CONFIG_GET_GROUP_NOT_FOUND;
- }
-
- return self::CONFIG_GET_SUCCESSFUL;
- }
-
- /**
- * Gets the defined option groups
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getGroups()
- {
- return [
- self::DB_GROUP,
- self::MAIL_GROUP,
- self::SESSION_GROUP
- ];
- }
-
- /**
- * Formats the configuration array into desired format
- *
- * @param array $configs Array of the configurations
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function formatConfig(Array $configs): array
- {
- $newConfig = [];
-
- foreach ($configs as $key => $config)
- {
- $config = $config === false ? "false" : $config;
- $config = $config === true ? "true" : $config;
-
- if (!in_array($key, ['cwd', 'execution']))
- {
- $newConfig[$key] = $config;
- }
- }
-
- return $newConfig;
- }
-
- /**
- * Handles the command when a single option is requested
- *
- * @param string $option The option we want to get its value
- *
- * @return integer
- *
- * @since 4.0.0
- */
- public function processSingleOption($option): int
- {
- $configs = $this->getApplication()->getConfig()->toArray();
-
- if (!array_key_exists($option, $configs))
- {
- $this->ioStyle->error("Can't find option *$option* in configuration list");
-
- return self::CONFIG_GET_OPTION_NOT_FOUND;
- }
-
- $value = $this->formatConfigValue($this->getApplication()->get($option));
-
- $this->ioStyle->table(['Option', 'Value'], [[$option, $value]]);
-
- return self::CONFIG_GET_SUCCESSFUL;
- }
-
- /**
- * Formats the Configuration value
- *
- * @param mixed $value Value to be formatted
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function formatConfigValue($value): string
- {
- if ($value === false)
- {
- return 'false';
- }
- elseif ($value === true)
- {
- return 'true';
- }
- elseif ($value === null)
- {
- return 'Not Set';
- }
- elseif (\is_array($value))
- {
- return \json_encode($value);
- }
- elseif (\is_object($value))
- {
- return \json_encode(\get_object_vars($value));
- }
- else
- {
- return $value;
- }
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $groups = $this->getGroups();
-
- foreach ($groups as $key => $group)
- {
- $groupNames[] = $group['name'];
- }
-
- $groupNames = implode(', ', $groupNames);
-
- $this->addArgument('option', null, 'Name of the option');
- $this->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Name of the option');
-
- $help = "%command.name% displays the current value of a configuration option
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'config:get';
+
+ /**
+ * Stores the Input Object
+ * @var Input
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Constant defining the Database option group
+ * @var array
+ * @since 4.0.0
+ */
+ public const DB_GROUP = [
+ 'name' => 'db',
+ 'options' => [
+ 'dbtype',
+ 'host',
+ 'user',
+ 'password',
+ 'dbprefix',
+ 'db',
+ 'dbencryption',
+ 'dbsslverifyservercert',
+ 'dbsslkey',
+ 'dbsslcert',
+ 'dbsslca',
+ 'dbsslcipher'
+ ]
+ ];
+
+ /**
+ * Constant defining the Session option group
+ * @var array
+ * @since 4.0.0
+ */
+ public const SESSION_GROUP = [
+ 'name' => 'session',
+ 'options' => [
+ 'session_handler',
+ 'shared_session',
+ 'session_metadata'
+ ]
+ ];
+
+ /**
+ * Constant defining the Mail option group
+ * @var array
+ * @since 4.0.0
+ */
+ public const MAIL_GROUP = [
+ 'name' => 'mail',
+ 'options' => [
+ 'mailonline',
+ 'mailer',
+ 'mailfrom',
+ 'fromname',
+ 'sendmail',
+ 'smtpauth',
+ 'smtpuser',
+ 'smtppass',
+ 'smtphost',
+ 'smtpsecure',
+ 'smtpport'
+ ]
+ ];
+
+ /**
+ * Return code if configuration is get successfully
+ * @since 4.0.0
+ */
+ public const CONFIG_GET_SUCCESSFUL = 0;
+
+ /**
+ * Return code if configuration group option is not found
+ * @since 4.0.0
+ */
+ public const CONFIG_GET_GROUP_NOT_FOUND = 1;
+
+ /**
+ * Return code if configuration option is not found
+ * @since 4.0.0
+ */
+ public const CONFIG_GET_OPTION_NOT_FOUND = 2;
+
+ /**
+ * Return code if the command has been invoked with wrong options
+ * @since 4.0.0
+ */
+ public const CONFIG_GET_OPTION_FAILED = 3;
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+
+ /**
+ * Displays logically grouped options
+ *
+ * @param string $group The group to be processed
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ public function processGroupOptions($group): int
+ {
+ $configs = $this->getApplication()->getConfig()->toArray();
+ $configs = $this->formatConfig($configs);
+
+ $groups = $this->getGroups();
+
+ $foundGroup = false;
+
+ foreach ($groups as $key => $value) {
+ if ($value['name'] === $group) {
+ $foundGroup = true;
+ $options = [];
+
+ foreach ($value['options'] as $option) {
+ $options[] = [$option, $configs[$option]];
+ }
+
+ $this->ioStyle->table(['Option', 'Value'], $options);
+ }
+ }
+
+ if (!$foundGroup) {
+ $this->ioStyle->error("Group *$group* not found");
+
+ return self::CONFIG_GET_GROUP_NOT_FOUND;
+ }
+
+ return self::CONFIG_GET_SUCCESSFUL;
+ }
+
+ /**
+ * Gets the defined option groups
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getGroups()
+ {
+ return [
+ self::DB_GROUP,
+ self::MAIL_GROUP,
+ self::SESSION_GROUP
+ ];
+ }
+
+ /**
+ * Formats the configuration array into desired format
+ *
+ * @param array $configs Array of the configurations
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function formatConfig(array $configs): array
+ {
+ $newConfig = [];
+
+ foreach ($configs as $key => $config) {
+ $config = $config === false ? "false" : $config;
+ $config = $config === true ? "true" : $config;
+
+ if (!in_array($key, ['cwd', 'execution'])) {
+ $newConfig[$key] = $config;
+ }
+ }
+
+ return $newConfig;
+ }
+
+ /**
+ * Handles the command when a single option is requested
+ *
+ * @param string $option The option we want to get its value
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ public function processSingleOption($option): int
+ {
+ $configs = $this->getApplication()->getConfig()->toArray();
+
+ if (!array_key_exists($option, $configs)) {
+ $this->ioStyle->error("Can't find option *$option* in configuration list");
+
+ return self::CONFIG_GET_OPTION_NOT_FOUND;
+ }
+
+ $value = $this->formatConfigValue($this->getApplication()->get($option));
+
+ $this->ioStyle->table(['Option', 'Value'], [[$option, $value]]);
+
+ return self::CONFIG_GET_SUCCESSFUL;
+ }
+
+ /**
+ * Formats the Configuration value
+ *
+ * @param mixed $value Value to be formatted
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function formatConfigValue($value): string
+ {
+ if ($value === false) {
+ return 'false';
+ } elseif ($value === true) {
+ return 'true';
+ } elseif ($value === null) {
+ return 'Not Set';
+ } elseif (\is_array($value)) {
+ return \json_encode($value);
+ } elseif (\is_object($value)) {
+ return \json_encode(\get_object_vars($value));
+ } else {
+ return $value;
+ }
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $groups = $this->getGroups();
+
+ foreach ($groups as $key => $group) {
+ $groupNames[] = $group['name'];
+ }
+
+ $groupNames = implode(', ', $groupNames);
+
+ $this->addArgument('option', null, 'Name of the option');
+ $this->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Name of the option');
+
+ $help = "%command.name% displays the current value of a configuration option
\nUsage: php %command.full_name%
\nGroup usage: php %command.full_name% --group
\nAvailable group names: $groupNames";
- $this->setDescription('Display the current value of a configuration option');
- $this->setHelp($help);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $configs = $this->formatConfig($this->getApplication()->getConfig()->toArray());
-
- $option = $this->cliInput->getArgument('option');
- $group = $this->cliInput->getOption('group');
-
- if ($group)
- {
- return $this->processGroupOptions($group);
- }
-
- if ($option)
- {
- return $this->processSingleOption($option);
- }
-
- if (!$option && !$group)
- {
- $options = [];
-
- array_walk(
- $configs,
- function ($value, $key) use (&$options) {
- $options[] = [$key, $this->formatConfigValue($value)];
- }
- );
-
- $this->ioStyle->title("Current options in Configuration");
- $this->ioStyle->table(['Option', 'Value'], $options);
-
- return self::CONFIG_GET_SUCCESSFUL;
- }
-
- return self::CONFIG_GET_OPTION_NOT_FOUND;
- }
+ $this->setDescription('Display the current value of a configuration option');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $configs = $this->formatConfig($this->getApplication()->getConfig()->toArray());
+
+ $option = $this->cliInput->getArgument('option');
+ $group = $this->cliInput->getOption('group');
+
+ if ($group) {
+ return $this->processGroupOptions($group);
+ }
+
+ if ($option) {
+ return $this->processSingleOption($option);
+ }
+
+ if (!$option && !$group) {
+ $options = [];
+
+ array_walk(
+ $configs,
+ function ($value, $key) use (&$options) {
+ $options[] = [$key, $this->formatConfigValue($value)];
+ }
+ );
+
+ $this->ioStyle->title("Current options in Configuration");
+ $this->ioStyle->table(['Option', 'Value'], $options);
+
+ return self::CONFIG_GET_SUCCESSFUL;
+ }
+
+ return self::CONFIG_GET_OPTION_NOT_FOUND;
+ }
}
diff --git a/libraries/src/Console/ListUserCommand.php b/libraries/src/Console/ListUserCommand.php
index 2c1b65e11f2da..a097b0c90b273 100644
--- a/libraries/src/Console/ListUserCommand.php
+++ b/libraries/src/Console/ListUserCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $db = $this->getDatabase();
-
- $this->configureIO($input, $output);
- $this->ioStyle->title('List users');
-
- $groupsQuery = $db->getQuery(true)
- ->select($db->quoteName(['title', 'id']))
- ->from($db->quoteName('#__usergroups'));
-
- $groups = $db->setQuery($groupsQuery)->loadAssocList('id', 'title');
-
- $query = $db->getQuery(true);
- $query->select($db->quoteName(['u.id', 'u.username', 'u.name', 'u.email', 'u.block']))
- ->select($query->groupConcat($query->castAs('CHAR', $db->quoteName('g.group_id'))) . ' AS ' . $db->quoteName('groups'))
- ->innerJoin($db->quoteName('#__user_usergroup_map', 'g'), $db->quoteName('g.user_id') . ' = ' . $db->quoteName('u.id'))
- ->from($db->quoteName('#__users', 'u'))
- ->group($db->quoteName('u.id'));
- $db->setQuery($query);
-
- $users = [];
-
- foreach ($db->loadAssocList() as $user)
- {
- $user["groups"] = array_map(
- function ($groupId) use ($groups) {
- return $groups[$groupId];
- },
- explode(",", $user["groups"])
- );
-
- $user["groups"] = implode(", ", $user["groups"]);
- $users[] = $user;
- }
-
- $this->ioStyle->table(['id', 'username', 'name', 'email', 'blocked', 'groups'], $users);
-
- return Command::SUCCESS;
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% will list all users
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'user:list';
+
+ /**
+ * SymfonyStyle Object
+ * @var object
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Command constructor.
+ *
+ * @param DatabaseInterface $db The database
+ *
+ * @since 4.2.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $db = $this->getDatabase();
+
+ $this->configureIO($input, $output);
+ $this->ioStyle->title('List users');
+
+ $groupsQuery = $db->getQuery(true)
+ ->select($db->quoteName(['title', 'id']))
+ ->from($db->quoteName('#__usergroups'));
+
+ $groups = $db->setQuery($groupsQuery)->loadAssocList('id', 'title');
+
+ $query = $db->getQuery(true);
+ $query->select($db->quoteName(['u.id', 'u.username', 'u.name', 'u.email', 'u.block']))
+ ->select($query->groupConcat($query->castAs('CHAR', $db->quoteName('g.group_id'))) . ' AS ' . $db->quoteName('groups'))
+ ->innerJoin($db->quoteName('#__user_usergroup_map', 'g'), $db->quoteName('g.user_id') . ' = ' . $db->quoteName('u.id'))
+ ->from($db->quoteName('#__users', 'u'))
+ ->group($db->quoteName('u.id'));
+ $db->setQuery($query);
+
+ $users = [];
+
+ foreach ($db->loadAssocList() as $user) {
+ $user["groups"] = array_map(
+ function ($groupId) use ($groups) {
+ return $groups[$groupId];
+ },
+ explode(",", $user["groups"])
+ );
+
+ $user["groups"] = implode(", ", $user["groups"]);
+ $users[] = $user;
+ }
+
+ $this->ioStyle->table(['id', 'username', 'name', 'email', 'blocked', 'groups'], $users);
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% will list all users
\nUsage: php %command.full_name% ";
- $this->setDescription('List all users');
- $this->setHelp($help);
- }
+ $this->setDescription('List all users');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/Loader/WritableContainerLoader.php b/libraries/src/Console/Loader/WritableContainerLoader.php
index cc199ee41dcf5..6ecefd239ae45 100644
--- a/libraries/src/Console/Loader/WritableContainerLoader.php
+++ b/libraries/src/Console/Loader/WritableContainerLoader.php
@@ -1,4 +1,5 @@
container = $container;
- $this->commandMap = $commandMap;
- }
+ /**
+ * Constructor.
+ *
+ * @param ContainerInterface $container A container from which to load command services.
+ * @param array $commandMap An array with command names as keys and service IDs as values.
+ *
+ * @since 4.0.0
+ */
+ public function __construct(ContainerInterface $container, array $commandMap)
+ {
+ $this->container = $container;
+ $this->commandMap = $commandMap;
+ }
- /**
- * Adds a command to the loader.
- *
- * @param string $commandName The name of the command to load.
- * @param string $className The fully qualified class name of the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function add(string $commandName, string $className)
- {
- $this->commandMap[$commandName] = $className;
- }
+ /**
+ * Adds a command to the loader.
+ *
+ * @param string $commandName The name of the command to load.
+ * @param string $className The fully qualified class name of the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function add(string $commandName, string $className)
+ {
+ $this->commandMap[$commandName] = $className;
+ }
- /**
- * Loads a command.
- *
- * @param string $name The command to load.
- *
- * @return AbstractCommand
- *
- * @since 4.0.0
- * @throws CommandNotFoundException
- */
- public function get(string $name): AbstractCommand
- {
- if (!$this->has($name))
- {
- throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
- }
+ /**
+ * Loads a command.
+ *
+ * @param string $name The command to load.
+ *
+ * @return AbstractCommand
+ *
+ * @since 4.0.0
+ * @throws CommandNotFoundException
+ */
+ public function get(string $name): AbstractCommand
+ {
+ if (!$this->has($name)) {
+ throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
+ }
- return $this->container->get($this->commandMap[$name]);
- }
+ return $this->container->get($this->commandMap[$name]);
+ }
- /**
- * Get the names of the registered commands.
- *
- * @return string[]
- *
- * @since 4.0.0
- */
- public function getNames(): array
- {
- return array_keys($this->commandMap);
- }
+ /**
+ * Get the names of the registered commands.
+ *
+ * @return string[]
+ *
+ * @since 4.0.0
+ */
+ public function getNames(): array
+ {
+ return array_keys($this->commandMap);
+ }
- /**
- * Checks if a command exists.
- *
- * @param string $name The command to check.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- public function has($name): bool
- {
- return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
- }
+ /**
+ * Checks if a command exists.
+ *
+ * @param string $name The command to check.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function has($name): bool
+ {
+ return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
+ }
}
diff --git a/libraries/src/Console/Loader/WritableLoaderInterface.php b/libraries/src/Console/Loader/WritableLoaderInterface.php
index bdeb21665bc4a..5ca715a507681 100644
--- a/libraries/src/Console/Loader/WritableLoaderInterface.php
+++ b/libraries/src/Console/Loader/WritableLoaderInterface.php
@@ -1,4 +1,5 @@
getOption('dry-run');
-
- $symfonyStyle->title('Removing Unneeded Files & Folders' . ($dryRun ? ' - Dry Run' : ''));
-
- // We need the update script
- \JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
-
- $status = (new \JoomlaInstallerScript)->deleteUnexistingFiles($dryRun, true);
-
- if ($output->isVeryVerbose()||$output->isDebug())
- {
- foreach ($status['files_checked'] as $file)
- {
- $exists = in_array($file, array_values($status['files_exist']));
-
- if ($exists)
- {
- $symfonyStyle->writeln('File Checked & Exists - ' . $file, OutputInterface::VERBOSITY_VERY_VERBOSE);
- }
- else
- {
- $symfonyStyle->writeln('File Checked & Doesn\'t Exist - ' . $file, OutputInterface::VERBOSITY_DEBUG);
- }
- }
-
- foreach ($status['folders_checked'] as $folder)
- {
- $exists = in_array($folder, array_values($status['folders_exist']));
-
- if ($exists)
- {
- $symfonyStyle->writeln('Folder Checked & Exists - ' . $folder, OutputInterface::VERBOSITY_VERY_VERBOSE);
- }
- else
- {
- $symfonyStyle->writeln('Folder Checked & Doesn\'t Exist - ' . $folder, OutputInterface::VERBOSITY_DEBUG);
- }
- }
- }
-
- if ($dryRun === false)
- {
- foreach ($status['files_deleted'] as $file)
- {
- $symfonyStyle->writeln('File Deleted = ' . $file . ' ', OutputInterface::VERBOSITY_VERBOSE);
- }
-
- foreach ($status['files_errors'] as $error)
- {
- $symfonyStyle->error($error);
- }
-
- foreach ($status['folders_deleted'] as $folder)
- {
- $symfonyStyle->writeln('Folder Deleted = ' . $folder . ' ', OutputInterface::VERBOSITY_VERBOSE);
- }
-
- foreach ($status['folders_errors'] as $error)
- {
- $symfonyStyle->error($error);
- }
- }
-
- $symfonyStyle->success(
- sprintf(
- $dryRun ? '%s Files checked and %s would be deleted' : '%s Files checked and %s deleted',
- \count($status['files_checked']),
- ($dryRun ? \count($status['files_exist']) : \count($status['files_deleted']))
- )
- );
-
- $symfonyStyle->success(
- sprintf(
- $dryRun ? '%s Folders checked and %s would be deleted' : '%s Folders checked and %s deleted',
- \count($status['folders_checked']),
- ($dryRun ? \count($status['folders_exist']) : \count($status['folders_deleted']))
- )
- );
-
- return Command::SUCCESS;
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% removes old files which should have been deleted during a Joomla update
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'update:joomla:remove-old-files';
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $dryRun = $input->getOption('dry-run');
+
+ $symfonyStyle->title('Removing Unneeded Files & Folders' . ($dryRun ? ' - Dry Run' : ''));
+
+ // We need the update script
+ \JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
+
+ $status = (new \JoomlaInstallerScript())->deleteUnexistingFiles($dryRun, true);
+
+ if ($output->isVeryVerbose() || $output->isDebug()) {
+ foreach ($status['files_checked'] as $file) {
+ $exists = in_array($file, array_values($status['files_exist']));
+
+ if ($exists) {
+ $symfonyStyle->writeln('File Checked & Exists - ' . $file, OutputInterface::VERBOSITY_VERY_VERBOSE);
+ } else {
+ $symfonyStyle->writeln('File Checked & Doesn\'t Exist - ' . $file, OutputInterface::VERBOSITY_DEBUG);
+ }
+ }
+
+ foreach ($status['folders_checked'] as $folder) {
+ $exists = in_array($folder, array_values($status['folders_exist']));
+
+ if ($exists) {
+ $symfonyStyle->writeln('Folder Checked & Exists - ' . $folder, OutputInterface::VERBOSITY_VERY_VERBOSE);
+ } else {
+ $symfonyStyle->writeln('Folder Checked & Doesn\'t Exist - ' . $folder, OutputInterface::VERBOSITY_DEBUG);
+ }
+ }
+ }
+
+ if ($dryRun === false) {
+ foreach ($status['files_deleted'] as $file) {
+ $symfonyStyle->writeln('File Deleted = ' . $file . ' ', OutputInterface::VERBOSITY_VERBOSE);
+ }
+
+ foreach ($status['files_errors'] as $error) {
+ $symfonyStyle->error($error);
+ }
+
+ foreach ($status['folders_deleted'] as $folder) {
+ $symfonyStyle->writeln('Folder Deleted = ' . $folder . ' ', OutputInterface::VERBOSITY_VERBOSE);
+ }
+
+ foreach ($status['folders_errors'] as $error) {
+ $symfonyStyle->error($error);
+ }
+ }
+
+ $symfonyStyle->success(
+ sprintf(
+ $dryRun ? '%s Files checked and %s would be deleted' : '%s Files checked and %s deleted',
+ \count($status['files_checked']),
+ ($dryRun ? \count($status['files_exist']) : \count($status['files_deleted']))
+ )
+ );
+
+ $symfonyStyle->success(
+ sprintf(
+ $dryRun ? '%s Folders checked and %s would be deleted' : '%s Folders checked and %s deleted',
+ \count($status['folders_checked']),
+ ($dryRun ? \count($status['folders_exist']) : \count($status['folders_deleted']))
+ )
+ );
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% removes old files which should have been deleted during a Joomla update
\nUsage: php %command.full_name% ";
- $this->setDescription('Remove old system files');
- $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Executes a dry run without deleting anything');
- $this->setHelp($help);
- }
+ $this->setDescription('Remove old system files');
+ $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Executes a dry run without deleting anything');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/RemoveUserFromGroupCommand.php b/libraries/src/Console/RemoveUserFromGroupCommand.php
index 8afc9a2808e6a..a914192699a48 100644
--- a/libraries/src/Console/RemoveUserFromGroupCommand.php
+++ b/libraries/src/Console/RemoveUserFromGroupCommand.php
@@ -1,4 +1,5 @@
setDatabase($db);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
- $this->username = $this->getStringFromOption('username', 'Please enter a username');
- $this->ioStyle->title('Remove user from group');
-
- $userId = UserHelper::getUserId($this->username);
-
- if (empty($userId))
- {
- $this->ioStyle->error("The user " . $this->username . " does not exist!");
-
- return 1;
- }
-
- $user = User::getInstance($userId);
-
- $this->userGroups = $this->getGroups($user);
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__usergroups'))
- ->where($db->quoteName('id') . ' = :userGroup');
-
- foreach ($this->userGroups as $userGroup)
- {
- $query->bind(':userGroup', $userGroup);
- $db->setQuery($query);
-
- $result = $db->loadResult();
-
- if (Access::checkGroup($userGroup, 'core.admin'))
- {
- $queryUser = $db->getQuery(true);
- $queryUser->select('COUNT(*)')
- ->from($db->quoteName('#__users', 'u'))
- ->leftJoin(
- $db->quoteName('#__user_usergroup_map', 'g'),
- '(' . $db->quoteName('u.id') . ' = ' . $db->quoteName('g.user_id') . ')'
- )
- ->where($db->quoteName('g.group_id') . " = :groupId")
- ->where($db->quoteName('u.block') . " = 0")
- ->bind(':groupId', $userGroup);
-
- $db->setQuery($queryUser);
- $activeSuperUser = $db->loadResult();
-
- if ($activeSuperUser < 2)
- {
- $this->ioStyle->error("Can't remove user '" . $user->username . "' from group '" . $result . "'! "
- . $result . " needs at least one active user!"
- );
-
- return Command::FAILURE;
- }
- }
-
- if (\count(Access::getGroupsByUser($user->id, false)) < 2)
- {
- $this->ioStyle->error("Can't remove '" . $user->username . "' from group '" . $result
- . "'! Every user needs to be a member of at least one group"
- );
-
- return Command::FAILURE;
- }
-
- if (!UserHelper::removeUserFromGroup($user->id, $userGroup))
- {
- $this->ioStyle->error("Can't remove '" . $user->username . "' from group '" . $result . "'!");
-
- return Command::FAILURE;
- }
-
- $this->ioStyle->success("Removed '" . $user->username . "' from group '" . $result . "'!");
- }
-
- return Command::SUCCESS;
- }
-
- /**
- * Method to get a value from option
- *
- * @param object $user user object
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getGroups($user): array
- {
- $option = $this->getApplication()->getConsoleInput()->getOption('group');
- $db = $this->getDatabase();
- $userGroups = Access::getGroupsByUser($user->id, false);
-
- if (!$option)
- {
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__usergroups'))
- ->whereIn($db->quoteName('id'), $userGroups);
- $db->setQuery($query);
-
- $result = $db->loadColumn();
-
- $choice = new ChoiceQuestion(
- 'Please select a usergroup (separate multiple groups with a comma)',
- $result
- );
- $choice->setMultiselect(true);
-
- $answer = (array) $this->ioStyle->askQuestion($choice);
-
- $groupList = [];
-
- foreach ($answer as $group)
- {
- $groupList[] = $this->getGroupId($group);
- }
-
- return $groupList;
- }
-
- $groupList = [];
- $option = explode(',', $option);
-
- foreach ($option as $group)
- {
- $groupId = $this->getGroupId($group);
-
- if (empty($groupId))
- {
- $this->ioStyle->error("Invalid group name '" . $group . "'");
- throw new InvalidOptionException("Invalid group name " . $group);
- }
-
- $groupList[] = $this->getGroupId($group);
- }
-
- return $groupList;
- }
-
- /**
- * Method to get groupId by groupName
- *
- * @param string $groupName name of group
- *
- * @return integer
- *
- * @since 4.0.0
- */
- protected function getGroupId($groupName)
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__usergroups'))
- ->where($db->quoteName('title') . '= :groupName')
- ->bind(':groupName', $groupName);
- $db->setQuery($query);
-
- return $db->loadResult();
- }
-
- /**
- * Method to get a value from option
- *
- * @param string $option set the option name
- *
- * @param string $question set the question if user enters no value to option
- *
- * @return string
- *
- * @since 4.0.0
- */
- protected function getStringFromOption($option, $question): string
- {
- $answer = (string) $this->getApplication()->getConsoleInput()->getOption($option);
-
- while (!$answer)
- {
- $answer = (string) $this->ioStyle->ask($question);
- }
-
- return $answer;
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% removes a user from a group
+ use DatabaseAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'user:removefromgroup';
+
+ /**
+ * SymfonyStyle Object
+ * @var object
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Stores the Input Object
+ * @var object
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * The username
+ *
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ private $username;
+
+ /**
+ * The usergroups
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ private $userGroups = array();
+
+ /**
+ * Command constructor.
+ *
+ * @param DatabaseInterface $db The database
+ *
+ * @since 4.2.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ parent::__construct();
+
+ $this->setDatabase($db);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+ $this->username = $this->getStringFromOption('username', 'Please enter a username');
+ $this->ioStyle->title('Remove user from group');
+
+ $userId = UserHelper::getUserId($this->username);
+
+ if (empty($userId)) {
+ $this->ioStyle->error("The user " . $this->username . " does not exist!");
+
+ return 1;
+ }
+
+ $user = User::getInstance($userId);
+
+ $this->userGroups = $this->getGroups($user);
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__usergroups'))
+ ->where($db->quoteName('id') . ' = :userGroup');
+
+ foreach ($this->userGroups as $userGroup) {
+ $query->bind(':userGroup', $userGroup);
+ $db->setQuery($query);
+
+ $result = $db->loadResult();
+
+ if (Access::checkGroup($userGroup, 'core.admin')) {
+ $queryUser = $db->getQuery(true);
+ $queryUser->select('COUNT(*)')
+ ->from($db->quoteName('#__users', 'u'))
+ ->leftJoin(
+ $db->quoteName('#__user_usergroup_map', 'g'),
+ '(' . $db->quoteName('u.id') . ' = ' . $db->quoteName('g.user_id') . ')'
+ )
+ ->where($db->quoteName('g.group_id') . " = :groupId")
+ ->where($db->quoteName('u.block') . " = 0")
+ ->bind(':groupId', $userGroup);
+
+ $db->setQuery($queryUser);
+ $activeSuperUser = $db->loadResult();
+
+ if ($activeSuperUser < 2) {
+ $this->ioStyle->error("Can't remove user '" . $user->username . "' from group '" . $result . "'! "
+ . $result . " needs at least one active user!");
+
+ return Command::FAILURE;
+ }
+ }
+
+ if (\count(Access::getGroupsByUser($user->id, false)) < 2) {
+ $this->ioStyle->error("Can't remove '" . $user->username . "' from group '" . $result
+ . "'! Every user needs to be a member of at least one group");
+
+ return Command::FAILURE;
+ }
+
+ if (!UserHelper::removeUserFromGroup($user->id, $userGroup)) {
+ $this->ioStyle->error("Can't remove '" . $user->username . "' from group '" . $result . "'!");
+
+ return Command::FAILURE;
+ }
+
+ $this->ioStyle->success("Removed '" . $user->username . "' from group '" . $result . "'!");
+ }
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Method to get a value from option
+ *
+ * @param object $user user object
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getGroups($user): array
+ {
+ $option = $this->getApplication()->getConsoleInput()->getOption('group');
+ $db = $this->getDatabase();
+ $userGroups = Access::getGroupsByUser($user->id, false);
+
+ if (!$option) {
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__usergroups'))
+ ->whereIn($db->quoteName('id'), $userGroups);
+ $db->setQuery($query);
+
+ $result = $db->loadColumn();
+
+ $choice = new ChoiceQuestion(
+ 'Please select a usergroup (separate multiple groups with a comma)',
+ $result
+ );
+ $choice->setMultiselect(true);
+
+ $answer = (array) $this->ioStyle->askQuestion($choice);
+
+ $groupList = [];
+
+ foreach ($answer as $group) {
+ $groupList[] = $this->getGroupId($group);
+ }
+
+ return $groupList;
+ }
+
+ $groupList = [];
+ $option = explode(',', $option);
+
+ foreach ($option as $group) {
+ $groupId = $this->getGroupId($group);
+
+ if (empty($groupId)) {
+ $this->ioStyle->error("Invalid group name '" . $group . "'");
+ throw new InvalidOptionException("Invalid group name " . $group);
+ }
+
+ $groupList[] = $this->getGroupId($group);
+ }
+
+ return $groupList;
+ }
+
+ /**
+ * Method to get groupId by groupName
+ *
+ * @param string $groupName name of group
+ *
+ * @return integer
+ *
+ * @since 4.0.0
+ */
+ protected function getGroupId($groupName)
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__usergroups'))
+ ->where($db->quoteName('title') . '= :groupName')
+ ->bind(':groupName', $groupName);
+ $db->setQuery($query);
+
+ return $db->loadResult();
+ }
+
+ /**
+ * Method to get a value from option
+ *
+ * @param string $option set the option name
+ *
+ * @param string $question set the question if user enters no value to option
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ protected function getStringFromOption($option, $question): string
+ {
+ $answer = (string) $this->getApplication()->getConsoleInput()->getOption($option);
+
+ while (!$answer) {
+ $answer = (string) $this->ioStyle->ask($question);
+ }
+
+ return $answer;
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% removes a user from a group
\nUsage: php %command.full_name% ";
- $this->setDescription('Remove a user from a group');
- $this->addOption('username', null, InputOption::VALUE_OPTIONAL, 'username');
- $this->addOption('group', null, InputOption::VALUE_OPTIONAL, 'group');
- $this->setHelp($help);
- }
+ $this->setDescription('Remove a user from a group');
+ $this->addOption('username', null, InputOption::VALUE_OPTIONAL, 'username');
+ $this->addOption('group', null, InputOption::VALUE_OPTIONAL, 'group');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/SessionGcCommand.php b/libraries/src/Console/SessionGcCommand.php
index a9266d1d6d9ff..0e3613dfea988 100644
--- a/libraries/src/Console/SessionGcCommand.php
+++ b/libraries/src/Console/SessionGcCommand.php
@@ -1,4 +1,5 @@
title('Running Session Garbage Collection');
-
- $session = $this->getSessionService($input->getOption('application'));
-
- $gcResult = $session->gc();
-
- // Destroy the session started for this process
- $session->destroy();
-
- if ($gcResult === false)
- {
- $symfonyStyle->error('Garbage collection was not completed. Either the operation failed or it is not supported on your platform.');
-
- return Command::FAILURE;
- }
-
- $symfonyStyle->success('Garbage collection completed.');
-
- return Command::SUCCESS;
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% runs PHP's garbage collection operation for session data
+ use ContainerAwareTrait;
+
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'session:gc';
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $symfonyStyle->title('Running Session Garbage Collection');
+
+ $session = $this->getSessionService($input->getOption('application'));
+
+ $gcResult = $session->gc();
+
+ // Destroy the session started for this process
+ $session->destroy();
+
+ if ($gcResult === false) {
+ $symfonyStyle->error('Garbage collection was not completed. Either the operation failed or it is not supported on your platform.');
+
+ return Command::FAILURE;
+ }
+
+ $symfonyStyle->success('Garbage collection completed.');
+
+ return Command::SUCCESS;
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% runs PHP's garbage collection operation for session data
\nUsage: php %command.full_name%
\nThis command defaults to performing garbage collection for the frontend (site) application.
\nTo run garbage collection for another application, you can specify it with the --application option.
\nUsage: php %command.full_name% --application=[APPLICATION] ";
- $this->setDescription('Perform session garbage collection');
- $this->addOption('application', 'app', InputOption::VALUE_OPTIONAL, 'The application to perform garbage collection for.', 'site');
- $this->setHelp($help);
- }
-
- /**
- * Get the session service for the requested application.
- *
- * @param string $application The application session service to retrieve
- *
- * @return SessionInterface
- *
- * @since 4.0.0
- */
- private function getSessionService(string $application): SessionInterface
- {
- if (!$this->getContainer()->has("session.web.$application"))
- {
- throw new \InvalidArgumentException(
- sprintf(
- 'The `%s` application is not a valid option.',
- $application
- )
- );
- }
-
- return $this->getContainer()->get("session.web.$application");
- }
+ $this->setDescription('Perform session garbage collection');
+ $this->addOption('application', 'app', InputOption::VALUE_OPTIONAL, 'The application to perform garbage collection for.', 'site');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Get the session service for the requested application.
+ *
+ * @param string $application The application session service to retrieve
+ *
+ * @return SessionInterface
+ *
+ * @since 4.0.0
+ */
+ private function getSessionService(string $application): SessionInterface
+ {
+ if (!$this->getContainer()->has("session.web.$application")) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'The `%s` application is not a valid option.',
+ $application
+ )
+ );
+ }
+
+ return $this->getContainer()->get("session.web.$application");
+ }
}
diff --git a/libraries/src/Console/SessionMetadataGcCommand.php b/libraries/src/Console/SessionMetadataGcCommand.php
index a018c9ea5759e..d49b315c79ff2 100644
--- a/libraries/src/Console/SessionMetadataGcCommand.php
+++ b/libraries/src/Console/SessionMetadataGcCommand.php
@@ -1,4 +1,5 @@
session = $session;
- $this->metadataManager = $metadataManager;
-
- parent::__construct();
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $symfonyStyle = new SymfonyStyle($input, $output);
-
- $symfonyStyle->title('Running Session Metadata Garbage Collection');
-
- $sessionExpire = $this->session->getExpire();
-
- $this->metadataManager->deletePriorTo(time() - $sessionExpire);
-
- $symfonyStyle->success('Metadata garbage collection completed.');
-
- return Command::FAILURE;
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% runs the garbage collection operation for Joomla session metadata
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'session:metadata:gc';
+
+ /**
+ * The session metadata manager.
+ *
+ * @var MetadataManager
+ * @since 4.0.0
+ */
+ private $metadataManager;
+
+ /**
+ * The session object.
+ *
+ * @var SessionInterface
+ * @since 4.0.0
+ */
+ private $session;
+
+ /**
+ * Instantiate the command.
+ *
+ * @param SessionInterface $session The session object.
+ * @param MetadataManager $metadataManager The session metadata manager.
+ *
+ * @since 4.0.0
+ */
+ public function __construct(SessionInterface $session, MetadataManager $metadataManager)
+ {
+ $this->session = $session;
+ $this->metadataManager = $metadataManager;
+
+ parent::__construct();
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $symfonyStyle = new SymfonyStyle($input, $output);
+
+ $symfonyStyle->title('Running Session Metadata Garbage Collection');
+
+ $sessionExpire = $this->session->getExpire();
+
+ $this->metadataManager->deletePriorTo(time() - $sessionExpire);
+
+ $symfonyStyle->success('Metadata garbage collection completed.');
+
+ return Command::FAILURE;
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% runs the garbage collection operation for Joomla session metadata
\nUsage: php %command.full_name% ";
- $this->setDescription('Perform session metadata garbage collection');
- $this->setHelp($help);
- }
+ $this->setDescription('Perform session metadata garbage collection');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/SetConfigurationCommand.php b/libraries/src/Console/SetConfigurationCommand.php
index 14de5c28a8529..00fcaf1f0713d 100644
--- a/libraries/src/Console/SetConfigurationCommand.php
+++ b/libraries/src/Console/SetConfigurationCommand.php
@@ -1,4 +1,5 @@
load('', JPATH_INSTALLATION, null, false, false) ||
- $language->load('', JPATH_INSTALLATION, null, true);
- $language->load('com_config', JPATH_ADMINISTRATOR, null, false, false)||
- $language->load('com_config', JPATH_ADMINISTRATOR, null, true);
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Collects options from user input
- *
- * @param array $options Options input by users
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- private function retrieveOptionsFromInput(array $options): bool
- {
- $collected = [];
-
- foreach ($options as $option)
- {
- if (strpos($option, '=') === false)
- {
- $this->ioStyle->error('Options and values should be separated by "="');
-
- return false;
- }
-
- list($option, $value) = explode('=', $option);
-
- $collected[$option] = $value;
- }
-
- $this->options = $collected;
-
- return true;
- }
-
- /**
- * Validates the options provided
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- private function validateOptions(): bool
- {
- $config = $this->getInitialConfigurationOptions();
-
- $configs = $config->toArray();
-
- $valid = true;
- array_walk(
- $this->options, function ($value, $key) use ($configs, &$valid) {
- if (!array_key_exists($key, $configs))
- {
- $this->ioStyle->error("Can't find option *$key* in configuration list");
- $valid = false;
- }
- }
- );
-
- return $valid;
- }
-
- /**
- * Sets the options array
- *
- * @param string $options Options string
- *
- * @since 4.0.0
- *
- * @return void
- */
- public function setOptions($options)
- {
- $this->options = explode(' ', $options);
- }
-
- /**
- * Collects the options array
- *
- * @return array|mixed
- *
- * @since 4.0.0
- */
- public function getOptions()
- {
- return $this->cliInput->getArgument('options');
- }
-
- /**
- * Returns Default configuration Object
- *
- * @return Registry
- *
- * @since 4.0.0
- */
- public function getInitialConfigurationOptions(): Registry
- {
- return (new Registry(new \JConfig));
- }
-
-
- /**
- * Save the configuration file
- *
- * @param array $options Options array
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- public function saveConfiguration($options): bool
- {
- $app = $this->getApplication();
-
- // Check db connection encryption properties
- $model = $app->bootComponent('com_config')->getMVCFactory($app)->createModel('Application', 'Administrator');
-
- if (!$model->save($options))
- {
- $this->ioStyle->error(Text::_('Failed to save properties'));
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $this->addArgument(
- 'options',
- InputArgument::REQUIRED | InputArgument::IS_ARRAY,
- 'All the options you want to set'
- );
-
- $help = "%command.name% sets the value for a configuration option
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'config:set';
+
+ /**
+ * Stores the Input Object
+ * @var Input
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Options Array
+ * @var array
+ * @since 4.0.0
+ */
+ private $options;
+
+
+ /**
+ * Return code if configuration is set successfully
+ * @since 4.0.0
+ */
+ public const CONFIG_SET_SUCCESSFUL = 0;
+
+ /**
+ * Return code if configuration set failed
+ * @since 4.0.0
+ */
+ public const CONFIG_SET_FAILED = 1;
+
+ /**
+ * Return code if config validation failed
+ * @since 4.0.0
+ */
+ public const CONFIG_VALIDATION_FAILED = 2;
+
+ /**
+ * Return code if options are wrong
+ * @since 4.0.0
+ */
+ public const CONFIG_OPTIONS_WRONG = 3;
+
+ /**
+ * Return code if database validation failed
+ * @since 4.0.0
+ */
+ public const DB_VALIDATION_FAILED = 4;
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $language = Factory::getLanguage();
+ $language->load('', JPATH_INSTALLATION, null, false, false) ||
+ $language->load('', JPATH_INSTALLATION, null, true);
+ $language->load('com_config', JPATH_ADMINISTRATOR, null, false, false) ||
+ $language->load('com_config', JPATH_ADMINISTRATOR, null, true);
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Collects options from user input
+ *
+ * @param array $options Options input by users
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ private function retrieveOptionsFromInput(array $options): bool
+ {
+ $collected = [];
+
+ foreach ($options as $option) {
+ if (strpos($option, '=') === false) {
+ $this->ioStyle->error('Options and values should be separated by "="');
+
+ return false;
+ }
+
+ list($option, $value) = explode('=', $option);
+
+ $collected[$option] = $value;
+ }
+
+ $this->options = $collected;
+
+ return true;
+ }
+
+ /**
+ * Validates the options provided
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ private function validateOptions(): bool
+ {
+ $config = $this->getInitialConfigurationOptions();
+
+ $configs = $config->toArray();
+
+ $valid = true;
+ array_walk(
+ $this->options,
+ function ($value, $key) use ($configs, &$valid) {
+ if (!array_key_exists($key, $configs)) {
+ $this->ioStyle->error("Can't find option *$key* in configuration list");
+ $valid = false;
+ }
+ }
+ );
+
+ return $valid;
+ }
+
+ /**
+ * Sets the options array
+ *
+ * @param string $options Options string
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ */
+ public function setOptions($options)
+ {
+ $this->options = explode(' ', $options);
+ }
+
+ /**
+ * Collects the options array
+ *
+ * @return array|mixed
+ *
+ * @since 4.0.0
+ */
+ public function getOptions()
+ {
+ return $this->cliInput->getArgument('options');
+ }
+
+ /**
+ * Returns Default configuration Object
+ *
+ * @return Registry
+ *
+ * @since 4.0.0
+ */
+ public function getInitialConfigurationOptions(): Registry
+ {
+ return (new Registry(new \JConfig()));
+ }
+
+
+ /**
+ * Save the configuration file
+ *
+ * @param array $options Options array
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function saveConfiguration($options): bool
+ {
+ $app = $this->getApplication();
+
+ // Check db connection encryption properties
+ $model = $app->bootComponent('com_config')->getMVCFactory($app)->createModel('Application', 'Administrator');
+
+ if (!$model->save($options)) {
+ $this->ioStyle->error(Text::_('Failed to save properties'));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $this->addArgument(
+ 'options',
+ InputArgument::REQUIRED | InputArgument::IS_ARRAY,
+ 'All the options you want to set'
+ );
+
+ $help = "%command.name% sets the value for a configuration option
\nUsage: php %command.full_name% =";
- $this->setDescription('Set a value for a configuration option');
- $this->setHelp($help);
- }
-
- /**
- * Verifies database connection
- *
- * @param array $options Options array
- *
- * @return boolean|\Joomla\Database\DatabaseInterface
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function checkDb($options): bool
- {
- // Ensure a database type was selected.
- if (empty($options['dbtype']))
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_INVALID_TYPE'));
-
- return false;
- }
-
- // Ensure that a hostname and user name were input.
- if (empty($options['host']) || empty($options['user']))
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_INVALID_DB_DETAILS'));
-
- return false;
- }
-
- // Validate database table prefix.
- if (isset($options['dbprefix']) && !preg_match('#^[a-zA-Z]+[a-zA-Z0-9_]*$#', $options['dbprefix']))
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_PREFIX_MSG'));
-
- return false;
- }
-
- // Validate length of database table prefix.
- if (isset($options['dbprefix']) && strlen($options['dbprefix']) > 15)
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_FIX_TOO_LONG'), 'warning');
-
- return false;
- }
-
- // Validate length of database name.
- if (strlen($options['db']) > 64)
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_TOO_LONG'));
-
- return false;
- }
-
- // Validate database name.
- if (in_array($options['dbtype'], ['pgsql', 'postgresql'], true) && !preg_match('#^[a-zA-Z_][0-9a-zA-Z_$]*$#', $options['db']))
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_MSG_POSTGRES'));
-
- return false;
- }
-
- if (in_array($options['dbtype'], ['mysql', 'mysqli']) && preg_match('#[\\\\\/]#', $options['db']))
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_MSG_MYSQL'));
-
- return false;
- }
-
- // Workaround for UPPERCASE table prefix for PostgreSQL
- if (in_array($options['dbtype'], ['pgsql', 'postgresql']))
- {
- if (isset($options['dbprefix']) && strtolower($options['dbprefix']) !== $options['dbprefix'])
- {
- $this->ioStyle->error(Text::_('INSTL_DATABASE_FIX_LOWERCASE'));
-
- return false;
- }
- }
-
- $app = $this->getApplication();
-
- // Check db connection encryption properties
- $model = $app->bootComponent('com_config')->getMVCFactory($app)->createModel('Application', 'Administrator');
-
- if (!$model->validateDbConnection($options))
- {
- $this->ioStyle->error(Text::_('Failed to validate the db connection encryption properties'));
-
- return false;
- }
-
- // Build the connection options array.
- $settings = [
- 'driver' => $options['dbtype'],
- 'host' => $options['host'],
- 'user' => $options['user'],
- 'password' => $options['password'],
- 'database' => $options['db'],
- 'prefix' => $options['dbprefix'],
- ];
-
- if ((int) $options['dbencryption'] !== 0)
- {
- $settings['ssl'] = [
- 'enable' => true,
- 'verify_server_cert' => (bool) $options['dbsslverifyservercert'],
- ];
-
- foreach (['cipher', 'ca', 'key', 'cert'] as $value)
- {
- $confVal = trim($options['dbssl' . $value]);
-
- if ($confVal !== '')
- {
- $settings['ssl'][$value] = $confVal;
- }
- }
- }
-
- // Get a database object.
- try
- {
- $db = DatabaseDriver::getInstance($settings);
- $db->getVersion();
- }
- catch (\Exception $e)
- {
- $this->ioStyle->error(
- Text::sprintf(
- 'Cannot connect to database, verify that you specified the correct database details %s',
- $e->getMessage()
- )
- );
-
- return false;
- }
-
- if ((int) $options['dbencryption'] !== 0 && empty($db->getConnectionEncryption()))
- {
- if ($db->isConnectionEncryptionSupported())
- {
- $this->ioStyle->error(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'));
- }
- else
- {
- $this->ioStyle->error(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'));
- }
-
- return false;
- }
-
- return true;
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- * @throws \Exception
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $options = $this->getOptions();
-
- if (!$this->retrieveOptionsFromInput($options))
- {
- return self::CONFIG_OPTIONS_WRONG;
- }
-
- if (!$this->validateOptions())
- {
- return self::CONFIG_VALIDATION_FAILED;
- }
-
- $initialOptions = $this->getInitialConfigurationOptions()->toArray();
-
- $combinedOptions = $this->sanitizeOptions(array_merge($initialOptions, $this->options));
-
- if (!$this->checkDb($combinedOptions))
- {
- return self::DB_VALIDATION_FAILED;
- }
-
- if ($this->saveConfiguration($combinedOptions))
- {
- $this->ioStyle->success('Configuration set');
-
- return self::CONFIG_SET_SUCCESSFUL;
- }
-
- return self::CONFIG_SET_FAILED;
- }
-
- /**
- * Sanitize the options array for boolean
- *
- * @param array $options Options array
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function sanitizeOptions(Array $options): array
- {
- foreach ($options as $key => $value)
- {
- $value = $value === 'false' ? false : $value;
- $value = $value === 'true' ? true : $value;
-
- $options[$key] = $value;
- }
-
- return $options;
- }
+ $this->setDescription('Set a value for a configuration option');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Verifies database connection
+ *
+ * @param array $options Options array
+ *
+ * @return boolean|\Joomla\Database\DatabaseInterface
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function checkDb($options): bool
+ {
+ // Ensure a database type was selected.
+ if (empty($options['dbtype'])) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_INVALID_TYPE'));
+
+ return false;
+ }
+
+ // Ensure that a hostname and user name were input.
+ if (empty($options['host']) || empty($options['user'])) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_INVALID_DB_DETAILS'));
+
+ return false;
+ }
+
+ // Validate database table prefix.
+ if (isset($options['dbprefix']) && !preg_match('#^[a-zA-Z]+[a-zA-Z0-9_]*$#', $options['dbprefix'])) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_PREFIX_MSG'));
+
+ return false;
+ }
+
+ // Validate length of database table prefix.
+ if (isset($options['dbprefix']) && strlen($options['dbprefix']) > 15) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_FIX_TOO_LONG'), 'warning');
+
+ return false;
+ }
+
+ // Validate length of database name.
+ if (strlen($options['db']) > 64) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_TOO_LONG'));
+
+ return false;
+ }
+
+ // Validate database name.
+ if (in_array($options['dbtype'], ['pgsql', 'postgresql'], true) && !preg_match('#^[a-zA-Z_][0-9a-zA-Z_$]*$#', $options['db'])) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_MSG_POSTGRES'));
+
+ return false;
+ }
+
+ if (in_array($options['dbtype'], ['mysql', 'mysqli']) && preg_match('#[\\\\\/]#', $options['db'])) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_NAME_MSG_MYSQL'));
+
+ return false;
+ }
+
+ // Workaround for UPPERCASE table prefix for PostgreSQL
+ if (in_array($options['dbtype'], ['pgsql', 'postgresql'])) {
+ if (isset($options['dbprefix']) && strtolower($options['dbprefix']) !== $options['dbprefix']) {
+ $this->ioStyle->error(Text::_('INSTL_DATABASE_FIX_LOWERCASE'));
+
+ return false;
+ }
+ }
+
+ $app = $this->getApplication();
+
+ // Check db connection encryption properties
+ $model = $app->bootComponent('com_config')->getMVCFactory($app)->createModel('Application', 'Administrator');
+
+ if (!$model->validateDbConnection($options)) {
+ $this->ioStyle->error(Text::_('Failed to validate the db connection encryption properties'));
+
+ return false;
+ }
+
+ // Build the connection options array.
+ $settings = [
+ 'driver' => $options['dbtype'],
+ 'host' => $options['host'],
+ 'user' => $options['user'],
+ 'password' => $options['password'],
+ 'database' => $options['db'],
+ 'prefix' => $options['dbprefix'],
+ ];
+
+ if ((int) $options['dbencryption'] !== 0) {
+ $settings['ssl'] = [
+ 'enable' => true,
+ 'verify_server_cert' => (bool) $options['dbsslverifyservercert'],
+ ];
+
+ foreach (['cipher', 'ca', 'key', 'cert'] as $value) {
+ $confVal = trim($options['dbssl' . $value]);
+
+ if ($confVal !== '') {
+ $settings['ssl'][$value] = $confVal;
+ }
+ }
+ }
+
+ // Get a database object.
+ try {
+ $db = DatabaseDriver::getInstance($settings);
+ $db->getVersion();
+ } catch (\Exception $e) {
+ $this->ioStyle->error(
+ Text::sprintf(
+ 'Cannot connect to database, verify that you specified the correct database details %s',
+ $e->getMessage()
+ )
+ );
+
+ return false;
+ }
+
+ if ((int) $options['dbencryption'] !== 0 && empty($db->getConnectionEncryption())) {
+ if ($db->isConnectionEncryptionSupported()) {
+ $this->ioStyle->error(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_CONN_NOT_ENCRYPT'));
+ } else {
+ $this->ioStyle->error(Text::_('COM_CONFIG_ERROR_DATABASE_ENCRYPTION_SRV_NOT_SUPPORTS'));
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $options = $this->getOptions();
+
+ if (!$this->retrieveOptionsFromInput($options)) {
+ return self::CONFIG_OPTIONS_WRONG;
+ }
+
+ if (!$this->validateOptions()) {
+ return self::CONFIG_VALIDATION_FAILED;
+ }
+
+ $initialOptions = $this->getInitialConfigurationOptions()->toArray();
+
+ $combinedOptions = $this->sanitizeOptions(array_merge($initialOptions, $this->options));
+
+ if (!$this->checkDb($combinedOptions)) {
+ return self::DB_VALIDATION_FAILED;
+ }
+
+ if ($this->saveConfiguration($combinedOptions)) {
+ $this->ioStyle->success('Configuration set');
+
+ return self::CONFIG_SET_SUCCESSFUL;
+ }
+
+ return self::CONFIG_SET_FAILED;
+ }
+
+ /**
+ * Sanitize the options array for boolean
+ *
+ * @param array $options Options array
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function sanitizeOptions(array $options): array
+ {
+ foreach ($options as $key => $value) {
+ $value = $value === 'false' ? false : $value;
+ $value = $value === 'true' ? true : $value;
+
+ $options[$key] = $value;
+ }
+
+ return $options;
+ }
}
diff --git a/libraries/src/Console/SiteDownCommand.php b/libraries/src/Console/SiteDownCommand.php
index c8cd6707a55e1..18ca0801f43ad 100644
--- a/libraries/src/Console/SiteDownCommand.php
+++ b/libraries/src/Console/SiteDownCommand.php
@@ -1,4 +1,5 @@
ioStyle = new SymfonyStyle($input, $output);
- }
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% puts the site into offline mode
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% puts the site into offline mode
\nUsage: php %command.full_name% ";
- $this->setDescription('Put the site into offline mode');
- $this->setHelp($help);
- }
+ $this->setDescription('Put the site into offline mode');
+ $this->setHelp($help);
+ }
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
- $returnCode = $this->getApplication()->getCommand(SetConfigurationCommand::getDefaultName())->execute(
- new ArrayInput(['options' => ['offline=true']]), $output
- );
+ $returnCode = $this->getApplication()->getCommand(SetConfigurationCommand::getDefaultName())->execute(
+ new ArrayInput(['options' => ['offline=true']]),
+ $output
+ );
- if ($returnCode === 0)
- {
- $this->ioStyle->success("Website is now offline");
+ if ($returnCode === 0) {
+ $this->ioStyle->success("Website is now offline");
- return self::SITE_DOWN_SUCCESSFUL;
- }
+ return self::SITE_DOWN_SUCCESSFUL;
+ }
- return self::SITE_DOWN_FAILED;
- }
+ return self::SITE_DOWN_FAILED;
+ }
}
diff --git a/libraries/src/Console/SiteUpCommand.php b/libraries/src/Console/SiteUpCommand.php
index 009ef6893d4db..6bd4a66bbefd1 100644
--- a/libraries/src/Console/SiteUpCommand.php
+++ b/libraries/src/Console/SiteUpCommand.php
@@ -1,4 +1,5 @@
ioStyle = new SymfonyStyle($input, $output);
- }
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% puts the site into online mode
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% puts the site into online mode
\nUsage: php %command.full_name% ";
- $this->setDescription('Put the site into online mode');
- $this->setHelp($help);
- }
+ $this->setDescription('Put the site into online mode');
+ $this->setHelp($help);
+ }
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- */
- protected function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
- $returnCode = $this->getApplication()->getCommand(SetConfigurationCommand::getDefaultName())->execute(
- new ArrayInput(['options' => ['offline=false']]), $output
- );
+ $returnCode = $this->getApplication()->getCommand(SetConfigurationCommand::getDefaultName())->execute(
+ new ArrayInput(['options' => ['offline=false']]),
+ $output
+ );
- if ($returnCode === 0)
- {
- $this->ioStyle->success("Website is now online");
+ if ($returnCode === 0) {
+ $this->ioStyle->success("Website is now online");
- return self::SITE_UP_SUCCESSFUL;
- }
+ return self::SITE_UP_SUCCESSFUL;
+ }
- return self::SITE_UP_FAILED;
- }
+ return self::SITE_UP_FAILED;
+ }
}
diff --git a/libraries/src/Console/TasksListCommand.php b/libraries/src/Console/TasksListCommand.php
index bb9aaa5f19ee0..1c5928e7c4a1e 100644
--- a/libraries/src/Console/TasksListCommand.php
+++ b/libraries/src/Console/TasksListCommand.php
@@ -1,4 +1,5 @@
getLanguage()->load('joomla', JPATH_ADMINISTRATOR);
-
- $this->configureIO($input, $output);
- $this->ioStyle->title('List Scheduled Tasks');
-
- $tasks = array_map(
- function (\stdClass $task): array {
- $enabled = $task->state === 1;
- $nextExec = Factory::getDate($task->next_execution, 'UTC');
- $due = $enabled && $task->taskOption && Factory::getDate('now', 'UTC') > $nextExec;
-
- return [
- 'id' => $task->id,
- 'title' => $task->title,
- 'type' => $task->safeTypeTitle,
- 'state' => $task->state === 1 ? 'Enabled' : ($task->state === 0 ? 'Disabled' : 'Trashed'),
- 'next_execution' => $due ? 'DUE!' : $nextExec->toRFC822(),
- ];
- },
- $this->getTasks()
- );
-
- $this->ioStyle->table(['id', 'title', 'type', 'state', 'next run'], $tasks);
-
- return 0;
- }
-
- /**
- * Returns a stdClass object array of scheduled tasks.
- *
- * @return array
- *
- * @since 4.1.0
- * @throws \RunTimeException
- */
- private function getTasks(): array
- {
- $scheduler = new Scheduler;
-
- return $scheduler->fetchTaskRecords(
- ['state' => '*'],
- ['ordering' => 'a.title', 'select' => 'a.id, a.title, a.type, a.state, a.next_execution']
- );
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function configure(): void
- {
- $help = "%command.name% lists all scheduled tasks.
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected static $defaultName = 'scheduler:list';
+
+ /**
+ * The console application object
+ *
+ * @var Application
+ * @since 4.1.0
+ */
+ protected $application;
+
+ /**
+ * @var SymfonyStyle
+ * @since 4.1.0
+ */
+ private $ioStyle;
+
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ Factory::getApplication()->getLanguage()->load('joomla', JPATH_ADMINISTRATOR);
+
+ $this->configureIO($input, $output);
+ $this->ioStyle->title('List Scheduled Tasks');
+
+ $tasks = array_map(
+ function (\stdClass $task): array {
+ $enabled = $task->state === 1;
+ $nextExec = Factory::getDate($task->next_execution, 'UTC');
+ $due = $enabled && $task->taskOption && Factory::getDate('now', 'UTC') > $nextExec;
+
+ return [
+ 'id' => $task->id,
+ 'title' => $task->title,
+ 'type' => $task->safeTypeTitle,
+ 'state' => $task->state === 1 ? 'Enabled' : ($task->state === 0 ? 'Disabled' : 'Trashed'),
+ 'next_execution' => $due ? 'DUE!' : $nextExec->toRFC822(),
+ ];
+ },
+ $this->getTasks()
+ );
+
+ $this->ioStyle->table(['id', 'title', 'type', 'state', 'next run'], $tasks);
+
+ return 0;
+ }
+
+ /**
+ * Returns a stdClass object array of scheduled tasks.
+ *
+ * @return array
+ *
+ * @since 4.1.0
+ * @throws \RunTimeException
+ */
+ private function getTasks(): array
+ {
+ $scheduler = new Scheduler();
+
+ return $scheduler->fetchTaskRecords(
+ ['state' => '*'],
+ ['ordering' => 'a.title', 'select' => 'a.id, a.title, a.type, a.state, a.next_execution']
+ );
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% lists all scheduled tasks.
\nUsage: php %command.full_name% ";
- $this->setDescription('List all scheduled tasks');
- $this->setHelp($help);
- }
+ $this->setDescription('List all scheduled tasks');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/TasksRunCommand.php b/libraries/src/Console/TasksRunCommand.php
index f13993457db90..7a6a2125258bc 100644
--- a/libraries/src/Console/TasksRunCommand.php
+++ b/libraries/src/Console/TasksRunCommand.php
@@ -1,4 +1,5 @@
'Task#%1$02d \'%2$s\' processed in %3$.2f seconds.',
- Status::WILL_RESUME => 'Task#%1$02d \'%2$s\' ran for %3$.2f seconds, will resume next time. ',
- Status::NO_RUN => 'Task#%1$02d \'%2$s\' failed to run. Is it already running? ',
- Status::NO_ROUTINE => 'Task#%1$02d \'%2$s\' is orphaned! Visit the backend to resolve. ',
- 'N/A' => 'Task#%1$02d \'%2$s\' exited with code %4$d in %3$.2f seconds. ',
- ];
-
- $this->configureIo($input, $output);
- $this->ioStyle->title('Run tasks');
-
- $scheduler = new Scheduler;
-
- $id = $input->getOption('id');
- $all = $input->getOption('all');
-
- if ($id)
- {
- $records[] = $scheduler->fetchTaskRecord($id);
- }
- else
- {
- $filters = $scheduler::TASK_QUEUE_FILTERS;
- $listConfig = $scheduler::TASK_QUEUE_LIST_CONFIG;
- $listConfig['limit'] = ($all ? null : 1);
-
- $records = $scheduler->fetchTaskRecords($filters, $listConfig);
- }
-
- if ($id && !$records[0])
- {
- $this->ioStyle->writeln('No matching task found! ');
-
- return Status::NO_TASK;
- }
- elseif (!$records)
- {
- $this->ioStyle->writeln('No tasks due! ');
-
- return Status::NO_TASK;
- }
-
- $status = ['startTime' => microtime(true)];
- $taskCount = \count($records);
- $exit = Status::OK;
-
- foreach ($records as $record)
- {
- $cStart = microtime(true);
- $task = $scheduler->runTask(['id' => $record->id, 'allowDisabled' => true, 'allowConcurrent' => true]);
- $exit = empty($task) ? Status::NO_RUN : $task->getContent()['status'];
- $duration = microtime(true) - $cStart;
- $key = (\array_key_exists($exit, $outTextMap)) ? $exit : 'N/A';
- $this->ioStyle->writeln(sprintf($outTextMap[$key], $record->id, $record->title, $duration, $exit));
- }
-
- $netTime = round(microtime(true) - $status['startTime'], 2);
- $this->ioStyle->newLine();
- $this->ioStyle->writeln("Finished running $taskCount tasks in $netTime seconds. ");
-
- return $taskCount === 1 ? $exit : Status::OK;
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function configure(): void
- {
- $this->addOption('id', 'i', InputOption::VALUE_REQUIRED, 'The id of the task to run.');
- $this->addOption('all', '', InputOption::VALUE_NONE, 'Run all due tasks. Note that this is overridden if --id is used.');
-
- $help = "%command.name% run scheduled tasks.
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected static $defaultName = 'scheduler:run';
+
+ /**
+ * @var SymfonyStyle
+ * @since 4.1.0
+ */
+ private $ioStyle;
+
+ /**
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code.
+ *
+ * @since 4.1.0
+ * @throws \RunTimeException
+ * @throws InvalidArgumentException
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ /**
+ * Not as a class constant because of some the autoload order doesn't let us
+ * load the namespace when it's time to do that (why?)
+ */
+ static $outTextMap = [
+ Status::OK => 'Task#%1$02d \'%2$s\' processed in %3$.2f seconds.',
+ Status::WILL_RESUME => 'Task#%1$02d \'%2$s\' ran for %3$.2f seconds, will resume next time. ',
+ Status::NO_RUN => 'Task#%1$02d \'%2$s\' failed to run. Is it already running? ',
+ Status::NO_ROUTINE => 'Task#%1$02d \'%2$s\' is orphaned! Visit the backend to resolve. ',
+ 'N/A' => 'Task#%1$02d \'%2$s\' exited with code %4$d in %3$.2f seconds. ',
+ ];
+
+ $this->configureIo($input, $output);
+ $this->ioStyle->title('Run tasks');
+
+ $scheduler = new Scheduler();
+
+ $id = $input->getOption('id');
+ $all = $input->getOption('all');
+
+ if ($id) {
+ $records[] = $scheduler->fetchTaskRecord($id);
+ } else {
+ $filters = $scheduler::TASK_QUEUE_FILTERS;
+ $listConfig = $scheduler::TASK_QUEUE_LIST_CONFIG;
+ $listConfig['limit'] = ($all ? null : 1);
+
+ $records = $scheduler->fetchTaskRecords($filters, $listConfig);
+ }
+
+ if ($id && !$records[0]) {
+ $this->ioStyle->writeln('No matching task found! ');
+
+ return Status::NO_TASK;
+ } elseif (!$records) {
+ $this->ioStyle->writeln('No tasks due! ');
+
+ return Status::NO_TASK;
+ }
+
+ $status = ['startTime' => microtime(true)];
+ $taskCount = \count($records);
+ $exit = Status::OK;
+
+ foreach ($records as $record) {
+ $cStart = microtime(true);
+ $task = $scheduler->runTask(['id' => $record->id, 'allowDisabled' => true, 'allowConcurrent' => true]);
+ $exit = empty($task) ? Status::NO_RUN : $task->getContent()['status'];
+ $duration = microtime(true) - $cStart;
+ $key = (\array_key_exists($exit, $outTextMap)) ? $exit : 'N/A';
+ $this->ioStyle->writeln(sprintf($outTextMap[$key], $record->id, $record->title, $duration, $exit));
+ }
+
+ $netTime = round(microtime(true) - $status['startTime'], 2);
+ $this->ioStyle->newLine();
+ $this->ioStyle->writeln("Finished running $taskCount tasks in $netTime seconds. ");
+
+ return $taskCount === 1 ? $exit : Status::OK;
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function configure(): void
+ {
+ $this->addOption('id', 'i', InputOption::VALUE_REQUIRED, 'The id of the task to run.');
+ $this->addOption('all', '', InputOption::VALUE_NONE, 'Run all due tasks. Note that this is overridden if --id is used.');
+
+ $help = "%command.name% run scheduled tasks.
\nUsage: php %command.full_name% [flags] ";
- $this->setDescription('Run one or more scheduled tasks');
- $this->setHelp($help);
- }
+ $this->setDescription('Run one or more scheduled tasks');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/TasksStateCommand.php b/libraries/src/Console/TasksStateCommand.php
index 9abd8d375dabd..91c426f7f4ae3 100644
--- a/libraries/src/Console/TasksStateCommand.php
+++ b/libraries/src/Console/TasksStateCommand.php
@@ -1,4 +1,5 @@
getLanguage()->load('joomla', JPATH_ADMINISTRATOR);
-
- $this->configureIO($input, $output);
-
- $id = (string) $input->getOption('id');
- $state = (string) $input->getOption('state');
-
- // Try to validate and process ID, if passed
- if (\strlen($id))
- {
- if (!Task::isValidId($id))
- {
- $this->ioStyle->error('Invalid id passed!');
-
- return 2;
- }
-
- $id = (is_numeric($id)) ? ($id + 0) : $id;
- }
-
- // Try to validate and process state, if passed
- if (\strlen($state))
- {
- // If we get the logical state, we try to get the enumeration (but as a string)
- if (!is_numeric($state))
- {
- $state = (string) ArrayHelper::arraySearch($state, Task::STATE_MAP);
- }
-
- if (!\strlen($state) || !Task::isValidState($state))
- {
- $this->ioStyle->error('Invalid state passed!');
-
- return 2;
- }
- }
-
- // If we didn't get ID as a flag, ask for it interactively
- while (!Task::isValidId($id))
- {
- $id = $this->ioStyle->ask('Please specify the ID of the task');
- }
-
- // If we didn't get state as a flag, ask for it interactively
- while ($state === false || !Task::isValidState($state))
- {
- $state = (string) $this->ioStyle->ask('Should the state be "enable" (1), "disable" (0) or "trash" (-2)');
-
- // Ensure we have the enumerated value (still as a string)
- $state = (Task::isValidState($state)) ? $state : ArrayHelper::arraySearch($state, Task::STATE_MAP);
- }
-
- // Finally, the enumerated state and id in their pure form
- $state = (int) $state;
- $id = (int) $id;
-
- /** @var ConsoleApplication $app */
- $app = $this->getApplication();
-
- /** @var TaskModel $taskModel */
- $taskModel = $app->bootComponent('com_scheduler')->getMVCFactory()->createModel('Task', 'Administrator');
-
- $task = $taskModel->getItem($id);
-
- // We couldn't fetch that task :(
- if (empty($task->id))
- {
- $this->ioStyle->error("Task ID '${id}' does not exist!");
-
- return 1;
- }
-
- // If the item is checked-out we need a check in (currently not possible through the CLI)
- if ($taskModel->isCheckedOut($task))
- {
- $this->ioStyle->error("Task ID '${id}' is checked out!");
-
- return 1;
- }
-
- /** @var TaskTable $table */
- $table = $taskModel->getTable();
-
- $action = Task::STATE_MAP[$state];
-
- if (!$table->publish($id, $state))
- {
- $this->ioStyle->error("Can't ${action} Task ID '${id}'");
-
- return 3;
- }
-
- $this->ioStyle->success("Task ID ${id} ${action}.");
-
- return 0;
- }
-
- /**
- * Configure the IO.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- private function configureIO(InputInterface $input, OutputInterface $output): void
- {
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Configure the command.
- *
- * @return void
- *
- * @since 4.1.0
- */
- protected function configure(): void
- {
- $this->addOption('id', 'i', InputOption::VALUE_REQUIRED, 'The id of the task to change state.');
- $this->addOption('state', 's', InputOption::VALUE_REQUIRED, 'The new state of the task, can be 1/enable, 0/disable, or -2/trash.');
-
- $help = "%command.name% changes the state of a task.
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.1.0
+ */
+ protected static $defaultName = 'scheduler:state';
+
+ /**
+ * The console application object
+ *
+ * @var Application
+ *
+ * @since 4.1.0
+ */
+ protected $application;
+
+ /**
+ * @var SymfonyStyle
+ *
+ * @since 4.1.0
+ */
+ private $ioStyle;
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.1.0
+ * @throws \Exception
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ Factory::getApplication()->getLanguage()->load('joomla', JPATH_ADMINISTRATOR);
+
+ $this->configureIO($input, $output);
+
+ $id = (string) $input->getOption('id');
+ $state = (string) $input->getOption('state');
+
+ // Try to validate and process ID, if passed
+ if (\strlen($id)) {
+ if (!Task::isValidId($id)) {
+ $this->ioStyle->error('Invalid id passed!');
+
+ return 2;
+ }
+
+ $id = (is_numeric($id)) ? ($id + 0) : $id;
+ }
+
+ // Try to validate and process state, if passed
+ if (\strlen($state)) {
+ // If we get the logical state, we try to get the enumeration (but as a string)
+ if (!is_numeric($state)) {
+ $state = (string) ArrayHelper::arraySearch($state, Task::STATE_MAP);
+ }
+
+ if (!\strlen($state) || !Task::isValidState($state)) {
+ $this->ioStyle->error('Invalid state passed!');
+
+ return 2;
+ }
+ }
+
+ // If we didn't get ID as a flag, ask for it interactively
+ while (!Task::isValidId($id)) {
+ $id = $this->ioStyle->ask('Please specify the ID of the task');
+ }
+
+ // If we didn't get state as a flag, ask for it interactively
+ while ($state === false || !Task::isValidState($state)) {
+ $state = (string) $this->ioStyle->ask('Should the state be "enable" (1), "disable" (0) or "trash" (-2)');
+
+ // Ensure we have the enumerated value (still as a string)
+ $state = (Task::isValidState($state)) ? $state : ArrayHelper::arraySearch($state, Task::STATE_MAP);
+ }
+
+ // Finally, the enumerated state and id in their pure form
+ $state = (int) $state;
+ $id = (int) $id;
+
+ /** @var ConsoleApplication $app */
+ $app = $this->getApplication();
+
+ /** @var TaskModel $taskModel */
+ $taskModel = $app->bootComponent('com_scheduler')->getMVCFactory()->createModel('Task', 'Administrator');
+
+ $task = $taskModel->getItem($id);
+
+ // We couldn't fetch that task :(
+ if (empty($task->id)) {
+ $this->ioStyle->error("Task ID '${id}' does not exist!");
+
+ return 1;
+ }
+
+ // If the item is checked-out we need a check in (currently not possible through the CLI)
+ if ($taskModel->isCheckedOut($task)) {
+ $this->ioStyle->error("Task ID '${id}' is checked out!");
+
+ return 1;
+ }
+
+ /** @var TaskTable $table */
+ $table = $taskModel->getTable();
+
+ $action = Task::STATE_MAP[$state];
+
+ if (!$table->publish($id, $state)) {
+ $this->ioStyle->error("Can't ${action} Task ID '${id}'");
+
+ return 3;
+ }
+
+ $this->ioStyle->success("Task ID ${id} ${action}.");
+
+ return 0;
+ }
+
+ /**
+ * Configure the IO.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output): void
+ {
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Configure the command.
+ *
+ * @return void
+ *
+ * @since 4.1.0
+ */
+ protected function configure(): void
+ {
+ $this->addOption('id', 'i', InputOption::VALUE_REQUIRED, 'The id of the task to change state.');
+ $this->addOption('state', 's', InputOption::VALUE_REQUIRED, 'The new state of the task, can be 1/enable, 0/disable, or -2/trash.');
+
+ $help = "%command.name% changes the state of a task.
\nUsage: php %command.full_name% ";
- $this->setDescription('Enable, disable or trash a scheduled task');
- $this->setHelp($help);
- }
+ $this->setDescription('Enable, disable or trash a scheduled task');
+ $this->setHelp($help);
+ }
}
diff --git a/libraries/src/Console/UpdateCoreCommand.php b/libraries/src/Console/UpdateCoreCommand.php
index c1b795a1807b0..b8c7d9435c7be 100644
--- a/libraries/src/Console/UpdateCoreCommand.php
+++ b/libraries/src/Console/UpdateCoreCommand.php
@@ -1,4 +1,5 @@
db = $db;
- parent::__construct();
- }
-
- /**
- * Configures the IO
- *
- * @param InputInterface $input Console Input
- * @param OutputInterface $output Console Output
- *
- * @return void
- *
- * @since 4.0.0
- *
- */
- private function configureIO(InputInterface $input, OutputInterface $output)
- {
- ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');
- $this->progressBar = new ProgressBar($output, 8);
- $this->progressBar->setFormat('custom');
-
- $this->cliInput = $input;
- $this->ioStyle = new SymfonyStyle($input, $output);
- }
-
- /**
- * Internal function to execute the command.
- *
- * @param InputInterface $input The input to inject into the command.
- * @param OutputInterface $output The output to inject into the command.
- *
- * @return integer The command exit code
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function doExecute(InputInterface $input, OutputInterface $output): int
- {
- $this->configureIO($input, $output);
-
- $this->progressBar->setMessage("Starting up ...");
- $this->progressBar->start();
-
- $model = $this->getUpdateModel();
-
- $this->setUpdateInfo($model->getUpdateInformation());
-
- $this->progressBar->advance();
- $this->progressBar->setMessage('Running checks ...');
-
- if (!$this->updateInfo['hasUpdate'])
- {
- $this->progressBar->finish();
- $this->ioStyle->note('You already have the latest Joomla! version. ' . $this->updateInfo['latest']);
-
- return self::ERR_CHECKS_FAILED;
- }
-
- $this->progressBar->advance();
- $this->progressBar->setMessage('Starting Joomla! update ...');
-
- if ($this->updateJoomlaCore($model))
- {
- $this->progressBar->finish();
- $this->ioStyle->success('Joomla core updated successfully!');
-
- return self::UPDATE_SUCCESSFUL;
- }
-
- $this->progressBar->finish();
-
- $this->ioStyle->error('Update cannot be performed.');
-
- return self::ERR_UPDATE_FAILED;
- }
-
- /**
- * Initialise the command.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function configure(): void
- {
- $help = "%command.name% is used to update Joomla
+ /**
+ * The default command name
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected static $defaultName = 'core:update';
+
+ /**
+ * Stores the Input Object
+ * @var CliInput
+ * @since 4.0.0
+ */
+ private $cliInput;
+
+ /**
+ * SymfonyStyle Object
+ * @var SymfonyStyle
+ * @since 4.0.0
+ */
+ private $ioStyle;
+
+ /**
+ * Update Information
+ * @var array
+ * @since 4.0.0
+ */
+ public $updateInfo;
+
+ /**
+ * Update Model
+ * @var array
+ * @since 4.0.0
+ */
+ public $updateModel;
+
+ /**
+ * Progress Bar object
+ * @var ProgressBar
+ * @since 4.0.0
+ */
+ public $progressBar;
+
+ /**
+ * Return code for successful update
+ * @since 4.0.0
+ */
+ public const UPDATE_SUCCESSFUL = 0;
+
+ /**
+ * Return code for failed update
+ * @since 4.0.0
+ */
+ public const ERR_UPDATE_FAILED = 2;
+
+ /**
+ * Return code for failed checks
+ * @since 4.0.0
+ */
+ public const ERR_CHECKS_FAILED = 1;
+
+ /**
+ * @var DatabaseInterface
+ * @since 4.0.0
+ */
+ private $db;
+
+ /**
+ * UpdateCoreCommand constructor.
+ *
+ * @param DatabaseInterface $db Database Instance
+ *
+ * @since 4.0.0
+ */
+ public function __construct(DatabaseInterface $db)
+ {
+ $this->db = $db;
+ parent::__construct();
+ }
+
+ /**
+ * Configures the IO
+ *
+ * @param InputInterface $input Console Input
+ * @param OutputInterface $output Console Output
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ *
+ */
+ private function configureIO(InputInterface $input, OutputInterface $output)
+ {
+ ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');
+ $this->progressBar = new ProgressBar($output, 8);
+ $this->progressBar->setFormat('custom');
+
+ $this->cliInput = $input;
+ $this->ioStyle = new SymfonyStyle($input, $output);
+ }
+
+ /**
+ * Internal function to execute the command.
+ *
+ * @param InputInterface $input The input to inject into the command.
+ * @param OutputInterface $output The output to inject into the command.
+ *
+ * @return integer The command exit code
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function doExecute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->configureIO($input, $output);
+
+ $this->progressBar->setMessage("Starting up ...");
+ $this->progressBar->start();
+
+ $model = $this->getUpdateModel();
+
+ $this->setUpdateInfo($model->getUpdateInformation());
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage('Running checks ...');
+
+ if (!$this->updateInfo['hasUpdate']) {
+ $this->progressBar->finish();
+ $this->ioStyle->note('You already have the latest Joomla! version. ' . $this->updateInfo['latest']);
+
+ return self::ERR_CHECKS_FAILED;
+ }
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage('Starting Joomla! update ...');
+
+ if ($this->updateJoomlaCore($model)) {
+ $this->progressBar->finish();
+ $this->ioStyle->success('Joomla core updated successfully!');
+
+ return self::UPDATE_SUCCESSFUL;
+ }
+
+ $this->progressBar->finish();
+
+ $this->ioStyle->error('Update cannot be performed.');
+
+ return self::ERR_UPDATE_FAILED;
+ }
+
+ /**
+ * Initialise the command.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function configure(): void
+ {
+ $help = "%command.name% is used to update Joomla
\nUsage: php %command.full_name% ";
- $this->setDescription('Update Joomla');
- $this->setHelp($help);
- }
-
- /**
- * Update Core Joomla
- *
- * @param mixed $updatemodel Update Model
- *
- * @return boolean success
- *
- * @since 4.0.0
- */
- private function updateJoomlaCore($updatemodel): bool
- {
- $updateInformation = $this->updateInfo;
-
- if (!empty($updateInformation['hasUpdate']))
- {
- $this->progressBar->advance();
- $this->progressBar->setMessage("Processing update package ...");
- $package = $this->processUpdatePackage($updateInformation);
-
- $this->progressBar->advance();
- $this->progressBar->setMessage("Finalizing update ...");
- $result = $updatemodel->finaliseUpgrade();
-
- if ($result)
- {
- $this->progressBar->advance();
- $this->progressBar->setMessage("Cleaning up ...");
-
- // Remove the xml
- if (file_exists(JPATH_BASE . '/joomla.xml'))
- {
- File::delete(JPATH_BASE . '/joomla.xml');
- }
-
- InstallerHelper::cleanupInstall($package['file'], $package['extractdir']);
-
- $updatemodel->purge();
-
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Sets the update Information
- *
- * @param array $data Stores the update information
- *
- * @since 4.0.0
- *
- * @return void
- */
- public function setUpdateInfo($data): void
- {
- $this->updateInfo = $data;
- }
-
- /**
- * Retrieves the Update model from com_joomlaupdate
- *
- * @return mixed
- *
- * @since 4.0.0
- *
- * @throws \Exception
- */
- public function getUpdateModel()
- {
- if (!isset($this->updateModel))
- {
- $this->setUpdateModel();
- }
-
- return $this->updateModel;
- }
-
- /**
- * Sets the Update Model
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setUpdateModel(): void
- {
- $app = $this->getApplication();
- $updatemodel = $app->bootComponent('com_joomlaupdate')->getMVCFactory($app)->createModel('Update', 'Administrator');
-
- if (is_bool($updatemodel))
- {
- $this->updateModel = $updatemodel;
-
- return;
- }
-
- $updatemodel->purge();
- $updatemodel->refreshUpdates(true);
-
- $this->updateModel = $updatemodel;
- }
-
- /**
- * Downloads and extracts the update Package
- *
- * @param array $updateInformation Stores the update information
- *
- * @return array | boolean
- *
- * @since 4.0.0
- */
- public function processUpdatePackage($updateInformation)
- {
- if (!$updateInformation['object'])
- {
- return false;
- }
-
- $this->progressBar->advance();
- $this->progressBar->setMessage("Downloading update package ...");
- $file = $this->downloadFile($updateInformation['object']->downloadurl->_data);
-
- $tmpPath = $this->getApplication()->get('tmp_path');
- $updatePackage = $tmpPath . '/' . $file;
-
- $this->progressBar->advance();
- $this->progressBar->setMessage("Extracting update package ...");
- $package = $this->extractFile($updatePackage);
-
- $this->progressBar->advance();
- $this->progressBar->setMessage("Copying files ...");
- $this->copyFileTo($package['extractdir'], JPATH_BASE);
-
- return ['file' => $updatePackage, 'extractdir' => $package['extractdir']];
- }
-
- /**
- * Downloads the Update file
- *
- * @param string $url URL to update file
- *
- * @return boolean | string
- *
- * @since 4.0.0
- */
- public function downloadFile($url)
- {
- $file = InstallerHelper::downloadPackage($url);
-
- if (!$file)
- {
- return false;
- }
-
- return $file;
- }
-
- /**
- * Extracts Update file
- *
- * @param string $file Full path to file location
- *
- * @return array | boolean
- *
- * @since 4.0.0
- */
- public function extractFile($file)
- {
- $package = InstallerHelper::unpack($file, true);
-
- return $package;
- }
-
- /**
- * Copy a file to a destination directory
- *
- * @param string $file Full path to file
- * @param string $dir Destination directory
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function copyFileTo($file, $dir): void
- {
- Folder::copy($file, $dir, '', true);
- }
+ $this->setDescription('Update Joomla');
+ $this->setHelp($help);
+ }
+
+ /**
+ * Update Core Joomla
+ *
+ * @param mixed $updatemodel Update Model
+ *
+ * @return boolean success
+ *
+ * @since 4.0.0
+ */
+ private function updateJoomlaCore($updatemodel): bool
+ {
+ $updateInformation = $this->updateInfo;
+
+ if (!empty($updateInformation['hasUpdate'])) {
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Processing update package ...");
+ $package = $this->processUpdatePackage($updateInformation);
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Finalizing update ...");
+ $result = $updatemodel->finaliseUpgrade();
+
+ if ($result) {
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Cleaning up ...");
+
+ // Remove the xml
+ if (file_exists(JPATH_BASE . '/joomla.xml')) {
+ File::delete(JPATH_BASE . '/joomla.xml');
+ }
+
+ InstallerHelper::cleanupInstall($package['file'], $package['extractdir']);
+
+ $updatemodel->purge();
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the update Information
+ *
+ * @param array $data Stores the update information
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ */
+ public function setUpdateInfo($data): void
+ {
+ $this->updateInfo = $data;
+ }
+
+ /**
+ * Retrieves the Update model from com_joomlaupdate
+ *
+ * @return mixed
+ *
+ * @since 4.0.0
+ *
+ * @throws \Exception
+ */
+ public function getUpdateModel()
+ {
+ if (!isset($this->updateModel)) {
+ $this->setUpdateModel();
+ }
+
+ return $this->updateModel;
+ }
+
+ /**
+ * Sets the Update Model
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setUpdateModel(): void
+ {
+ $app = $this->getApplication();
+ $updatemodel = $app->bootComponent('com_joomlaupdate')->getMVCFactory($app)->createModel('Update', 'Administrator');
+
+ if (is_bool($updatemodel)) {
+ $this->updateModel = $updatemodel;
+
+ return;
+ }
+
+ $updatemodel->purge();
+ $updatemodel->refreshUpdates(true);
+
+ $this->updateModel = $updatemodel;
+ }
+
+ /**
+ * Downloads and extracts the update Package
+ *
+ * @param array $updateInformation Stores the update information
+ *
+ * @return array | boolean
+ *
+ * @since 4.0.0
+ */
+ public function processUpdatePackage($updateInformation)
+ {
+ if (!$updateInformation['object']) {
+ return false;
+ }
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Downloading update package ...");
+ $file = $this->downloadFile($updateInformation['object']->downloadurl->_data);
+
+ $tmpPath = $this->getApplication()->get('tmp_path');
+ $updatePackage = $tmpPath . '/' . $file;
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Extracting update package ...");
+ $package = $this->extractFile($updatePackage);
+
+ $this->progressBar->advance();
+ $this->progressBar->setMessage("Copying files ...");
+ $this->copyFileTo($package['extractdir'], JPATH_BASE);
+
+ return ['file' => $updatePackage, 'extractdir' => $package['extractdir']];
+ }
+
+ /**
+ * Downloads the Update file
+ *
+ * @param string $url URL to update file
+ *
+ * @return boolean | string
+ *
+ * @since 4.0.0
+ */
+ public function downloadFile($url)
+ {
+ $file = InstallerHelper::downloadPackage($url);
+
+ if (!$file) {
+ return false;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Extracts Update file
+ *
+ * @param string $file Full path to file location
+ *
+ * @return array | boolean
+ *
+ * @since 4.0.0
+ */
+ public function extractFile($file)
+ {
+ $package = InstallerHelper::unpack($file, true);
+
+ return $package;
+ }
+
+ /**
+ * Copy a file to a destination directory
+ *
+ * @param string $file Full path to file
+ * @param string $dir Destination directory
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function copyFileTo($file, $dir): void
+ {
+ Folder::copy($file, $dir, '', true);
+ }
}
diff --git a/libraries/src/Crypt/Cipher/CryptoCipher.php b/libraries/src/Crypt/Cipher/CryptoCipher.php
index 99bedbf39c325..5a1c3615253e0 100644
--- a/libraries/src/Crypt/Cipher/CryptoCipher.php
+++ b/libraries/src/Crypt/Cipher/CryptoCipher.php
@@ -1,4 +1,5 @@
getType() !== 'crypto')
- {
- throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected crypto.');
- }
+ /**
+ * Method to decrypt a data string.
+ *
+ * @param string $data The encrypted string to decrypt.
+ * @param Key $key The key object to use for decryption.
+ *
+ * @return string The decrypted data string.
+ *
+ * @since 3.5
+ * @throws \RuntimeException
+ */
+ public function decrypt($data, Key $key)
+ {
+ // Validate key.
+ if ($key->getType() !== 'crypto') {
+ throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected crypto.');
+ }
- // Decrypt the data.
- try
- {
- return \Crypto::Decrypt($data, $key->getPublic());
- }
- catch (\InvalidCiphertextException $ex)
- {
- throw new \RuntimeException('DANGER! DANGER! The ciphertext has been tampered with!', $ex->getCode(), $ex);
- }
- catch (\CryptoTestFailedException $ex)
- {
- throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
- }
- catch (\CannotPerformOperationException $ex)
- {
- throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
- }
- }
+ // Decrypt the data.
+ try {
+ return \Crypto::Decrypt($data, $key->getPublic());
+ } catch (\InvalidCiphertextException $ex) {
+ throw new \RuntimeException('DANGER! DANGER! The ciphertext has been tampered with!', $ex->getCode(), $ex);
+ } catch (\CryptoTestFailedException $ex) {
+ throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
+ } catch (\CannotPerformOperationException $ex) {
+ throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
+ }
+ }
- /**
- * Method to encrypt a data string.
- *
- * @param string $data The data string to encrypt.
- * @param Key $key The key object to use for encryption.
- *
- * @return string The encrypted data string.
- *
- * @since 3.5
- * @throws \RuntimeException
- */
- public function encrypt($data, Key $key)
- {
- // Validate key.
- if ($key->getType() !== 'crypto')
- {
- throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected crypto.');
- }
+ /**
+ * Method to encrypt a data string.
+ *
+ * @param string $data The data string to encrypt.
+ * @param Key $key The key object to use for encryption.
+ *
+ * @return string The encrypted data string.
+ *
+ * @since 3.5
+ * @throws \RuntimeException
+ */
+ public function encrypt($data, Key $key)
+ {
+ // Validate key.
+ if ($key->getType() !== 'crypto') {
+ throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected crypto.');
+ }
- // Encrypt the data.
- try
- {
- return \Crypto::Encrypt($data, $key->getPublic());
- }
- catch (\CryptoTestFailedException $ex)
- {
- throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
- }
- catch (\CannotPerformOperationException $ex)
- {
- throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
- }
- }
+ // Encrypt the data.
+ try {
+ return \Crypto::Encrypt($data, $key->getPublic());
+ } catch (\CryptoTestFailedException $ex) {
+ throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
+ } catch (\CannotPerformOperationException $ex) {
+ throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
+ }
+ }
- /**
- * Method to generate a new encryption key object.
- *
- * @param array $options Key generation options.
- *
- * @return Key
- *
- * @since 3.5
- * @throws \RuntimeException
- */
- public function generateKey(array $options = array())
- {
- // Generate the encryption key.
- try
- {
- $public = \Crypto::CreateNewRandomKey();
- }
- catch (\CryptoTestFailedException $ex)
- {
- throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
- }
- catch (\CannotPerformOperationException $ex)
- {
- throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
- }
+ /**
+ * Method to generate a new encryption key object.
+ *
+ * @param array $options Key generation options.
+ *
+ * @return Key
+ *
+ * @since 3.5
+ * @throws \RuntimeException
+ */
+ public function generateKey(array $options = array())
+ {
+ // Generate the encryption key.
+ try {
+ $public = \Crypto::CreateNewRandomKey();
+ } catch (\CryptoTestFailedException $ex) {
+ throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
+ } catch (\CannotPerformOperationException $ex) {
+ throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
+ }
- // Explicitly flag the private as unused in this cipher.
- $private = 'unused';
+ // Explicitly flag the private as unused in this cipher.
+ $private = 'unused';
- return new Key('crypto', $private, $public);
- }
+ return new Key('crypto', $private, $public);
+ }
- /**
- * Check if the cipher is supported in this environment.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- public static function isSupported(): bool
- {
- try
- {
- \Crypto::RuntimeTest();
+ /**
+ * Check if the cipher is supported in this environment.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public static function isSupported(): bool
+ {
+ try {
+ \Crypto::RuntimeTest();
- return true;
- }
- catch (\CryptoTestFailedException $e)
- {
- return false;
- }
- }
+ return true;
+ } catch (\CryptoTestFailedException $e) {
+ return false;
+ }
+ }
}
diff --git a/libraries/src/Crypt/Cipher/SodiumCipher.php b/libraries/src/Crypt/Cipher/SodiumCipher.php
index 88b027996246b..1d661c80c2d35 100644
--- a/libraries/src/Crypt/Cipher/SodiumCipher.php
+++ b/libraries/src/Crypt/Cipher/SodiumCipher.php
@@ -1,4 +1,5 @@
getType() !== 'sodium')
- {
- throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected sodium.');
- }
-
- if (!$this->nonce)
- {
- throw new \RuntimeException('Missing nonce to decrypt data');
- }
-
- $decrypted = Compat::crypto_box_open(
- $data,
- $this->nonce,
- Compat::crypto_box_keypair_from_secretkey_and_publickey($key->getPrivate(), $key->getPublic())
- );
-
- if ($decrypted === false)
- {
- throw new \RuntimeException('Malformed message or invalid MAC');
- }
-
- return $decrypted;
- }
-
- /**
- * Method to encrypt a data string.
- *
- * @param string $data The data string to encrypt.
- * @param Key $key The key object to use for encryption.
- *
- * @return string The encrypted data string.
- *
- * @since 3.8.0
- * @throws \RuntimeException
- */
- public function encrypt($data, Key $key)
- {
- // Validate key.
- if ($key->getType() !== 'sodium')
- {
- throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected sodium.');
- }
-
- if (!$this->nonce)
- {
- throw new \RuntimeException('Missing nonce to decrypt data');
- }
-
- return Compat::crypto_box(
- $data,
- $this->nonce,
- Compat::crypto_box_keypair_from_secretkey_and_publickey($key->getPrivate(), $key->getPublic())
- );
- }
-
- /**
- * Method to generate a new encryption key object.
- *
- * @param array $options Key generation options.
- *
- * @return Key
- *
- * @since 3.8.0
- * @throws \RuntimeException
- */
- public function generateKey(array $options = array())
- {
- // Generate the encryption key.
- $pair = Compat::crypto_box_keypair();
-
- return new Key('sodium', Compat::crypto_box_secretkey($pair), Compat::crypto_box_publickey($pair));
- }
-
- /**
- * Check if the cipher is supported in this environment.
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- public static function isSupported(): bool
- {
- return class_exists(Compat::class);
- }
-
- /**
- * Set the nonce to use for encrypting/decrypting messages
- *
- * @param string $nonce The message nonce
- *
- * @return void
- *
- * @since 3.8.0
- */
- public function setNonce($nonce)
- {
- $this->nonce = $nonce;
- }
+ /**
+ * The message nonce to be used with encryption/decryption
+ *
+ * @var string
+ * @since 3.8.0
+ */
+ private $nonce;
+
+ /**
+ * Method to decrypt a data string.
+ *
+ * @param string $data The encrypted string to decrypt.
+ * @param Key $key The key object to use for decryption.
+ *
+ * @return string The decrypted data string.
+ *
+ * @since 3.8.0
+ * @throws \RuntimeException
+ */
+ public function decrypt($data, Key $key)
+ {
+ // Validate key.
+ if ($key->getType() !== 'sodium') {
+ throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected sodium.');
+ }
+
+ if (!$this->nonce) {
+ throw new \RuntimeException('Missing nonce to decrypt data');
+ }
+
+ $decrypted = Compat::crypto_box_open(
+ $data,
+ $this->nonce,
+ Compat::crypto_box_keypair_from_secretkey_and_publickey($key->getPrivate(), $key->getPublic())
+ );
+
+ if ($decrypted === false) {
+ throw new \RuntimeException('Malformed message or invalid MAC');
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Method to encrypt a data string.
+ *
+ * @param string $data The data string to encrypt.
+ * @param Key $key The key object to use for encryption.
+ *
+ * @return string The encrypted data string.
+ *
+ * @since 3.8.0
+ * @throws \RuntimeException
+ */
+ public function encrypt($data, Key $key)
+ {
+ // Validate key.
+ if ($key->getType() !== 'sodium') {
+ throw new \InvalidArgumentException('Invalid key of type: ' . $key->getType() . '. Expected sodium.');
+ }
+
+ if (!$this->nonce) {
+ throw new \RuntimeException('Missing nonce to decrypt data');
+ }
+
+ return Compat::crypto_box(
+ $data,
+ $this->nonce,
+ Compat::crypto_box_keypair_from_secretkey_and_publickey($key->getPrivate(), $key->getPublic())
+ );
+ }
+
+ /**
+ * Method to generate a new encryption key object.
+ *
+ * @param array $options Key generation options.
+ *
+ * @return Key
+ *
+ * @since 3.8.0
+ * @throws \RuntimeException
+ */
+ public function generateKey(array $options = array())
+ {
+ // Generate the encryption key.
+ $pair = Compat::crypto_box_keypair();
+
+ return new Key('sodium', Compat::crypto_box_secretkey($pair), Compat::crypto_box_publickey($pair));
+ }
+
+ /**
+ * Check if the cipher is supported in this environment.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public static function isSupported(): bool
+ {
+ return class_exists(Compat::class);
+ }
+
+ /**
+ * Set the nonce to use for encrypting/decrypting messages
+ *
+ * @param string $nonce The message nonce
+ *
+ * @return void
+ *
+ * @since 3.8.0
+ */
+ public function setNonce($nonce)
+ {
+ $this->nonce = $nonce;
+ }
}
diff --git a/libraries/src/Crypt/Crypt.php b/libraries/src/Crypt/Crypt.php
index 778346100d481..9a898e0d4505e 100644
--- a/libraries/src/Crypt/Crypt.php
+++ b/libraries/src/Crypt/Crypt.php
@@ -1,4 +1,5 @@
= 0)
- {
- $length = static::safeStrlen($str) - $start;
- }
- else
- {
- $length = -$start;
- }
- }
-
- return mb_substr($str, $start, $length, '8bit');
- }
-
- // Unlike mb_substr(), substr() doesn't accept NULL for length
- if ($length !== null)
- {
- return substr($str, $start, $length);
- }
-
- return substr($str, $start);
- }
+ /**
+ * A timing safe comparison method.
+ *
+ * This defeats hacking attempts that use timing based attack vectors.
+ *
+ * NOTE: Length will leak.
+ *
+ * @param string $known A known string to check against.
+ * @param string $unknown An unknown string to check.
+ *
+ * @return boolean True if the two strings are exactly the same.
+ *
+ * @since 3.2
+ */
+ public static function timingSafeCompare($known, $unknown)
+ {
+ /**
+ * Explanation about the function_exists
+ *
+ * Yes, hash_equals has existed since PHP 5.6.0 and Joomla's minimum requirements are higher
+ * than that. However, this does not prevent a misguided server administrator from disabling
+ * hash_equals in php.ini. Hence the need for checking whether the function exists or not.
+ */
+ if (function_exists('hash_equals')) {
+ return hash_equals($known, $unknown);
+ }
+
+ /**
+ * If hash_equals is not available we use a pure PHP implementation by Anthony Ferrara.
+ *
+ * @see https://blog.ircmaxell.com/2014/11/its-all-about-time.html
+ */
+ $safeLen = strlen($known);
+ $userLen = strlen($unknown);
+
+ if ($userLen != $safeLen) {
+ return false;
+ }
+
+ $result = 0;
+
+ for ($i = 0; $i < $userLen; $i++) {
+ $result |= (ord($known[$i]) ^ ord($unknown[$i]));
+ }
+
+ // They are only identical strings if $result is exactly 0...
+ return $result === 0;
+ }
+
+ /**
+ * Safely detect a string's length
+ *
+ * This method is derived from \ParagonIE\Halite\Util::safeStrlen()
+ *
+ * @param string $str String to check the length of
+ *
+ * @return integer
+ *
+ * @since 3.5
+ * @ref mbstring.func_overload
+ * @throws \RuntimeException
+ */
+ public static function safeStrlen($str)
+ {
+ static $exists = null;
+
+ if ($exists === null) {
+ $exists = \function_exists('mb_strlen');
+ }
+
+ if ($exists) {
+ $length = mb_strlen($str, '8bit');
+
+ if ($length === false) {
+ throw new \RuntimeException('mb_strlen() failed unexpectedly');
+ }
+
+ return $length;
+ }
+
+ // If we reached here, we can rely on strlen to count bytes:
+ return \strlen($str);
+ }
+
+ /**
+ * Safely extract a substring
+ *
+ * This method is derived from \ParagonIE\Halite\Util::safeSubstr()
+ *
+ * @param string $str The string to extract the substring from
+ * @param integer $start The starting position to extract from
+ * @param integer $length The length of the string to return
+ *
+ * @return string
+ *
+ * @since 3.5
+ */
+ public static function safeSubstr($str, $start, $length = null)
+ {
+ static $exists = null;
+
+ if ($exists === null) {
+ $exists = \function_exists('mb_substr');
+ }
+
+ if ($exists) {
+ // In PHP 5.3 mb_substr($str, 0, NULL, '8bit') returns an empty string, so we have to find the length ourselves.
+ if ($length === null) {
+ if ($start >= 0) {
+ $length = static::safeStrlen($str) - $start;
+ } else {
+ $length = -$start;
+ }
+ }
+
+ return mb_substr($str, $start, $length, '8bit');
+ }
+
+ // Unlike mb_substr(), substr() doesn't accept NULL for length
+ if ($length !== null) {
+ return substr($str, $start, $length);
+ }
+
+ return substr($str, $start);
+ }
}
diff --git a/libraries/src/Date/Date.php b/libraries/src/Date/Date.php
index c26cc0f7a3e43..cff1148e2eb30 100644
--- a/libraries/src/Date/Date.php
+++ b/libraries/src/Date/Date.php
@@ -1,4 +1,5 @@
tz = $tz;
- }
-
- /**
- * Magic method to access properties of the date given by class to the format method.
- *
- * @param string $name The name of the property.
- *
- * @return mixed A value if the property name is valid, null otherwise.
- *
- * @since 1.7.0
- */
- public function __get($name)
- {
- $value = null;
-
- switch ($name)
- {
- case 'daysinmonth':
- $value = $this->format('t', true);
- break;
-
- case 'dayofweek':
- $value = $this->format('N', true);
- break;
-
- case 'dayofyear':
- $value = $this->format('z', true);
- break;
-
- case 'isleapyear':
- $value = (boolean) $this->format('L', true);
- break;
-
- case 'day':
- $value = $this->format('d', true);
- break;
-
- case 'hour':
- $value = $this->format('H', true);
- break;
-
- case 'minute':
- $value = $this->format('i', true);
- break;
-
- case 'second':
- $value = $this->format('s', true);
- break;
-
- case 'month':
- $value = $this->format('m', true);
- break;
-
- case 'ordinal':
- $value = $this->format('S', true);
- break;
-
- case 'week':
- $value = $this->format('W', true);
- break;
-
- case 'year':
- $value = $this->format('Y', true);
- break;
-
- default:
- $trace = debug_backtrace();
- trigger_error(
- 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
- E_USER_NOTICE
- );
- }
-
- return $value;
- }
-
- /**
- * Magic method to render the date object in the format specified in the public
- * static member Date::$format.
- *
- * @return string The date as a formatted string.
- *
- * @since 1.7.0
- */
- public function __toString()
- {
- return (string) parent::format(self::$format);
- }
-
- /**
- * Proxy for new Date().
- *
- * @param string $date String in a format accepted by strtotime(), defaults to "now".
- * @param mixed $tz Time zone to be used for the date.
- *
- * @return Date
- *
- * @since 1.7.3
- */
- public static function getInstance($date = 'now', $tz = null)
- {
- return new static($date, $tz);
- }
-
- /**
- * Translates day of week number to a string.
- *
- * @param integer $day The numeric day of the week.
- * @param boolean $abbr Return the abbreviated day string?
- *
- * @return string The day of the week.
- *
- * @since 1.7.0
- */
- public function dayToString($day, $abbr = false)
- {
- switch ($day)
- {
- case 0:
- return $abbr ? Text::_('SUN') : Text::_('SUNDAY');
- case 1:
- return $abbr ? Text::_('MON') : Text::_('MONDAY');
- case 2:
- return $abbr ? Text::_('TUE') : Text::_('TUESDAY');
- case 3:
- return $abbr ? Text::_('WED') : Text::_('WEDNESDAY');
- case 4:
- return $abbr ? Text::_('THU') : Text::_('THURSDAY');
- case 5:
- return $abbr ? Text::_('FRI') : Text::_('FRIDAY');
- case 6:
- return $abbr ? Text::_('SAT') : Text::_('SATURDAY');
- }
- }
-
- /**
- * Gets the date as a formatted string in a local calendar.
- *
- * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
- * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
- * @param boolean $translate True to translate localised strings
- *
- * @return string The date string in the specified format format.
- *
- * @since 1.7.0
- */
- public function calendar($format, $local = false, $translate = true)
- {
- return $this->format($format, $local, $translate);
- }
-
- /**
- * Gets the date as a formatted string.
- *
- * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
- * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
- * @param boolean $translate True to translate localised strings
- *
- * @return string The date string in the specified format format.
- *
- * @since 1.7.0
- */
- #[\ReturnTypeWillChange]
- public function format($format, $local = false, $translate = true)
- {
- if ($translate)
- {
- // Do string replacements for date format options that can be translated.
- $format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
- $format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
- $format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
- $format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
- }
-
- // If the returned time should not be local use UTC.
- if ($local == false)
- {
- parent::setTimezone(new \DateTimeZone('UTC'));
- }
-
- // Format the date.
- $return = parent::format($format);
-
- if ($translate)
- {
- // Manually modify the month and day strings in the formatted time.
- if (strpos($return, self::DAY_ABBR) !== false)
- {
- $return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
- }
-
- if (strpos($return, self::DAY_NAME) !== false)
- {
- $return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
- }
-
- if (strpos($return, self::MONTH_ABBR) !== false)
- {
- $return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
- }
-
- if (strpos($return, self::MONTH_NAME) !== false)
- {
- $return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
- }
- }
-
- if ($local == false && $this->tz !== null)
- {
- parent::setTimezone($this->tz);
- }
-
- return $return;
- }
-
- /**
- * Get the time offset from GMT in hours or seconds.
- *
- * @param boolean $hours True to return the value in hours.
- *
- * @return float The time offset from GMT either in hours or in seconds.
- *
- * @since 1.7.0
- */
- public function getOffsetFromGmt($hours = false)
- {
- return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
- }
-
- /**
- * Translates month number to a string.
- *
- * @param integer $month The numeric month of the year.
- * @param boolean $abbr If true, return the abbreviated month string
- *
- * @return string The month of the year.
- *
- * @since 1.7.0
- */
- public function monthToString($month, $abbr = false)
- {
- switch ($month)
- {
- case 1:
- return $abbr ? Text::_('JANUARY_SHORT') : Text::_('JANUARY');
- case 2:
- return $abbr ? Text::_('FEBRUARY_SHORT') : Text::_('FEBRUARY');
- case 3:
- return $abbr ? Text::_('MARCH_SHORT') : Text::_('MARCH');
- case 4:
- return $abbr ? Text::_('APRIL_SHORT') : Text::_('APRIL');
- case 5:
- return $abbr ? Text::_('MAY_SHORT') : Text::_('MAY');
- case 6:
- return $abbr ? Text::_('JUNE_SHORT') : Text::_('JUNE');
- case 7:
- return $abbr ? Text::_('JULY_SHORT') : Text::_('JULY');
- case 8:
- return $abbr ? Text::_('AUGUST_SHORT') : Text::_('AUGUST');
- case 9:
- return $abbr ? Text::_('SEPTEMBER_SHORT') : Text::_('SEPTEMBER');
- case 10:
- return $abbr ? Text::_('OCTOBER_SHORT') : Text::_('OCTOBER');
- case 11:
- return $abbr ? Text::_('NOVEMBER_SHORT') : Text::_('NOVEMBER');
- case 12:
- return $abbr ? Text::_('DECEMBER_SHORT') : Text::_('DECEMBER');
- }
- }
-
- /**
- * Method to wrap the setTimezone() function and set the internal time zone object.
- *
- * @param \DateTimeZone $tz The new \DateTimeZone object.
- *
- * @return Date
- *
- * @since 1.7.0
- * @note This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
- */
- #[\ReturnTypeWillChange]
- public function setTimezone($tz)
- {
- $this->tz = $tz;
-
- return parent::setTimezone($tz);
- }
-
- /**
- * Gets the date as an ISO 8601 string. IETF RFC 3339 defines the ISO 8601 format
- * and it can be found at the IETF Web site.
- *
- * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
- *
- * @return string The date string in ISO 8601 format.
- *
- * @link http://www.ietf.org/rfc/rfc3339.txt
- * @since 1.7.0
- */
- public function toISO8601($local = false)
- {
- return $this->format(\DateTimeInterface::RFC3339, $local, false);
- }
-
- /**
- * Gets the date as an SQL datetime string.
- *
- * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
- * @param DatabaseDriver $db The database driver or null to use Factory::getDbo()
- *
- * @return string The date string in SQL datetime format.
- *
- * @link http://dev.mysql.com/doc/refman/5.0/en/datetime.html
- * @since 2.5.0
- */
- public function toSql($local = false, DatabaseDriver $db = null)
- {
- if ($db === null)
- {
- $db = Factory::getDbo();
- }
-
- return $this->format($db->getDateFormat(), $local, false);
- }
-
- /**
- * Gets the date as an RFC 822 string. IETF RFC 2822 supercedes RFC 822 and its definition
- * can be found at the IETF Web site.
- *
- * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
- *
- * @return string The date string in RFC 822 format.
- *
- * @link http://www.ietf.org/rfc/rfc2822.txt
- * @since 1.7.0
- */
- public function toRFC822($local = false)
- {
- return $this->format(\DateTimeInterface::RFC2822, $local, false);
- }
-
- /**
- * Gets the date as UNIX time stamp.
- *
- * @return integer The date as a UNIX timestamp.
- *
- * @since 1.7.0
- */
- public function toUnix()
- {
- return (int) parent::format('U');
- }
+ public const DAY_ABBR = "\x021\x03";
+ public const DAY_NAME = "\x022\x03";
+ public const MONTH_ABBR = "\x023\x03";
+ public const MONTH_NAME = "\x024\x03";
+
+ /**
+ * The format string to be applied when using the __toString() magic method.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public static $format = 'Y-m-d H:i:s';
+
+ /**
+ * Placeholder for a \DateTimeZone object with GMT as the time zone.
+ *
+ * @var object
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Without replacement
+ */
+ protected static $gmt;
+
+ /**
+ * Placeholder for a \DateTimeZone object with the default server
+ * time zone as the time zone.
+ *
+ * @var object
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Without replacement
+ */
+ protected static $stz;
+
+ /**
+ * The \DateTimeZone object for usage in rending dates as strings.
+ *
+ * @var \DateTimeZone
+ * @since 3.0.0
+ */
+ protected $tz;
+
+ /**
+ * Constructor.
+ *
+ * @param string $date String in a format accepted by strtotime(), defaults to "now".
+ * @param mixed $tz Time zone to be used for the date. Might be a string or a DateTimeZone object.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($date = 'now', $tz = null)
+ {
+ // Create the base GMT and server time zone objects.
+ if (empty(self::$gmt) || empty(self::$stz)) {
+ // @TODO: This code block stays here only for B/C, can be removed in 5.0
+ self::$gmt = new \DateTimeZone('GMT');
+ self::$stz = new \DateTimeZone(@date_default_timezone_get());
+ }
+
+ // If the time zone object is not set, attempt to build it.
+ if (!($tz instanceof \DateTimeZone)) {
+ if (\is_string($tz)) {
+ $tz = new \DateTimeZone($tz);
+ } else {
+ $tz = new \DateTimeZone('UTC');
+ }
+ }
+
+ // Backup active time zone
+ $activeTZ = date_default_timezone_get();
+
+ // Force UTC timezone for correct time handling
+ date_default_timezone_set('UTC');
+
+ // If the date is numeric assume a unix timestamp and convert it.
+ $date = is_numeric($date) ? date('c', $date) : $date;
+
+ // Call the DateTime constructor.
+ parent::__construct($date, $tz);
+
+ // Restore previously active timezone
+ date_default_timezone_set($activeTZ);
+
+ // Set the timezone object for access later.
+ $this->tz = $tz;
+ }
+
+ /**
+ * Magic method to access properties of the date given by class to the format method.
+ *
+ * @param string $name The name of the property.
+ *
+ * @return mixed A value if the property name is valid, null otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function __get($name)
+ {
+ $value = null;
+
+ switch ($name) {
+ case 'daysinmonth':
+ $value = $this->format('t', true);
+ break;
+
+ case 'dayofweek':
+ $value = $this->format('N', true);
+ break;
+
+ case 'dayofyear':
+ $value = $this->format('z', true);
+ break;
+
+ case 'isleapyear':
+ $value = (bool) $this->format('L', true);
+ break;
+
+ case 'day':
+ $value = $this->format('d', true);
+ break;
+
+ case 'hour':
+ $value = $this->format('H', true);
+ break;
+
+ case 'minute':
+ $value = $this->format('i', true);
+ break;
+
+ case 'second':
+ $value = $this->format('s', true);
+ break;
+
+ case 'month':
+ $value = $this->format('m', true);
+ break;
+
+ case 'ordinal':
+ $value = $this->format('S', true);
+ break;
+
+ case 'week':
+ $value = $this->format('W', true);
+ break;
+
+ case 'year':
+ $value = $this->format('Y', true);
+ break;
+
+ default:
+ $trace = debug_backtrace();
+ trigger_error(
+ 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
+ E_USER_NOTICE
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Magic method to render the date object in the format specified in the public
+ * static member Date::$format.
+ *
+ * @return string The date as a formatted string.
+ *
+ * @since 1.7.0
+ */
+ public function __toString()
+ {
+ return (string) parent::format(self::$format);
+ }
+
+ /**
+ * Proxy for new Date().
+ *
+ * @param string $date String in a format accepted by strtotime(), defaults to "now".
+ * @param mixed $tz Time zone to be used for the date.
+ *
+ * @return Date
+ *
+ * @since 1.7.3
+ */
+ public static function getInstance($date = 'now', $tz = null)
+ {
+ return new static($date, $tz);
+ }
+
+ /**
+ * Translates day of week number to a string.
+ *
+ * @param integer $day The numeric day of the week.
+ * @param boolean $abbr Return the abbreviated day string?
+ *
+ * @return string The day of the week.
+ *
+ * @since 1.7.0
+ */
+ public function dayToString($day, $abbr = false)
+ {
+ switch ($day) {
+ case 0:
+ return $abbr ? Text::_('SUN') : Text::_('SUNDAY');
+ case 1:
+ return $abbr ? Text::_('MON') : Text::_('MONDAY');
+ case 2:
+ return $abbr ? Text::_('TUE') : Text::_('TUESDAY');
+ case 3:
+ return $abbr ? Text::_('WED') : Text::_('WEDNESDAY');
+ case 4:
+ return $abbr ? Text::_('THU') : Text::_('THURSDAY');
+ case 5:
+ return $abbr ? Text::_('FRI') : Text::_('FRIDAY');
+ case 6:
+ return $abbr ? Text::_('SAT') : Text::_('SATURDAY');
+ }
+ }
+
+ /**
+ * Gets the date as a formatted string in a local calendar.
+ *
+ * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param boolean $translate True to translate localised strings
+ *
+ * @return string The date string in the specified format format.
+ *
+ * @since 1.7.0
+ */
+ public function calendar($format, $local = false, $translate = true)
+ {
+ return $this->format($format, $local, $translate);
+ }
+
+ /**
+ * Gets the date as a formatted string.
+ *
+ * @param string $format The date format specification string (see {@link PHP_MANUAL#date})
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param boolean $translate True to translate localised strings
+ *
+ * @return string The date string in the specified format format.
+ *
+ * @since 1.7.0
+ */
+ #[\ReturnTypeWillChange]
+ public function format($format, $local = false, $translate = true)
+ {
+ if ($translate) {
+ // Do string replacements for date format options that can be translated.
+ $format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
+ $format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
+ $format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
+ $format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
+ }
+
+ // If the returned time should not be local use UTC.
+ if ($local == false) {
+ parent::setTimezone(new \DateTimeZone('UTC'));
+ }
+
+ // Format the date.
+ $return = parent::format($format);
+
+ if ($translate) {
+ // Manually modify the month and day strings in the formatted time.
+ if (strpos($return, self::DAY_ABBR) !== false) {
+ $return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
+ }
+
+ if (strpos($return, self::DAY_NAME) !== false) {
+ $return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
+ }
+
+ if (strpos($return, self::MONTH_ABBR) !== false) {
+ $return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
+ }
+
+ if (strpos($return, self::MONTH_NAME) !== false) {
+ $return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
+ }
+ }
+
+ if ($local == false && $this->tz !== null) {
+ parent::setTimezone($this->tz);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get the time offset from GMT in hours or seconds.
+ *
+ * @param boolean $hours True to return the value in hours.
+ *
+ * @return float The time offset from GMT either in hours or in seconds.
+ *
+ * @since 1.7.0
+ */
+ public function getOffsetFromGmt($hours = false)
+ {
+ return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
+ }
+
+ /**
+ * Translates month number to a string.
+ *
+ * @param integer $month The numeric month of the year.
+ * @param boolean $abbr If true, return the abbreviated month string
+ *
+ * @return string The month of the year.
+ *
+ * @since 1.7.0
+ */
+ public function monthToString($month, $abbr = false)
+ {
+ switch ($month) {
+ case 1:
+ return $abbr ? Text::_('JANUARY_SHORT') : Text::_('JANUARY');
+ case 2:
+ return $abbr ? Text::_('FEBRUARY_SHORT') : Text::_('FEBRUARY');
+ case 3:
+ return $abbr ? Text::_('MARCH_SHORT') : Text::_('MARCH');
+ case 4:
+ return $abbr ? Text::_('APRIL_SHORT') : Text::_('APRIL');
+ case 5:
+ return $abbr ? Text::_('MAY_SHORT') : Text::_('MAY');
+ case 6:
+ return $abbr ? Text::_('JUNE_SHORT') : Text::_('JUNE');
+ case 7:
+ return $abbr ? Text::_('JULY_SHORT') : Text::_('JULY');
+ case 8:
+ return $abbr ? Text::_('AUGUST_SHORT') : Text::_('AUGUST');
+ case 9:
+ return $abbr ? Text::_('SEPTEMBER_SHORT') : Text::_('SEPTEMBER');
+ case 10:
+ return $abbr ? Text::_('OCTOBER_SHORT') : Text::_('OCTOBER');
+ case 11:
+ return $abbr ? Text::_('NOVEMBER_SHORT') : Text::_('NOVEMBER');
+ case 12:
+ return $abbr ? Text::_('DECEMBER_SHORT') : Text::_('DECEMBER');
+ }
+ }
+
+ /**
+ * Method to wrap the setTimezone() function and set the internal time zone object.
+ *
+ * @param \DateTimeZone $tz The new \DateTimeZone object.
+ *
+ * @return Date
+ *
+ * @since 1.7.0
+ * @note This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
+ */
+ #[\ReturnTypeWillChange]
+ public function setTimezone($tz)
+ {
+ $this->tz = $tz;
+
+ return parent::setTimezone($tz);
+ }
+
+ /**
+ * Gets the date as an ISO 8601 string. IETF RFC 3339 defines the ISO 8601 format
+ * and it can be found at the IETF Web site.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ *
+ * @return string The date string in ISO 8601 format.
+ *
+ * @link http://www.ietf.org/rfc/rfc3339.txt
+ * @since 1.7.0
+ */
+ public function toISO8601($local = false)
+ {
+ return $this->format(\DateTimeInterface::RFC3339, $local, false);
+ }
+
+ /**
+ * Gets the date as an SQL datetime string.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ * @param DatabaseDriver $db The database driver or null to use Factory::getDbo()
+ *
+ * @return string The date string in SQL datetime format.
+ *
+ * @link http://dev.mysql.com/doc/refman/5.0/en/datetime.html
+ * @since 2.5.0
+ */
+ public function toSql($local = false, DatabaseDriver $db = null)
+ {
+ if ($db === null) {
+ $db = Factory::getDbo();
+ }
+
+ return $this->format($db->getDateFormat(), $local, false);
+ }
+
+ /**
+ * Gets the date as an RFC 822 string. IETF RFC 2822 supercedes RFC 822 and its definition
+ * can be found at the IETF Web site.
+ *
+ * @param boolean $local True to return the date string in the local time zone, false to return it in GMT.
+ *
+ * @return string The date string in RFC 822 format.
+ *
+ * @link http://www.ietf.org/rfc/rfc2822.txt
+ * @since 1.7.0
+ */
+ public function toRFC822($local = false)
+ {
+ return $this->format(\DateTimeInterface::RFC2822, $local, false);
+ }
+
+ /**
+ * Gets the date as UNIX time stamp.
+ *
+ * @return integer The date as a UNIX timestamp.
+ *
+ * @since 1.7.0
+ */
+ public function toUnix()
+ {
+ return (int) parent::format('U');
+ }
}
diff --git a/libraries/src/Dispatcher/AbstractModuleDispatcher.php b/libraries/src/Dispatcher/AbstractModuleDispatcher.php
index fa8ccec5c5ead..3d3ff621cd117 100644
--- a/libraries/src/Dispatcher/AbstractModuleDispatcher.php
+++ b/libraries/src/Dispatcher/AbstractModuleDispatcher.php
@@ -1,4 +1,5 @@
module = $module;
- }
-
- /**
- * Runs the dispatcher.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dispatch()
- {
- $this->loadLanguage();
-
- $displayData = $this->getLayoutData();
-
- // Abort when display data is false
- if ($displayData === false)
- {
- return;
- }
-
- // Execute the layout without the module context
- $loader = static function (array $displayData)
- {
- // If $displayData doesn't exist in extracted data, unset the variable.
- if (!\array_key_exists('displayData', $displayData))
- {
- extract($displayData);
- unset($displayData);
- }
- else
- {
- extract($displayData);
- }
-
- /**
- * Extracted variables
- * -----------------
- * @var \stdClass $module
- * @var Registry $params
- */
-
- require ModuleHelper::getLayoutPath($module->module, $params->get('layout', 'default'));
- };
-
- $loader($displayData);
- }
-
- /**
- * Returns the layout data. This function can be overridden by subclasses to add more
- * attributes for the layout.
- *
- * If false is returned, then it means that the dispatch process should be aborted.
- *
- * @return array|false
- *
- * @since 4.0.0
- */
- protected function getLayoutData()
- {
- return [
- 'module' => $this->module,
- 'app' => $this->app,
- 'input' => $this->input,
- 'params' => new Registry($this->module->params),
- 'template' => $this->app->getTemplate()
- ];
- }
-
- /**
- * Load the language.
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function loadLanguage()
- {
- $language = $this->app->getLanguage();
-
- $coreLanguageDirectory = JPATH_BASE;
- $extensionLanguageDirectory = JPATH_BASE . '/modules/' . $this->module->module;
-
- $langPaths = $language->getPaths();
-
- // Only load the module's language file if it hasn't been already
- if (!$langPaths || (!isset($langPaths[$coreLanguageDirectory]) && !isset($langPaths[$extensionLanguageDirectory])))
- {
- // 1.5 or Core then 1.6 3PD
- $language->load($this->module->module, $coreLanguageDirectory) ||
- $language->load($this->module->module, $extensionLanguageDirectory);
- }
- }
+ /**
+ * The module instance
+ *
+ * @var \stdClass
+ * @since 4.0.0
+ */
+ protected $module;
+
+ /**
+ * Constructor for Dispatcher
+ *
+ * @param \stdClass $module The module
+ * @param CMSApplicationInterface $app The application instance
+ * @param Input $input The input instance
+ *
+ * @since 4.0.0
+ */
+ public function __construct(\stdClass $module, CMSApplicationInterface $app, Input $input)
+ {
+ parent::__construct($app, $input);
+
+ $this->module = $module;
+ }
+
+ /**
+ * Runs the dispatcher.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dispatch()
+ {
+ $this->loadLanguage();
+
+ $displayData = $this->getLayoutData();
+
+ // Abort when display data is false
+ if ($displayData === false) {
+ return;
+ }
+
+ // Execute the layout without the module context
+ $loader = static function (array $displayData) {
+ // If $displayData doesn't exist in extracted data, unset the variable.
+ if (!\array_key_exists('displayData', $displayData)) {
+ extract($displayData);
+ unset($displayData);
+ } else {
+ extract($displayData);
+ }
+
+ /**
+ * Extracted variables
+ * -----------------
+ * @var \stdClass $module
+ * @var Registry $params
+ */
+
+ require ModuleHelper::getLayoutPath($module->module, $params->get('layout', 'default'));
+ };
+
+ $loader($displayData);
+ }
+
+ /**
+ * Returns the layout data. This function can be overridden by subclasses to add more
+ * attributes for the layout.
+ *
+ * If false is returned, then it means that the dispatch process should be aborted.
+ *
+ * @return array|false
+ *
+ * @since 4.0.0
+ */
+ protected function getLayoutData()
+ {
+ return [
+ 'module' => $this->module,
+ 'app' => $this->app,
+ 'input' => $this->input,
+ 'params' => new Registry($this->module->params),
+ 'template' => $this->app->getTemplate()
+ ];
+ }
+
+ /**
+ * Load the language.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function loadLanguage()
+ {
+ $language = $this->app->getLanguage();
+
+ $coreLanguageDirectory = JPATH_BASE;
+ $extensionLanguageDirectory = JPATH_BASE . '/modules/' . $this->module->module;
+
+ $langPaths = $language->getPaths();
+
+ // Only load the module's language file if it hasn't been already
+ if (!$langPaths || (!isset($langPaths[$coreLanguageDirectory]) && !isset($langPaths[$extensionLanguageDirectory]))) {
+ // 1.5 or Core then 1.6 3PD
+ $language->load($this->module->module, $coreLanguageDirectory) ||
+ $language->load($this->module->module, $extensionLanguageDirectory);
+ }
+ }
}
diff --git a/libraries/src/Dispatcher/ApiDispatcher.php b/libraries/src/Dispatcher/ApiDispatcher.php
index 5c82ede304a4b..d0296848ff333 100644
--- a/libraries/src/Dispatcher/ApiDispatcher.php
+++ b/libraries/src/Dispatcher/ApiDispatcher.php
@@ -1,4 +1,5 @@
app->getLanguage()->load($this->option, JPATH_BASE) ||
- $this->app->getLanguage()->load($this->option, JPATH_ADMINISTRATOR) ||
- $this->app->getLanguage()->load($this->option, JPATH_COMPONENT_ADMINISTRATOR);
- }
-
- /**
- * Dispatch a controller task. API Overrides to ensure there is no redirects.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dispatch()
- {
- $task = $this->input->getCmd('task', 'display');
-
- // Build controller config data
- $config['option'] = $this->option;
-
- // Set name of controller if it is passed in the request
- if ($this->input->exists('controller'))
- {
- $config['name'] = strtolower($this->input->get('controller'));
- }
-
- $controller = $this->input->get('controller');
- $controller = $this->getController($controller, ucfirst($this->app->getName()), $config);
-
- $controller->execute($task);
- }
+ /**
+ * Load the component's administrator language
+ *
+ * @since 4.0.0
+ *
+ * @return void
+ */
+ protected function loadLanguage()
+ {
+ // Load common and local language files.
+ $this->app->getLanguage()->load($this->option, JPATH_BASE) ||
+ $this->app->getLanguage()->load($this->option, JPATH_ADMINISTRATOR) ||
+ $this->app->getLanguage()->load($this->option, JPATH_COMPONENT_ADMINISTRATOR);
+ }
+
+ /**
+ * Dispatch a controller task. API Overrides to ensure there is no redirects.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dispatch()
+ {
+ $task = $this->input->getCmd('task', 'display');
+
+ // Build controller config data
+ $config['option'] = $this->option;
+
+ // Set name of controller if it is passed in the request
+ if ($this->input->exists('controller')) {
+ $config['name'] = strtolower($this->input->get('controller'));
+ }
+
+ $controller = $this->input->get('controller');
+ $controller = $this->getController($controller, ucfirst($this->app->getName()), $config);
+
+ $controller->execute($task);
+ }
}
diff --git a/libraries/src/Dispatcher/ComponentDispatcher.php b/libraries/src/Dispatcher/ComponentDispatcher.php
index 70d61ac174320..f0b819f04802d 100644
--- a/libraries/src/Dispatcher/ComponentDispatcher.php
+++ b/libraries/src/Dispatcher/ComponentDispatcher.php
@@ -1,4 +1,5 @@
mvcFactory = $mvcFactory;
-
- // If option is not provided, detect it from dispatcher class name, ie ContentDispatcher
- if (empty($this->option))
- {
- $this->option = ComponentHelper::getComponentName(
- $this,
- str_replace('com_', '', $input->get('option'))
- );
- }
-
- $this->loadLanguage();
- }
-
- /**
- * Load the language
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function loadLanguage()
- {
- // Load common and local language files.
- $this->app->getLanguage()->load($this->option, JPATH_BASE) ||
- $this->app->getLanguage()->load($this->option, JPATH_COMPONENT);
- }
-
- /**
- * Method to check component access permission
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function checkAccess()
- {
- // Check the user has permission to access this component if in the backend
- if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage', $this->option))
- {
- throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
- }
- }
-
- /**
- * Dispatch a controller task. Redirecting the user if appropriate.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dispatch()
- {
- // Check component access permission
- $this->checkAccess();
-
- $command = $this->input->getCmd('task', 'display');
-
- // Check for a controller.task command.
- if (strpos($command, '.') !== false)
- {
- // Explode the controller.task command.
- list ($controller, $task) = explode('.', $command);
-
- $this->input->set('controller', $controller);
- $this->input->set('task', $task);
- }
- else
- {
- // Do we have a controller?
- $controller = $this->input->get('controller', 'display');
- $task = $command;
- }
-
- // Build controller config data
- $config['option'] = $this->option;
-
- // Set name of controller if it is passed in the request
- if ($this->input->exists('controller'))
- {
- $config['name'] = strtolower($this->input->get('controller'));
- }
-
- // Execute the task for this component
- $controller = $this->getController($controller, ucfirst($this->app->getName()), $config);
- $controller->execute($task);
- $controller->redirect();
- }
-
- /**
- * Get a controller from the component
- *
- * @param string $name Controller name
- * @param string $client Optional client (like Administrator, Site etc.)
- * @param array $config Optional controller config
- *
- * @return BaseController
- *
- * @since 4.0.0
- */
- public function getController(string $name, string $client = '', array $config = array()): BaseController
- {
- // Set up the client
- $client = $client ?: ucfirst($this->app->getName());
-
- // Get the controller instance
- $controller = $this->mvcFactory->createController(
- $name,
- $client,
- $config,
- $this->app,
- $this->input
- );
-
- // Check if the controller could be created
- if (!$controller)
- {
- throw new \InvalidArgumentException(Text::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER_CLASS', $name));
- }
-
- return $controller;
- }
+ /**
+ * The URL option for the component.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $option;
+
+ /**
+ * The MVC factory
+ *
+ * @var MVCFactoryInterface
+ *
+ * @since 4.0.0
+ */
+ protected $mvcFactory;
+
+ /**
+ * Constructor for ComponentDispatcher
+ *
+ * @param CMSApplicationInterface $app The application instance
+ * @param Input $input The input instance
+ * @param MVCFactoryInterface $mvcFactory The MVC factory instance
+ *
+ * @since 4.0.0
+ */
+ public function __construct(CMSApplicationInterface $app, Input $input, MVCFactoryInterface $mvcFactory)
+ {
+ parent::__construct($app, $input);
+
+ $this->mvcFactory = $mvcFactory;
+
+ // If option is not provided, detect it from dispatcher class name, ie ContentDispatcher
+ if (empty($this->option)) {
+ $this->option = ComponentHelper::getComponentName(
+ $this,
+ str_replace('com_', '', $input->get('option'))
+ );
+ }
+
+ $this->loadLanguage();
+ }
+
+ /**
+ * Load the language
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function loadLanguage()
+ {
+ // Load common and local language files.
+ $this->app->getLanguage()->load($this->option, JPATH_BASE) ||
+ $this->app->getLanguage()->load($this->option, JPATH_COMPONENT);
+ }
+
+ /**
+ * Method to check component access permission
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function checkAccess()
+ {
+ // Check the user has permission to access this component if in the backend
+ if ($this->app->isClient('administrator') && !$this->app->getIdentity()->authorise('core.manage', $this->option)) {
+ throw new NotAllowed($this->app->getLanguage()->_('JERROR_ALERTNOAUTHOR'), 403);
+ }
+ }
+
+ /**
+ * Dispatch a controller task. Redirecting the user if appropriate.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dispatch()
+ {
+ // Check component access permission
+ $this->checkAccess();
+
+ $command = $this->input->getCmd('task', 'display');
+
+ // Check for a controller.task command.
+ if (strpos($command, '.') !== false) {
+ // Explode the controller.task command.
+ list ($controller, $task) = explode('.', $command);
+
+ $this->input->set('controller', $controller);
+ $this->input->set('task', $task);
+ } else {
+ // Do we have a controller?
+ $controller = $this->input->get('controller', 'display');
+ $task = $command;
+ }
+
+ // Build controller config data
+ $config['option'] = $this->option;
+
+ // Set name of controller if it is passed in the request
+ if ($this->input->exists('controller')) {
+ $config['name'] = strtolower($this->input->get('controller'));
+ }
+
+ // Execute the task for this component
+ $controller = $this->getController($controller, ucfirst($this->app->getName()), $config);
+ $controller->execute($task);
+ $controller->redirect();
+ }
+
+ /**
+ * Get a controller from the component
+ *
+ * @param string $name Controller name
+ * @param string $client Optional client (like Administrator, Site etc.)
+ * @param array $config Optional controller config
+ *
+ * @return BaseController
+ *
+ * @since 4.0.0
+ */
+ public function getController(string $name, string $client = '', array $config = array()): BaseController
+ {
+ // Set up the client
+ $client = $client ?: ucfirst($this->app->getName());
+
+ // Get the controller instance
+ $controller = $this->mvcFactory->createController(
+ $name,
+ $client,
+ $config,
+ $this->app,
+ $this->input
+ );
+
+ // Check if the controller could be created
+ if (!$controller) {
+ throw new \InvalidArgumentException(Text::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER_CLASS', $name));
+ }
+
+ return $controller;
+ }
}
diff --git a/libraries/src/Dispatcher/ComponentDispatcherFactory.php b/libraries/src/Dispatcher/ComponentDispatcherFactory.php
index 2a07932271137..18ffda14aba08 100644
--- a/libraries/src/Dispatcher/ComponentDispatcherFactory.php
+++ b/libraries/src/Dispatcher/ComponentDispatcherFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- $this->mvcFactory = $mvcFactory;
- }
+ /**
+ * ComponentDispatcherFactory constructor.
+ *
+ * @param string $namespace The namespace
+ * @param MVCFactoryInterface $mvcFactory The MVC factory
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace, MVCFactoryInterface $mvcFactory)
+ {
+ $this->namespace = $namespace;
+ $this->mvcFactory = $mvcFactory;
+ }
- /**
- * Creates a dispatcher.
- *
- * @param CMSApplicationInterface $application The application
- * @param Input $input The input object, defaults to the one in the application
- *
- * @return DispatcherInterface
- *
- * @since 4.0.0
- */
- public function createDispatcher(CMSApplicationInterface $application, Input $input = null): DispatcherInterface
- {
- $name = ucfirst($application->getName());
+ /**
+ * Creates a dispatcher.
+ *
+ * @param CMSApplicationInterface $application The application
+ * @param Input $input The input object, defaults to the one in the application
+ *
+ * @return DispatcherInterface
+ *
+ * @since 4.0.0
+ */
+ public function createDispatcher(CMSApplicationInterface $application, Input $input = null): DispatcherInterface
+ {
+ $name = ucfirst($application->getName());
- $className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
+ $className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
- if (!class_exists($className))
- {
- if ($application->isClient('api'))
- {
- $className = ApiDispatcher::class;
- }
- else
- {
- $className = ComponentDispatcher::class;
- }
- }
+ if (!class_exists($className)) {
+ if ($application->isClient('api')) {
+ $className = ApiDispatcher::class;
+ } else {
+ $className = ComponentDispatcher::class;
+ }
+ }
- return new $className($application, $input ?: $application->input, $this->mvcFactory);
- }
+ return new $className($application, $input ?: $application->input, $this->mvcFactory);
+ }
}
diff --git a/libraries/src/Dispatcher/ComponentDispatcherFactoryInterface.php b/libraries/src/Dispatcher/ComponentDispatcherFactoryInterface.php
index 3976cc12880f4..4f297e7f18774 100644
--- a/libraries/src/Dispatcher/ComponentDispatcherFactoryInterface.php
+++ b/libraries/src/Dispatcher/ComponentDispatcherFactoryInterface.php
@@ -1,4 +1,5 @@
app = $app;
- $this->input = $input;
- }
+ /**
+ * Constructor for Dispatcher
+ *
+ * @param CMSApplicationInterface $app The application instance
+ * @param Input $input The input instance
+ *
+ * @since 4.0.0
+ */
+ public function __construct(CMSApplicationInterface $app, Input $input)
+ {
+ $this->app = $app;
+ $this->input = $input;
+ }
- /**
- * The application the dispatcher is working with.
- *
- * @return CMSApplicationInterface
- *
- * @since 4.0.0
- */
- protected function getApplication(): CMSApplicationInterface
- {
- return $this->app;
- }
+ /**
+ * The application the dispatcher is working with.
+ *
+ * @return CMSApplicationInterface
+ *
+ * @since 4.0.0
+ */
+ protected function getApplication(): CMSApplicationInterface
+ {
+ return $this->app;
+ }
}
diff --git a/libraries/src/Dispatcher/DispatcherInterface.php b/libraries/src/Dispatcher/DispatcherInterface.php
index ef1985a71e73b..6e28990856410 100644
--- a/libraries/src/Dispatcher/DispatcherInterface.php
+++ b/libraries/src/Dispatcher/DispatcherInterface.php
@@ -1,4 +1,5 @@
app = $app;
- }
+ /**
+ * Constructor for Dispatcher
+ *
+ * @param CMSApplication $app The application instance
+ *
+ * @since 4.0.0
+ */
+ public function __construct(CMSApplication $app)
+ {
+ $this->app = $app;
+ }
- /**
- * Dispatch a controller task. Redirecting the user if appropriate.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dispatch()
- {
- $path = JPATH_COMPONENT . '/' . substr($this->app->scope, 4) . '.php';
+ /**
+ * Dispatch a controller task. Redirecting the user if appropriate.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dispatch()
+ {
+ $path = JPATH_COMPONENT . '/' . substr($this->app->scope, 4) . '.php';
- // If component file doesn't exist throw error
- if (!is_file($path))
- {
- throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
- }
+ // If component file doesn't exist throw error
+ if (!is_file($path)) {
+ throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
+ }
- $lang = $this->app->getLanguage();
+ $lang = $this->app->getLanguage();
- // Load common and local language files.
- $lang->load($this->app->scope, JPATH_BASE) || $lang->load($this->app->scope, JPATH_COMPONENT);
+ // Load common and local language files.
+ $lang->load($this->app->scope, JPATH_BASE) || $lang->load($this->app->scope, JPATH_COMPONENT);
- // Execute the component
- $loader = static function ($path) {
- require_once $path;
- };
- $loader($path);
- }
+ // Execute the component
+ $loader = static function ($path) {
+ require_once $path;
+ };
+ $loader($path);
+ }
}
diff --git a/libraries/src/Dispatcher/ModuleDispatcher.php b/libraries/src/Dispatcher/ModuleDispatcher.php
index 7c8a6679ffa82..84a5f53bb8479 100644
--- a/libraries/src/Dispatcher/ModuleDispatcher.php
+++ b/libraries/src/Dispatcher/ModuleDispatcher.php
@@ -1,4 +1,5 @@
module->module . '/' . $this->module->module . '.php';
-
- if (!is_file($path))
- {
- return;
- }
-
- $this->loadLanguage();
-
- // Execute the layout without the module context
- $loader = static function ($path, array $displayData)
- {
- // If $displayData doesn't exist in extracted data, unset the variable.
- if (!\array_key_exists('displayData', $displayData))
- {
- extract($displayData);
- unset($displayData);
- }
- else
- {
- extract($displayData);
- }
-
- include $path;
- };
-
- $loader($path, $this->getLayoutData());
- }
+ /**
+ * Dispatches the dispatcher.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dispatch()
+ {
+ $path = JPATH_BASE . '/modules/' . $this->module->module . '/' . $this->module->module . '.php';
+
+ if (!is_file($path)) {
+ return;
+ }
+
+ $this->loadLanguage();
+
+ // Execute the layout without the module context
+ $loader = static function ($path, array $displayData) {
+ // If $displayData doesn't exist in extracted data, unset the variable.
+ if (!\array_key_exists('displayData', $displayData)) {
+ extract($displayData);
+ unset($displayData);
+ } else {
+ extract($displayData);
+ }
+
+ include $path;
+ };
+
+ $loader($path, $this->getLayoutData());
+ }
}
diff --git a/libraries/src/Dispatcher/ModuleDispatcherFactory.php b/libraries/src/Dispatcher/ModuleDispatcherFactory.php
index aca5bec3d3f31..dd788ec4a3aac 100644
--- a/libraries/src/Dispatcher/ModuleDispatcherFactory.php
+++ b/libraries/src/Dispatcher/ModuleDispatcherFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * ModuleDispatcherFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Creates a dispatcher.
- *
- * @param \stdClass $module The module
- * @param CMSApplicationInterface $application The application
- * @param Input $input The input object, defaults to the one in the application
- *
- * @return DispatcherInterface
- *
- * @since 4.0.0
- */
- public function createDispatcher(\stdClass $module, CMSApplicationInterface $application, Input $input = null): DispatcherInterface
- {
- $name = 'Site';
+ /**
+ * Creates a dispatcher.
+ *
+ * @param \stdClass $module The module
+ * @param CMSApplicationInterface $application The application
+ * @param Input $input The input object, defaults to the one in the application
+ *
+ * @return DispatcherInterface
+ *
+ * @since 4.0.0
+ */
+ public function createDispatcher(\stdClass $module, CMSApplicationInterface $application, Input $input = null): DispatcherInterface
+ {
+ $name = 'Site';
- if ($application->isClient('administrator'))
- {
- $name = 'Administrator';
- }
+ if ($application->isClient('administrator')) {
+ $name = 'Administrator';
+ }
- $className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
+ $className = '\\' . trim($this->namespace, '\\') . '\\' . $name . '\\Dispatcher\\Dispatcher';
- if (!class_exists($className))
- {
- $className = ModuleDispatcher::class;
- }
+ if (!class_exists($className)) {
+ $className = ModuleDispatcher::class;
+ }
- return new $className($module, $application, $input ?: $application->input);
- }
+ return new $className($module, $application, $input ?: $application->input);
+ }
}
diff --git a/libraries/src/Dispatcher/ModuleDispatcherFactoryInterface.php b/libraries/src/Dispatcher/ModuleDispatcherFactoryInterface.php
index 2ea02857b3e97..74896770a7b75 100644
--- a/libraries/src/Dispatcher/ModuleDispatcherFactoryInterface.php
+++ b/libraries/src/Dispatcher/ModuleDispatcherFactoryInterface.php
@@ -1,4 +1,5 @@
setLineEnd($options['lineend']);
- }
-
- if (\array_key_exists('charset', $options))
- {
- $this->setCharset($options['charset']);
- }
-
- if (\array_key_exists('language', $options))
- {
- $this->setLanguage($options['language']);
- }
-
- if (\array_key_exists('direction', $options))
- {
- $this->setDirection($options['direction']);
- }
-
- if (\array_key_exists('tab', $options))
- {
- $this->setTab($options['tab']);
- }
-
- if (\array_key_exists('link', $options))
- {
- $this->setLink($options['link']);
- }
-
- if (\array_key_exists('base', $options))
- {
- $this->setBase($options['base']);
- }
-
- if (\array_key_exists('mediaversion', $options))
- {
- $this->setMediaVersion($options['mediaversion']);
- }
-
- if (\array_key_exists('factory', $options))
- {
- $this->setFactory($options['factory']);
- }
- else
- {
- $this->setFactory(new Factory);
- }
-
- if (\array_key_exists('preloadManager', $options))
- {
- $this->setPreloadManager($options['preloadManager']);
- }
- else
- {
- $this->setPreloadManager(new PreloadManager);
- }
-
- if (\array_key_exists('webAssetManager', $options))
- {
- $this->setWebAssetManager($options['webAssetManager']);
- }
- else
- {
- $webAssetManager = new WebAssetManager(\Joomla\CMS\Factory::getContainer()->get('webassetregistry'));
-
- $this->setWebAssetManager($webAssetManager);
- }
- }
-
- /**
- * Returns the global Document object, only creating it
- * if it doesn't already exist.
- *
- * @param string $type The document type to instantiate
- * @param array $attributes Array of attributes
- *
- * @return static The document object.
- *
- * @since 1.7.0
- * @deprecated 5.0 Use the \Joomla\CMS\Document\FactoryInterface instead
- */
- public static function getInstance($type = 'html', $attributes = array())
- {
- $signature = serialize(array($type, $attributes));
-
- if (empty(self::$instances[$signature]))
- {
- self::$instances[$signature] = CmsFactory::getContainer()->get(FactoryInterface::class)->createDocument($type, $attributes);
- }
-
- return self::$instances[$signature];
- }
-
- /**
- * Set the factory instance
- *
- * @param FactoryInterface $factory The factory instance
- *
- * @return Document
- *
- * @since 4.0.0
- */
- public function setFactory(FactoryInterface $factory): self
- {
- $this->factory = $factory;
-
- return $this;
- }
-
- /**
- * Set the document type
- *
- * @param string $type Type document is to set to
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setType($type)
- {
- $this->_type = $type;
-
- return $this;
- }
-
- /**
- * Returns the document type
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getType()
- {
- return $this->_type;
- }
-
- /**
- * Get the contents of the document buffer
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function getBuffer()
- {
- return self::$_buffer;
- }
-
- /**
- * Set the contents of the document buffer
- *
- * @param string $content The content to be set in the buffer.
- * @param array $options Array of optional elements.
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setBuffer($content, $options = array())
- {
- self::$_buffer = $content;
-
- return $this;
- }
-
- /**
- * Gets a meta tag.
- *
- * @param string $name Name of the meta HTML tag
- * @param string $attribute Attribute to use in the meta HTML tag
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getMetaData($name, $attribute = 'name')
- {
- // B/C old http_equiv parameter.
- if (!\is_string($attribute))
- {
- $attribute = $attribute == true ? 'http-equiv' : 'name';
- }
-
- if ($name === 'generator')
- {
- $result = $this->getGenerator();
- }
- elseif ($name === 'description')
- {
- $result = $this->getDescription();
- }
- else
- {
- $result = isset($this->_metaTags[$attribute]) && isset($this->_metaTags[$attribute][$name]) ? $this->_metaTags[$attribute][$name] : '';
- }
-
- return $result;
- }
-
- /**
- * Sets or alters a meta tag.
- *
- * @param string $name Name of the meta HTML tag
- * @param mixed $content Value of the meta HTML tag as array or string
- * @param string $attribute Attribute to use in the meta HTML tag
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setMetaData($name, $content, $attribute = 'name')
- {
- // Pop the element off the end of array if target function expects a string or this http_equiv parameter.
- if (\is_array($content) && (\in_array($name, array('generator', 'description')) || !\is_string($attribute)))
- {
- $content = array_pop($content);
- }
-
- // B/C old http_equiv parameter.
- if (!\is_string($attribute))
- {
- $attribute = $attribute == true ? 'http-equiv' : 'name';
- }
-
- if ($name === 'generator')
- {
- $this->setGenerator($content);
- }
- elseif ($name === 'description')
- {
- $this->setDescription($content);
- }
- else
- {
- $this->_metaTags[$attribute][$name] = $content;
- }
-
- return $this;
- }
-
- /**
- * Adds a linked script to the page
- *
- * @param string $url URL to the linked script.
- * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9', 'preload' => array('preload'))
- * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- *
- * @deprecated 5.0 Use WebAssetManager
- */
- public function addScript($url, $options = array(), $attribs = array())
- {
- // Default value for type.
- if (!isset($attribs['type']) && !isset($attribs['mime']))
- {
- $attribs['type'] = 'text/javascript';
- }
-
- $this->_scripts[$url] = isset($this->_scripts[$url]) ? array_replace($this->_scripts[$url], $attribs) : $attribs;
- $this->_scripts[$url]['options'] = isset($this->_scripts[$url]['options']) ? array_replace($this->_scripts[$url]['options'], $options) : $options;
-
- return $this;
- }
-
- /**
- * Adds a script to the page
- *
- * @param string $content Script
- * @param string $type Scripting mime (defaults to 'text/javascript')
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- *
- * @deprecated 5.0 Use WebAssetManager
- */
- public function addScriptDeclaration($content, $type = 'text/javascript')
- {
- $type = strtolower($type);
-
- if (empty($this->_script[$type]))
- {
- $this->_script[$type] = array();
- }
-
- $this->_script[$type][md5($content)] = $content;
-
- return $this;
- }
-
- /**
- * Add option for script
- *
- * @param string $key Name in Storage
- * @param mixed $options Scrip options as array or string
- * @param bool $merge Whether merge with existing (true) or replace (false)
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 3.5
- */
- public function addScriptOptions($key, $options, $merge = true)
- {
- if (empty($this->scriptOptions[$key]))
- {
- $this->scriptOptions[$key] = array();
- }
-
- if ($merge && \is_array($options))
- {
- $this->scriptOptions[$key] = array_replace_recursive($this->scriptOptions[$key], $options);
- }
- else
- {
- $this->scriptOptions[$key] = $options;
- }
-
- return $this;
- }
-
- /**
- * Get script(s) options
- *
- * @param string $key Name in Storage
- *
- * @return array Options for given $key, or all script options
- *
- * @since 3.5
- */
- public function getScriptOptions($key = null)
- {
- if ($key)
- {
- return (empty($this->scriptOptions[$key])) ? array() : $this->scriptOptions[$key];
- }
- else
- {
- return $this->scriptOptions;
- }
- }
-
- /**
- * Adds a linked stylesheet to the page
- *
- * @param string $url URL to the linked style sheet
- * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9', 'preload' => array('preload'))
- * @param array $attribs Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- *
- * @deprecated 5.0 Use WebAssetManager
- */
- public function addStyleSheet($url, $options = array(), $attribs = array())
- {
- // Default value for type.
- if (!isset($attribs['type']) && !isset($attribs['mime']))
- {
- $attribs['type'] = 'text/css';
- }
-
- $this->_styleSheets[$url] = isset($this->_styleSheets[$url]) ? array_replace($this->_styleSheets[$url], $attribs) : $attribs;
-
- if (isset($this->_styleSheets[$url]['options']))
- {
- $this->_styleSheets[$url]['options'] = array_replace($this->_styleSheets[$url]['options'], $options);
- }
- else
- {
- $this->_styleSheets[$url]['options'] = $options;
- }
-
- return $this;
- }
-
- /**
- * Adds a stylesheet declaration to the page
- *
- * @param string $content Style declarations
- * @param string $type Type of stylesheet (defaults to 'text/css')
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- *
- * @deprecated 5.0 Use WebAssetManager
- */
- public function addStyleDeclaration($content, $type = 'text/css')
- {
- $type = strtolower($type);
-
- if (empty($this->_style[$type]))
- {
- $this->_style[$type] = array();
- }
-
- $this->_style[$type][md5($content)] = $content;
-
- return $this;
- }
-
- /**
- * Sets the document charset
- *
- * @param string $type Charset encoding string
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setCharset($type = 'utf-8')
- {
- $this->_charset = $type;
-
- return $this;
- }
-
- /**
- * Returns the document charset encoding.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getCharset()
- {
- return $this->_charset;
- }
-
- /**
- * Sets the global document language declaration. Default is English (en-gb).
- *
- * @param string $lang The language to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setLanguage($lang = 'en-gb')
- {
- $this->language = strtolower($lang);
-
- return $this;
- }
-
- /**
- * Returns the document language.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getLanguage()
- {
- return $this->language;
- }
-
- /**
- * Sets the global document direction declaration. Default is left-to-right (ltr).
- *
- * @param string $dir The language direction to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setDirection($dir = 'ltr')
- {
- $this->direction = strtolower($dir);
-
- return $this;
- }
-
- /**
- * Returns the document direction declaration.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getDirection()
- {
- return $this->direction;
- }
-
- /**
- * Sets the title of the document
- *
- * @param string $title The title to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setTitle($title)
- {
- $this->title = $title;
-
- return $this;
- }
-
- /**
- * Return the title of the document.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getTitle()
- {
- return $this->title;
- }
-
- /**
- * Set the assets version
- *
- * @param string $mediaVersion Media version to use
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 3.2
- */
- public function setMediaVersion($mediaVersion)
- {
- $this->mediaVersion = strtolower($mediaVersion);
-
- return $this;
- }
-
- /**
- * Return the media version
- *
- * @return string
- *
- * @since 3.2
- */
- public function getMediaVersion()
- {
- return $this->mediaVersion;
- }
-
- /**
- * Set the preload manager
- *
- * @param PreloadManagerInterface $preloadManager The preload manager service
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 4.0.0
- */
- public function setPreloadManager(PreloadManagerInterface $preloadManager): self
- {
- $this->preloadManager = $preloadManager;
-
- return $this;
- }
-
- /**
- * Return the preload manager
- *
- * @return PreloadManagerInterface
- *
- * @since 4.0.0
- */
- public function getPreloadManager(): PreloadManagerInterface
- {
- return $this->preloadManager;
- }
-
- /**
- * Set WebAsset manager
- *
- * @param WebAssetManager $webAsset The WebAsset instance
- *
- * @return Document
- *
- * @since 4.0.0
- */
- public function setWebAssetManager(WebAssetManager $webAsset): self
- {
- $this->webAssetManager = $webAsset;
-
- return $this;
- }
-
- /**
- * Return WebAsset manager
- *
- * @return WebAssetManager
- *
- * @since 4.0.0
- */
- public function getWebAssetManager(): WebAssetManager
- {
- return $this->webAssetManager;
- }
-
- /**
- * Sets the base URI of the document
- *
- * @param string $base The base URI to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setBase($base)
- {
- $this->base = $base;
-
- return $this;
- }
-
- /**
- * Return the base URI of the document.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getBase()
- {
- return $this->base;
- }
-
- /**
- * Sets the description of the document
- *
- * @param string $description The description to set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setDescription($description)
- {
- $this->description = $description;
-
- return $this;
- }
-
- /**
- * Return the description of the document.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getDescription()
- {
- return $this->description;
- }
-
- /**
- * Sets the document link
- *
- * @param string $url A url
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setLink($url)
- {
- $this->link = $url;
-
- return $this;
- }
-
- /**
- * Returns the document base url
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getLink()
- {
- return $this->link;
- }
-
- /**
- * Sets the document generator
- *
- * @param string $generator The generator to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setGenerator($generator)
- {
- $this->_generator = $generator;
-
- return $this;
- }
-
- /**
- * Returns the document generator
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getGenerator()
- {
- return $this->_generator;
- }
-
- /**
- * Sets the document modified date
- *
- * @param string|Date $date The date to be set
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- * @throws \InvalidArgumentException
- */
- public function setModifiedDate($date)
- {
- if (!\is_string($date) && !($date instanceof Date))
- {
- throw new \InvalidArgumentException(
- sprintf(
- 'The $date parameter of %1$s must be a string or a %2$s instance, a %3$s was given.',
- __METHOD__ . '()',
- 'Joomla\\CMS\\Date\\Date',
- \gettype($date) === 'object' ? (\get_class($date) . ' instance') : \gettype($date)
- )
- );
- }
-
- $this->_mdate = $date;
-
- return $this;
- }
-
- /**
- * Returns the document modified date
- *
- * @return string|Date
- *
- * @since 1.7.0
- */
- public function getModifiedDate()
- {
- return $this->_mdate;
- }
-
- /**
- * Sets the document MIME encoding that is sent to the browser.
- *
- * This usually will be text/html because most browsers cannot yet
- * accept the proper mime settings for XHTML: application/xhtml+xml
- * and to a lesser extent application/xml and text/xml. See the W3C note
- * ({@link https://www.w3.org/TR/xhtml-media-types/
- * https://www.w3.org/TR/xhtml-media-types/}) for more details.
- *
- * @param string $type The document type to be sent
- * @param boolean $sync Should the type be synced with HTML?
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- *
- * @link https://www.w3.org/TR/xhtml-media-types/
- */
- public function setMimeEncoding($type = 'text/html', $sync = true)
- {
- $this->_mime = strtolower($type);
-
- // Syncing with metadata
- if ($sync)
- {
- $this->setMetaData('content-type', $type . '; charset=' . $this->_charset, true);
- }
-
- return $this;
- }
-
- /**
- * Return the document MIME encoding that is sent to the browser.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getMimeEncoding()
- {
- return $this->_mime;
- }
-
- /**
- * Sets the line end style to Windows, Mac, Unix or a custom string.
- *
- * @param string $style "win", "mac", "unix" or custom string.
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setLineEnd($style)
- {
- switch ($style)
- {
- case 'win':
- $this->_lineEnd = "\15\12";
- break;
- case 'unix':
- $this->_lineEnd = "\12";
- break;
- case 'mac':
- $this->_lineEnd = "\15";
- break;
- default:
- $this->_lineEnd = $style;
- }
-
- return $this;
- }
-
- /**
- * Returns the lineEnd
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function _getLineEnd()
- {
- return $this->_lineEnd;
- }
-
- /**
- * Sets the string used to indent HTML
- *
- * @param string $string String used to indent ("\11", "\t", ' ', etc.).
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setTab($string)
- {
- $this->_tab = $string;
-
- return $this;
- }
-
- /**
- * Returns a string containing the unit for indenting HTML
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function _getTab()
- {
- return $this->_tab;
- }
-
- /**
- * Load a renderer
- *
- * @param string $type The renderer type
- *
- * @return RendererInterface
- *
- * @since 1.7.0
- * @throws \RuntimeException
- */
- public function loadRenderer($type)
- {
- return $this->factory->createRenderer($this, $type);
- }
-
- /**
- * Parses the document and prepares the buffers
- *
- * @param array $params The array of parameters
- *
- * @return Document instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function parse($params = array())
- {
- return $this;
- }
-
- /**
- * Outputs the document
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- $app = CmsFactory::getApplication();
-
- if ($mdate = $this->getModifiedDate())
- {
- if (!($mdate instanceof Date))
- {
- $mdate = new Date($mdate);
- }
-
- $app->modifiedDate = $mdate;
- }
-
- $app->mimeType = $this->_mime;
- $app->charSet = $this->_charset;
-
- // Handle preloading for configured assets in web applications
- if ($app instanceof AbstractWebApplication)
- {
- $this->preloadAssets();
- }
-
- return '';
- }
-
- /**
- * Generate the Link header for assets configured for preloading
- *
- * @return void
- *
- * @since 4.0.0
- */
- protected function preloadAssets()
- {
- // Process stylesheets first
- foreach ($this->_styleSheets as $link => $properties)
- {
- if (empty($properties['options']['preload']))
- {
- continue;
- }
-
- foreach ($properties['options']['preload'] as $preloadMethod)
- {
- // Make sure the preload method is supported, special case for `dns-prefetch` to convert it to the right method name
- if ($preloadMethod === 'dns-prefetch')
- {
- $this->getPreloadManager()->dnsPrefetch($link);
- }
- elseif (\in_array($preloadMethod, $this->preloadTypes))
- {
- $this->getPreloadManager()->$preloadMethod($link);
- }
- else
- {
- throw new \InvalidArgumentException(sprintf('The "%s" method is not supported for preloading.', $preloadMethod), 500);
- }
- }
- }
-
- // Now process scripts
- foreach ($this->_scripts as $link => $properties)
- {
- if (empty($properties['options']['preload']))
- {
- continue;
- }
-
- foreach ($properties['options']['preload'] as $preloadMethod)
- {
- // Make sure the preload method is supported, special case for `dns-prefetch` to convert it to the right method name
- if ($preloadMethod === 'dns-prefetch')
- {
- $this->getPreloadManager()->dnsPrefetch($link);
- }
- elseif (\in_array($preloadMethod, $this->preloadTypes))
- {
- $this->getPreloadManager()->$preloadMethod($link);
- }
- else
- {
- throw new \InvalidArgumentException(sprintf('The "%s" method is not supported for preloading.', $preloadMethod), 500);
- }
- }
- }
-
- // Check if the manager's provider has links, if so add the Link header
- if ($links = $this->getPreloadManager()->getLinkProvider()->getLinks())
- {
- CmsFactory::getApplication()->setHeader('Link', (new HttpHeaderSerializer)->serialize($links));
- }
- }
+ /**
+ * Document title
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $title = '';
+
+ /**
+ * Document description
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $description = '';
+
+ /**
+ * Document full URL
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $link = '';
+
+ /**
+ * Document base URL
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $base = '';
+
+ /**
+ * Contains the document language setting
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $language = 'en-gb';
+
+ /**
+ * Contains the document direction setting
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $direction = 'ltr';
+
+ /**
+ * Document generator
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_generator = 'Joomla! - Open Source Content Management';
+
+ /**
+ * Document modified date
+ *
+ * @var string|Date
+ * @since 1.7.0
+ */
+ public $_mdate = '';
+
+ /**
+ * Tab string
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_tab = "\11";
+
+ /**
+ * Contains the line end string
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_lineEnd = "\12";
+
+ /**
+ * Contains the character encoding string
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_charset = 'utf-8';
+
+ /**
+ * Document mime type
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_mime = '';
+
+ /**
+ * Document namespace
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_namespace = '';
+
+ /**
+ * Document profile
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_profile = '';
+
+ /**
+ * Array of linked scripts
+ *
+ * @var array
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public $_scripts = array();
+
+ /**
+ * Array of scripts placed in the header
+ *
+ * @var array
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public $_script = array();
+
+ /**
+ * Array of scripts options
+ *
+ * @var array
+ */
+ protected $scriptOptions = array();
+
+ /**
+ * Array of linked style sheets
+ *
+ * @var array
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public $_styleSheets = array();
+
+ /**
+ * Array of included style declarations
+ *
+ * @var array
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public $_style = array();
+
+ /**
+ * Array of meta tags
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $_metaTags = array();
+
+ /**
+ * The rendering engine
+ *
+ * @var object
+ * @since 1.7.0
+ */
+ public $_engine = null;
+
+ /**
+ * The document type
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $_type = null;
+
+ /**
+ * Array of buffered output
+ *
+ * @var mixed (depends on the renderer)
+ * @since 1.7.0
+ */
+ public static $_buffer = null;
+
+ /**
+ * Document instances container.
+ *
+ * @var array
+ * @since 1.7.3
+ */
+ protected static $instances = array();
+
+ /**
+ * Media version added to assets
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $mediaVersion = null;
+
+ /**
+ * Factory for creating JDocument API objects
+ *
+ * @var FactoryInterface
+ * @since 4.0.0
+ */
+ protected $factory;
+
+ /**
+ * Preload manager
+ *
+ * @var PreloadManagerInterface
+ * @since 4.0.0
+ */
+ protected $preloadManager = null;
+
+ /**
+ * The supported preload types
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $preloadTypes = ['preload', 'dns-prefetch', 'preconnect', 'prefetch', 'prerender'];
+
+ /**
+ * Web Asset instance
+ *
+ * @var WebAssetManager
+ * @since 4.0.0
+ */
+ protected $webAssetManager = null;
+
+ /**
+ * Class constructor.
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ if (\array_key_exists('lineend', $options)) {
+ $this->setLineEnd($options['lineend']);
+ }
+
+ if (\array_key_exists('charset', $options)) {
+ $this->setCharset($options['charset']);
+ }
+
+ if (\array_key_exists('language', $options)) {
+ $this->setLanguage($options['language']);
+ }
+
+ if (\array_key_exists('direction', $options)) {
+ $this->setDirection($options['direction']);
+ }
+
+ if (\array_key_exists('tab', $options)) {
+ $this->setTab($options['tab']);
+ }
+
+ if (\array_key_exists('link', $options)) {
+ $this->setLink($options['link']);
+ }
+
+ if (\array_key_exists('base', $options)) {
+ $this->setBase($options['base']);
+ }
+
+ if (\array_key_exists('mediaversion', $options)) {
+ $this->setMediaVersion($options['mediaversion']);
+ }
+
+ if (\array_key_exists('factory', $options)) {
+ $this->setFactory($options['factory']);
+ } else {
+ $this->setFactory(new Factory());
+ }
+
+ if (\array_key_exists('preloadManager', $options)) {
+ $this->setPreloadManager($options['preloadManager']);
+ } else {
+ $this->setPreloadManager(new PreloadManager());
+ }
+
+ if (\array_key_exists('webAssetManager', $options)) {
+ $this->setWebAssetManager($options['webAssetManager']);
+ } else {
+ $webAssetManager = new WebAssetManager(\Joomla\CMS\Factory::getContainer()->get('webassetregistry'));
+
+ $this->setWebAssetManager($webAssetManager);
+ }
+ }
+
+ /**
+ * Returns the global Document object, only creating it
+ * if it doesn't already exist.
+ *
+ * @param string $type The document type to instantiate
+ * @param array $attributes Array of attributes
+ *
+ * @return static The document object.
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Use the \Joomla\CMS\Document\FactoryInterface instead
+ */
+ public static function getInstance($type = 'html', $attributes = array())
+ {
+ $signature = serialize(array($type, $attributes));
+
+ if (empty(self::$instances[$signature])) {
+ self::$instances[$signature] = CmsFactory::getContainer()->get(FactoryInterface::class)->createDocument($type, $attributes);
+ }
+
+ return self::$instances[$signature];
+ }
+
+ /**
+ * Set the factory instance
+ *
+ * @param FactoryInterface $factory The factory instance
+ *
+ * @return Document
+ *
+ * @since 4.0.0
+ */
+ public function setFactory(FactoryInterface $factory): self
+ {
+ $this->factory = $factory;
+
+ return $this;
+ }
+
+ /**
+ * Set the document type
+ *
+ * @param string $type Type document is to set to
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setType($type)
+ {
+ $this->_type = $type;
+
+ return $this;
+ }
+
+ /**
+ * Returns the document type
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Get the contents of the document buffer
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function getBuffer()
+ {
+ return self::$_buffer;
+ }
+
+ /**
+ * Set the contents of the document buffer
+ *
+ * @param string $content The content to be set in the buffer.
+ * @param array $options Array of optional elements.
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setBuffer($content, $options = array())
+ {
+ self::$_buffer = $content;
+
+ return $this;
+ }
+
+ /**
+ * Gets a meta tag.
+ *
+ * @param string $name Name of the meta HTML tag
+ * @param string $attribute Attribute to use in the meta HTML tag
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getMetaData($name, $attribute = 'name')
+ {
+ // B/C old http_equiv parameter.
+ if (!\is_string($attribute)) {
+ $attribute = $attribute == true ? 'http-equiv' : 'name';
+ }
+
+ if ($name === 'generator') {
+ $result = $this->getGenerator();
+ } elseif ($name === 'description') {
+ $result = $this->getDescription();
+ } else {
+ $result = isset($this->_metaTags[$attribute]) && isset($this->_metaTags[$attribute][$name]) ? $this->_metaTags[$attribute][$name] : '';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Sets or alters a meta tag.
+ *
+ * @param string $name Name of the meta HTML tag
+ * @param mixed $content Value of the meta HTML tag as array or string
+ * @param string $attribute Attribute to use in the meta HTML tag
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setMetaData($name, $content, $attribute = 'name')
+ {
+ // Pop the element off the end of array if target function expects a string or this http_equiv parameter.
+ if (\is_array($content) && (\in_array($name, array('generator', 'description')) || !\is_string($attribute))) {
+ $content = array_pop($content);
+ }
+
+ // B/C old http_equiv parameter.
+ if (!\is_string($attribute)) {
+ $attribute = $attribute == true ? 'http-equiv' : 'name';
+ }
+
+ if ($name === 'generator') {
+ $this->setGenerator($content);
+ } elseif ($name === 'description') {
+ $this->setDescription($content);
+ } else {
+ $this->_metaTags[$attribute][$name] = $content;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a linked script to the page
+ *
+ * @param string $url URL to the linked script.
+ * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9', 'preload' => array('preload'))
+ * @param array $attribs Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public function addScript($url, $options = array(), $attribs = array())
+ {
+ // Default value for type.
+ if (!isset($attribs['type']) && !isset($attribs['mime'])) {
+ $attribs['type'] = 'text/javascript';
+ }
+
+ $this->_scripts[$url] = isset($this->_scripts[$url]) ? array_replace($this->_scripts[$url], $attribs) : $attribs;
+ $this->_scripts[$url]['options'] = isset($this->_scripts[$url]['options']) ? array_replace($this->_scripts[$url]['options'], $options) : $options;
+
+ return $this;
+ }
+
+ /**
+ * Adds a script to the page
+ *
+ * @param string $content Script
+ * @param string $type Scripting mime (defaults to 'text/javascript')
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public function addScriptDeclaration($content, $type = 'text/javascript')
+ {
+ $type = strtolower($type);
+
+ if (empty($this->_script[$type])) {
+ $this->_script[$type] = array();
+ }
+
+ $this->_script[$type][md5($content)] = $content;
+
+ return $this;
+ }
+
+ /**
+ * Add option for script
+ *
+ * @param string $key Name in Storage
+ * @param mixed $options Scrip options as array or string
+ * @param bool $merge Whether merge with existing (true) or replace (false)
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 3.5
+ */
+ public function addScriptOptions($key, $options, $merge = true)
+ {
+ if (empty($this->scriptOptions[$key])) {
+ $this->scriptOptions[$key] = array();
+ }
+
+ if ($merge && \is_array($options)) {
+ $this->scriptOptions[$key] = array_replace_recursive($this->scriptOptions[$key], $options);
+ } else {
+ $this->scriptOptions[$key] = $options;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get script(s) options
+ *
+ * @param string $key Name in Storage
+ *
+ * @return array Options for given $key, or all script options
+ *
+ * @since 3.5
+ */
+ public function getScriptOptions($key = null)
+ {
+ if ($key) {
+ return (empty($this->scriptOptions[$key])) ? array() : $this->scriptOptions[$key];
+ } else {
+ return $this->scriptOptions;
+ }
+ }
+
+ /**
+ * Adds a linked stylesheet to the page
+ *
+ * @param string $url URL to the linked style sheet
+ * @param array $options Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9', 'preload' => array('preload'))
+ * @param array $attribs Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public function addStyleSheet($url, $options = array(), $attribs = array())
+ {
+ // Default value for type.
+ if (!isset($attribs['type']) && !isset($attribs['mime'])) {
+ $attribs['type'] = 'text/css';
+ }
+
+ $this->_styleSheets[$url] = isset($this->_styleSheets[$url]) ? array_replace($this->_styleSheets[$url], $attribs) : $attribs;
+
+ if (isset($this->_styleSheets[$url]['options'])) {
+ $this->_styleSheets[$url]['options'] = array_replace($this->_styleSheets[$url]['options'], $options);
+ } else {
+ $this->_styleSheets[$url]['options'] = $options;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a stylesheet declaration to the page
+ *
+ * @param string $content Style declarations
+ * @param string $type Type of stylesheet (defaults to 'text/css')
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ *
+ * @deprecated 5.0 Use WebAssetManager
+ */
+ public function addStyleDeclaration($content, $type = 'text/css')
+ {
+ $type = strtolower($type);
+
+ if (empty($this->_style[$type])) {
+ $this->_style[$type] = array();
+ }
+
+ $this->_style[$type][md5($content)] = $content;
+
+ return $this;
+ }
+
+ /**
+ * Sets the document charset
+ *
+ * @param string $type Charset encoding string
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setCharset($type = 'utf-8')
+ {
+ $this->_charset = $type;
+
+ return $this;
+ }
+
+ /**
+ * Returns the document charset encoding.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getCharset()
+ {
+ return $this->_charset;
+ }
+
+ /**
+ * Sets the global document language declaration. Default is English (en-gb).
+ *
+ * @param string $lang The language to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setLanguage($lang = 'en-gb')
+ {
+ $this->language = strtolower($lang);
+
+ return $this;
+ }
+
+ /**
+ * Returns the document language.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getLanguage()
+ {
+ return $this->language;
+ }
+
+ /**
+ * Sets the global document direction declaration. Default is left-to-right (ltr).
+ *
+ * @param string $dir The language direction to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setDirection($dir = 'ltr')
+ {
+ $this->direction = strtolower($dir);
+
+ return $this;
+ }
+
+ /**
+ * Returns the document direction declaration.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getDirection()
+ {
+ return $this->direction;
+ }
+
+ /**
+ * Sets the title of the document
+ *
+ * @param string $title The title to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Return the title of the document.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set the assets version
+ *
+ * @param string $mediaVersion Media version to use
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 3.2
+ */
+ public function setMediaVersion($mediaVersion)
+ {
+ $this->mediaVersion = strtolower($mediaVersion);
+
+ return $this;
+ }
+
+ /**
+ * Return the media version
+ *
+ * @return string
+ *
+ * @since 3.2
+ */
+ public function getMediaVersion()
+ {
+ return $this->mediaVersion;
+ }
+
+ /**
+ * Set the preload manager
+ *
+ * @param PreloadManagerInterface $preloadManager The preload manager service
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 4.0.0
+ */
+ public function setPreloadManager(PreloadManagerInterface $preloadManager): self
+ {
+ $this->preloadManager = $preloadManager;
+
+ return $this;
+ }
+
+ /**
+ * Return the preload manager
+ *
+ * @return PreloadManagerInterface
+ *
+ * @since 4.0.0
+ */
+ public function getPreloadManager(): PreloadManagerInterface
+ {
+ return $this->preloadManager;
+ }
+
+ /**
+ * Set WebAsset manager
+ *
+ * @param WebAssetManager $webAsset The WebAsset instance
+ *
+ * @return Document
+ *
+ * @since 4.0.0
+ */
+ public function setWebAssetManager(WebAssetManager $webAsset): self
+ {
+ $this->webAssetManager = $webAsset;
+
+ return $this;
+ }
+
+ /**
+ * Return WebAsset manager
+ *
+ * @return WebAssetManager
+ *
+ * @since 4.0.0
+ */
+ public function getWebAssetManager(): WebAssetManager
+ {
+ return $this->webAssetManager;
+ }
+
+ /**
+ * Sets the base URI of the document
+ *
+ * @param string $base The base URI to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setBase($base)
+ {
+ $this->base = $base;
+
+ return $this;
+ }
+
+ /**
+ * Return the base URI of the document.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getBase()
+ {
+ return $this->base;
+ }
+
+ /**
+ * Sets the description of the document
+ *
+ * @param string $description The description to set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Return the description of the document.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Sets the document link
+ *
+ * @param string $url A url
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setLink($url)
+ {
+ $this->link = $url;
+
+ return $this;
+ }
+
+ /**
+ * Returns the document base url
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getLink()
+ {
+ return $this->link;
+ }
+
+ /**
+ * Sets the document generator
+ *
+ * @param string $generator The generator to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setGenerator($generator)
+ {
+ $this->_generator = $generator;
+
+ return $this;
+ }
+
+ /**
+ * Returns the document generator
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getGenerator()
+ {
+ return $this->_generator;
+ }
+
+ /**
+ * Sets the document modified date
+ *
+ * @param string|Date $date The date to be set
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ * @throws \InvalidArgumentException
+ */
+ public function setModifiedDate($date)
+ {
+ if (!\is_string($date) && !($date instanceof Date)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'The $date parameter of %1$s must be a string or a %2$s instance, a %3$s was given.',
+ __METHOD__ . '()',
+ 'Joomla\\CMS\\Date\\Date',
+ \gettype($date) === 'object' ? (\get_class($date) . ' instance') : \gettype($date)
+ )
+ );
+ }
+
+ $this->_mdate = $date;
+
+ return $this;
+ }
+
+ /**
+ * Returns the document modified date
+ *
+ * @return string|Date
+ *
+ * @since 1.7.0
+ */
+ public function getModifiedDate()
+ {
+ return $this->_mdate;
+ }
+
+ /**
+ * Sets the document MIME encoding that is sent to the browser.
+ *
+ * This usually will be text/html because most browsers cannot yet
+ * accept the proper mime settings for XHTML: application/xhtml+xml
+ * and to a lesser extent application/xml and text/xml. See the W3C note
+ * ({@link https://www.w3.org/TR/xhtml-media-types/
+ * https://www.w3.org/TR/xhtml-media-types/}) for more details.
+ *
+ * @param string $type The document type to be sent
+ * @param boolean $sync Should the type be synced with HTML?
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ *
+ * @link https://www.w3.org/TR/xhtml-media-types/
+ */
+ public function setMimeEncoding($type = 'text/html', $sync = true)
+ {
+ $this->_mime = strtolower($type);
+
+ // Syncing with metadata
+ if ($sync) {
+ $this->setMetaData('content-type', $type . '; charset=' . $this->_charset, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the document MIME encoding that is sent to the browser.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getMimeEncoding()
+ {
+ return $this->_mime;
+ }
+
+ /**
+ * Sets the line end style to Windows, Mac, Unix or a custom string.
+ *
+ * @param string $style "win", "mac", "unix" or custom string.
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setLineEnd($style)
+ {
+ switch ($style) {
+ case 'win':
+ $this->_lineEnd = "\15\12";
+ break;
+ case 'unix':
+ $this->_lineEnd = "\12";
+ break;
+ case 'mac':
+ $this->_lineEnd = "\15";
+ break;
+ default:
+ $this->_lineEnd = $style;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the lineEnd
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function _getLineEnd()
+ {
+ return $this->_lineEnd;
+ }
+
+ /**
+ * Sets the string used to indent HTML
+ *
+ * @param string $string String used to indent ("\11", "\t", ' ', etc.).
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setTab($string)
+ {
+ $this->_tab = $string;
+
+ return $this;
+ }
+
+ /**
+ * Returns a string containing the unit for indenting HTML
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function _getTab()
+ {
+ return $this->_tab;
+ }
+
+ /**
+ * Load a renderer
+ *
+ * @param string $type The renderer type
+ *
+ * @return RendererInterface
+ *
+ * @since 1.7.0
+ * @throws \RuntimeException
+ */
+ public function loadRenderer($type)
+ {
+ return $this->factory->createRenderer($this, $type);
+ }
+
+ /**
+ * Parses the document and prepares the buffers
+ *
+ * @param array $params The array of parameters
+ *
+ * @return Document instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function parse($params = array())
+ {
+ return $this;
+ }
+
+ /**
+ * Outputs the document
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ $app = CmsFactory::getApplication();
+
+ if ($mdate = $this->getModifiedDate()) {
+ if (!($mdate instanceof Date)) {
+ $mdate = new Date($mdate);
+ }
+
+ $app->modifiedDate = $mdate;
+ }
+
+ $app->mimeType = $this->_mime;
+ $app->charSet = $this->_charset;
+
+ // Handle preloading for configured assets in web applications
+ if ($app instanceof AbstractWebApplication) {
+ $this->preloadAssets();
+ }
+
+ return '';
+ }
+
+ /**
+ * Generate the Link header for assets configured for preloading
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ protected function preloadAssets()
+ {
+ // Process stylesheets first
+ foreach ($this->_styleSheets as $link => $properties) {
+ if (empty($properties['options']['preload'])) {
+ continue;
+ }
+
+ foreach ($properties['options']['preload'] as $preloadMethod) {
+ // Make sure the preload method is supported, special case for `dns-prefetch` to convert it to the right method name
+ if ($preloadMethod === 'dns-prefetch') {
+ $this->getPreloadManager()->dnsPrefetch($link);
+ } elseif (\in_array($preloadMethod, $this->preloadTypes)) {
+ $this->getPreloadManager()->$preloadMethod($link);
+ } else {
+ throw new \InvalidArgumentException(sprintf('The "%s" method is not supported for preloading.', $preloadMethod), 500);
+ }
+ }
+ }
+
+ // Now process scripts
+ foreach ($this->_scripts as $link => $properties) {
+ if (empty($properties['options']['preload'])) {
+ continue;
+ }
+
+ foreach ($properties['options']['preload'] as $preloadMethod) {
+ // Make sure the preload method is supported, special case for `dns-prefetch` to convert it to the right method name
+ if ($preloadMethod === 'dns-prefetch') {
+ $this->getPreloadManager()->dnsPrefetch($link);
+ } elseif (\in_array($preloadMethod, $this->preloadTypes)) {
+ $this->getPreloadManager()->$preloadMethod($link);
+ } else {
+ throw new \InvalidArgumentException(sprintf('The "%s" method is not supported for preloading.', $preloadMethod), 500);
+ }
+ }
+ }
+
+ // Check if the manager's provider has links, if so add the Link header
+ if ($links = $this->getPreloadManager()->getLinkProvider()->getLinks()) {
+ CmsFactory::getApplication()->setHeader('Link', (new HttpHeaderSerializer())->serialize($links));
+ }
+ }
}
diff --git a/libraries/src/Document/DocumentRenderer.php b/libraries/src/Document/DocumentRenderer.php
index a553744e2bde5..32b64ddf0da3e 100644
--- a/libraries/src/Document/DocumentRenderer.php
+++ b/libraries/src/Document/DocumentRenderer.php
@@ -1,4 +1,5 @@
_doc = $doc;
- }
+ /**
+ * Class constructor
+ *
+ * @param Document $doc A reference to the Document object that instantiated the renderer
+ *
+ * @since 1.7.0
+ */
+ public function __construct(Document $doc)
+ {
+ $this->_doc = $doc;
+ }
- /**
- * Return the content type of the renderer
- *
- * @return string The contentType
- *
- * @since 1.7.0
- */
- public function getContentType()
- {
- return $this->_mime;
- }
+ /**
+ * Return the content type of the renderer
+ *
+ * @return string The contentType
+ *
+ * @since 1.7.0
+ */
+ public function getContentType()
+ {
+ return $this->_mime;
+ }
- /**
- * Convert links in a text from relative to absolute
- *
- * @param string $text The text processed
- *
- * @return string Text with converted links
- *
- * @since 1.7.0
- */
- protected function _relToAbs($text)
- {
- $base = Uri::base();
- $text = preg_replace("/(href|src)=\"(?!http|ftp|https|mailto|data|\/\/)([^\"]*)\"/", "$1=\"$base\$2\"", $text);
+ /**
+ * Convert links in a text from relative to absolute
+ *
+ * @param string $text The text processed
+ *
+ * @return string Text with converted links
+ *
+ * @since 1.7.0
+ */
+ protected function _relToAbs($text)
+ {
+ $base = Uri::base();
+ $text = preg_replace("/(href|src)=\"(?!http|ftp|https|mailto|data|\/\/)([^\"]*)\"/", "$1=\"$base\$2\"", $text);
- return $text;
- }
+ return $text;
+ }
}
diff --git a/libraries/src/Document/ErrorDocument.php b/libraries/src/Document/ErrorDocument.php
index 2ca680aa65b22..dc912c8829a65 100644
--- a/libraries/src/Document/ErrorDocument.php
+++ b/libraries/src/Document/ErrorDocument.php
@@ -1,4 +1,5 @@
_type = 'error';
- }
-
- /**
- * Set error object
- *
- * @param \Throwable $error Error object to set
- *
- * @return boolean True on success
- *
- * @since 1.7.0
- */
- public function setError($error)
- {
- if ($error instanceof \Throwable)
- {
- $this->_error = & $error;
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Load a renderer
- *
- * @param string $type The renderer type
- *
- * @return RendererInterface
- *
- * @since 4.0.0
- * @throws \RuntimeException
- */
- public function loadRenderer($type)
- {
- // Need to force everything to go to the HTML renderers or we duplicate all the things
- return $this->factory->createRenderer($this, $type, 'html');
- }
-
- /**
- * Render the document
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- // If no error object is set return null
- if (!isset($this->_error))
- {
- return;
- }
-
- // Set the status header
- $status = $this->_error->getCode();
-
- if ($status < 400 || $status > 599)
- {
- $status = 500;
- }
-
- $errorReporting = CmsFactory::getApplication()->get('error_reporting');
-
- if ($errorReporting === "development" || $errorReporting === "maximum")
- {
- $status .= ' ' . str_replace("\n", ' ', $this->_error->getMessage());
- }
-
- CmsFactory::getApplication()->setHeader('status', $status);
-
- // Set variables
- $this->debug = $params['debug'] ?? false;
- $this->error = $this->_error;
-
- $params['file'] = 'error.php';
-
- return parent::render($cache, $params);
- }
-
- /**
- * Render the backtrace
- *
- * @return string The contents of the backtrace
- *
- * @since 1.7.0
- */
- public function renderBacktrace()
- {
- // If no error object is set return null
- if (!isset($this->_error))
- {
- return;
- }
-
- // The back trace
- $backtrace = $this->_error->getTrace();
-
- // Add the position of the actual file
- array_unshift($backtrace, array('file' => $this->_error->getFile(), 'line' => $this->_error->getLine(), 'function' => ''));
-
- return LayoutHelper::render('joomla.error.backtrace', array('backtrace' => $backtrace));
- }
+ /**
+ * Flag if debug mode has been enabled
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ public $debug = false;
+
+ /**
+ * Error Object
+ *
+ * @var \Throwable
+ * @since 1.7.0
+ */
+ public $error;
+
+ /**
+ * Error Object
+ *
+ * @var \Throwable
+ * @since 1.7.0
+ */
+ protected $_error;
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Associative array of attributes
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set document type
+ $this->_type = 'error';
+ }
+
+ /**
+ * Set error object
+ *
+ * @param \Throwable $error Error object to set
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public function setError($error)
+ {
+ if ($error instanceof \Throwable) {
+ $this->_error = & $error;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Load a renderer
+ *
+ * @param string $type The renderer type
+ *
+ * @return RendererInterface
+ *
+ * @since 4.0.0
+ * @throws \RuntimeException
+ */
+ public function loadRenderer($type)
+ {
+ // Need to force everything to go to the HTML renderers or we duplicate all the things
+ return $this->factory->createRenderer($this, $type, 'html');
+ }
+
+ /**
+ * Render the document
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ // If no error object is set return null
+ if (!isset($this->_error)) {
+ return;
+ }
+
+ // Set the status header
+ $status = $this->_error->getCode();
+
+ if ($status < 400 || $status > 599) {
+ $status = 500;
+ }
+
+ $errorReporting = CmsFactory::getApplication()->get('error_reporting');
+
+ if ($errorReporting === "development" || $errorReporting === "maximum") {
+ $status .= ' ' . str_replace("\n", ' ', $this->_error->getMessage());
+ }
+
+ CmsFactory::getApplication()->setHeader('status', $status);
+
+ // Set variables
+ $this->debug = $params['debug'] ?? false;
+ $this->error = $this->_error;
+
+ $params['file'] = 'error.php';
+
+ return parent::render($cache, $params);
+ }
+
+ /**
+ * Render the backtrace
+ *
+ * @return string The contents of the backtrace
+ *
+ * @since 1.7.0
+ */
+ public function renderBacktrace()
+ {
+ // If no error object is set return null
+ if (!isset($this->_error)) {
+ return;
+ }
+
+ // The back trace
+ $backtrace = $this->_error->getTrace();
+
+ // Add the position of the actual file
+ array_unshift($backtrace, array('file' => $this->_error->getFile(), 'line' => $this->_error->getLine(), 'function' => ''));
+
+ return LayoutHelper::render('joomla.error.backtrace', array('backtrace' => $backtrace));
+ }
}
diff --git a/libraries/src/Document/Factory.php b/libraries/src/Document/Factory.php
index e3d6134098416..53abba66919a7 100644
--- a/libraries/src/Document/Factory.php
+++ b/libraries/src/Document/Factory.php
@@ -1,4 +1,5 @@
setType($ntype);
- }
-
- if ($instance instanceof CacheControllerFactoryAwareInterface)
- {
- $instance->setCacheControllerFactory($this->getCacheControllerFactory());
- }
-
- return $instance;
- }
-
- /**
- * Creates a new renderer object.
- *
- * @param Document $document The Document instance to attach to the renderer
- * @param string $type The renderer type to instantiate
- * @param string $docType The document type the renderer is part of
- *
- * @return RendererInterface
- *
- * @since 4.0.0
- */
- public function createRenderer(Document $document, string $type, string $docType = ''): RendererInterface
- {
- $docType = $docType ? ucfirst($docType) : ucfirst($document->getType());
-
- // Determine the path and class
- $class = __NAMESPACE__ . '\\Renderer\\' . $docType . '\\' . ucfirst($type) . 'Renderer';
-
- if (!class_exists($class))
- {
- $class = 'JDocumentRenderer' . $docType . ucfirst($type);
- }
-
- if (!class_exists($class))
- {
- // "Legacy" class name structure
- $class = '\\JDocumentRenderer' . $type;
-
- if (!class_exists($class))
- {
- throw new \RuntimeException(sprintf('Unable to load renderer class %s', $type), 500);
- }
- }
-
- $instance = new $class($document);
-
- if ($instance instanceof CacheControllerFactoryAwareInterface)
- {
- $instance->setCacheControllerFactory($this->getCacheControllerFactory());
- }
-
- return $instance;
- }
+ use CacheControllerFactoryAwareTrait;
+
+ /**
+ * Creates a new Document object for the requested format.
+ *
+ * @param string $type The document type to instantiate
+ * @param array $attributes Array of attributes
+ *
+ * @return Document
+ *
+ * @since 4.0.0
+ */
+ public function createDocument(string $type = 'html', array $attributes = []): Document
+ {
+ $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
+ $ntype = null;
+
+ $class = __NAMESPACE__ . '\\' . ucfirst($type) . 'Document';
+
+ if (!class_exists($class)) {
+ $class = 'JDocument' . ucfirst($type);
+ }
+
+ if (!class_exists($class)) {
+ $ntype = $type;
+ $class = RawDocument::class;
+ }
+
+ // Inject this factory into the document unless one was provided
+ if (!isset($attributes['factory'])) {
+ $attributes['factory'] = $this;
+ }
+
+ /** @var Document $instance */
+ $instance = new $class($attributes);
+
+ if (!\is_null($ntype)) {
+ // Set the type to the Document type originally requested
+ $instance->setType($ntype);
+ }
+
+ if ($instance instanceof CacheControllerFactoryAwareInterface) {
+ $instance->setCacheControllerFactory($this->getCacheControllerFactory());
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Creates a new renderer object.
+ *
+ * @param Document $document The Document instance to attach to the renderer
+ * @param string $type The renderer type to instantiate
+ * @param string $docType The document type the renderer is part of
+ *
+ * @return RendererInterface
+ *
+ * @since 4.0.0
+ */
+ public function createRenderer(Document $document, string $type, string $docType = ''): RendererInterface
+ {
+ $docType = $docType ? ucfirst($docType) : ucfirst($document->getType());
+
+ // Determine the path and class
+ $class = __NAMESPACE__ . '\\Renderer\\' . $docType . '\\' . ucfirst($type) . 'Renderer';
+
+ if (!class_exists($class)) {
+ $class = 'JDocumentRenderer' . $docType . ucfirst($type);
+ }
+
+ if (!class_exists($class)) {
+ // "Legacy" class name structure
+ $class = '\\JDocumentRenderer' . $type;
+
+ if (!class_exists($class)) {
+ throw new \RuntimeException(sprintf('Unable to load renderer class %s', $type), 500);
+ }
+ }
+
+ $instance = new $class($document);
+
+ if ($instance instanceof CacheControllerFactoryAwareInterface) {
+ $instance->setCacheControllerFactory($this->getCacheControllerFactory());
+ }
+
+ return $instance;
+ }
}
diff --git a/libraries/src/Document/FactoryInterface.php b/libraries/src/Document/FactoryInterface.php
index 005555a7ae4ac..ca4e6bf230947 100644
--- a/libraries/src/Document/FactoryInterface.php
+++ b/libraries/src/Document/FactoryInterface.php
@@ -1,4 +1,5 @@
enclosure = $enclosure;
+ /**
+ * Set the FeedEnclosure for this item
+ *
+ * @param FeedEnclosure $enclosure The FeedEnclosure to add to the feed.
+ *
+ * @return FeedItem instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setEnclosure(FeedEnclosure $enclosure)
+ {
+ $this->enclosure = $enclosure;
- return $this;
- }
+ return $this;
+ }
}
diff --git a/libraries/src/Document/FeedDocument.php b/libraries/src/Document/FeedDocument.php
index e90261af9cd72..1df183f23c500 100644
--- a/libraries/src/Document/FeedDocument.php
+++ b/libraries/src/Document/FeedDocument.php
@@ -1,4 +1,5 @@
_type = 'feed';
-
- // Gets and sets timezone offset from site configuration
- $this->lastBuildDate = CmsFactory::getDate();
- $this->lastBuildDate->setTimezone(new \DateTimeZone(CmsFactory::getApplication()->get('offset', 'UTC')));
- }
-
- /**
- * Render the document
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- * @throws \Exception
- * @todo Make this cacheable
- */
- public function render($cache = false, $params = array())
- {
- // Get the feed type
- $type = CmsFactory::getApplication()->input->get('type', 'rss');
-
- // Instantiate feed renderer and set the mime encoding
- $renderer = $this->loadRenderer($type ?: 'rss');
-
- if (!($renderer instanceof DocumentRenderer))
- {
- throw new \Exception(Text::_('JGLOBAL_RESOURCE_NOT_FOUND'), 404);
- }
-
- $this->setMimeEncoding($renderer->getContentType());
-
- // Output
- // Generate prolog
- $data = "_charset . "\"?>\n";
- $data .= "\n";
-
- // Generate stylesheet links
- foreach ($this->_styleSheets as $src => $attr)
- {
- $data .= "\n";
- }
-
- // Render the feed
- $data .= $renderer->render();
-
- parent::render($cache, $params);
-
- return $data;
- }
-
- /**
- * Adds a FeedItem to the feed.
- *
- * @param FeedItem $item The feeditem to add to the feed.
- *
- * @return FeedDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addItem(FeedItem $item)
- {
- $item->source = $this->link;
- $this->items[] = $item;
-
- return $this;
- }
+ /**
+ * Syndication URL feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $syndicationURL = '';
+
+ /**
+ * Image feed element
+ *
+ * optional
+ *
+ * @var FeedImage
+ * @since 1.7.0
+ */
+ public $image = null;
+
+ /**
+ * Copyright feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $copyright = '';
+
+ /**
+ * Published date feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $pubDate = '';
+
+ /**
+ * Lastbuild date feed element
+ *
+ * @var \Joomla\CMS\Date\Date
+ * @since 1.7.0
+ */
+ public $lastBuildDate;
+
+ /**
+ * Editor feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $editor = '';
+
+ /**
+ * Docs feed element
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $docs = '';
+
+ /**
+ * Editor email feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $editorEmail = '';
+
+ /**
+ * Webmaster email feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $webmaster = '';
+
+ /**
+ * Category feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $category = '';
+
+ /**
+ * TTL feed attribute
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $ttl = '';
+
+ /**
+ * Rating feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $rating = '';
+
+ /**
+ * Skiphours feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $skipHours = '';
+
+ /**
+ * Skipdays feed element
+ *
+ * optional
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $skipDays = '';
+
+ /**
+ * The feed items collection
+ *
+ * @var FeedItem[]
+ * @since 1.7.0
+ */
+ public $items = array();
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set document type
+ $this->_type = 'feed';
+
+ // Gets and sets timezone offset from site configuration
+ $this->lastBuildDate = CmsFactory::getDate();
+ $this->lastBuildDate->setTimezone(new \DateTimeZone(CmsFactory::getApplication()->get('offset', 'UTC')));
+ }
+
+ /**
+ * Render the document
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ * @throws \Exception
+ * @todo Make this cacheable
+ */
+ public function render($cache = false, $params = array())
+ {
+ // Get the feed type
+ $type = CmsFactory::getApplication()->input->get('type', 'rss');
+
+ // Instantiate feed renderer and set the mime encoding
+ $renderer = $this->loadRenderer($type ?: 'rss');
+
+ if (!($renderer instanceof DocumentRenderer)) {
+ throw new \Exception(Text::_('JGLOBAL_RESOURCE_NOT_FOUND'), 404);
+ }
+
+ $this->setMimeEncoding($renderer->getContentType());
+
+ // Output
+ // Generate prolog
+ $data = "_charset . "\"?>\n";
+ $data .= "\n";
+
+ // Generate stylesheet links
+ foreach ($this->_styleSheets as $src => $attr) {
+ $data .= "\n";
+ }
+
+ // Render the feed
+ $data .= $renderer->render();
+
+ parent::render($cache, $params);
+
+ return $data;
+ }
+
+ /**
+ * Adds a FeedItem to the feed.
+ *
+ * @param FeedItem $item The feeditem to add to the feed.
+ *
+ * @return FeedDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addItem(FeedItem $item)
+ {
+ $item->source = $this->link;
+ $this->items[] = $item;
+
+ return $this;
+ }
}
diff --git a/libraries/src/Document/HtmlDocument.php b/libraries/src/Document/HtmlDocument.php
index 4878c84eb994c..df9d34a1202b7 100644
--- a/libraries/src/Document/HtmlDocument.php
+++ b/libraries/src/Document/HtmlDocument.php
@@ -1,4 +1,5 @@
` tags
- *
- * @var array
- * @since 1.7.0
- */
- public $_links = array();
-
- /**
- * Array of custom tags
- *
- * @var array
- * @since 1.7.0
- */
- public $_custom = array();
-
- /**
- * Name of the template
- *
- * @var string
- * @since 1.7.0
- */
- public $template = null;
-
- /**
- * Base url
- *
- * @var string
- * @since 1.7.0
- */
- public $baseurl = null;
-
- /**
- * Array of template parameters
- *
- * @var array
- * @since 1.7.0
- */
- public $params = null;
-
- /**
- * File name
- *
- * @var array
- * @since 1.7.0
- */
- public $_file = null;
-
- /**
- * Script nonce (string if set, null otherwise)
- *
- * @var string|null
- * @since 4.0.0
- */
- public $cspNonce = null;
-
- /**
- * String holding parsed template
- *
- * @var string
- * @since 1.7.0
- */
- protected $_template = '';
-
- /**
- * Array of parsed template JDoc tags
- *
- * @var array
- * @since 1.7.0
- */
- protected $_template_tags = array();
-
- /**
- * Integer with caching setting
- *
- * @var integer
- * @since 1.7.0
- */
- protected $_caching = null;
-
- /**
- * Set to true when the document should be output as HTML5
- *
- * @var boolean
- * @since 4.0.0
- */
- private $html5 = true;
-
- /**
- * Class constructor
- *
- * @param array $options Associative array of options
- *
- * @since 1.7.0
- */
- public function __construct($options = array())
- {
- parent::__construct($options);
-
- // Set document type
- $this->_type = 'html';
-
- // Set default mime type and document metadata (metadata syncs with mime type by default)
- $this->setMimeEncoding('text/html');
- }
-
- /**
- * Get the HTML document head data
- *
- * @return array The document head data in array form
- *
- * @since 1.7.0
- */
- public function getHeadData()
- {
- $data = array();
- $data['title'] = $this->title;
- $data['description'] = $this->description;
- $data['link'] = $this->link;
- $data['metaTags'] = $this->_metaTags;
- $data['links'] = $this->_links;
- $data['styleSheets'] = $this->_styleSheets;
- $data['style'] = $this->_style;
- $data['scripts'] = $this->_scripts;
- $data['script'] = $this->_script;
- $data['custom'] = $this->_custom;
-
- // @deprecated 5.0 This property is for backwards compatibility. Pass text through script options in the future
- $data['scriptText'] = Text::getScriptStrings();
-
- $data['scriptOptions'] = $this->scriptOptions;
-
- // Get Asset manager state
- $wa = $this->getWebAssetManager();
- $waState = $wa->getManagerState();
-
- // Get asset objects and filter only manually added/enabled assets,
- // Dependencies will be picked up from registry files
- $waState['assets'] = [];
-
- foreach ($waState['activeAssets'] as $assetType => $assetNames)
- {
- foreach ($assetNames as $assetName => $assetState)
- {
- $waState['assets'][$assetType][] = $wa->getAsset($assetType, $assetName);
- }
- }
-
- // We have loaded asset objects, now can remove unused stuff
- unset($waState['activeAssets']);
-
- $data['assetManager'] = $waState;
-
- return $data;
- }
-
- /**
- * Reset the HTML document head data
- *
- * @param mixed $types type or types of the heads elements to reset
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 3.7.0
- */
- public function resetHeadData($types = null)
- {
- if (\is_null($types))
- {
- $this->title = '';
- $this->description = '';
- $this->link = '';
- $this->_metaTags = array();
- $this->_links = array();
- $this->_styleSheets = array();
- $this->_style = array();
- $this->_scripts = array();
- $this->_script = array();
- $this->_custom = array();
- $this->scriptOptions = array();
- }
-
- if (\is_array($types))
- {
- foreach ($types as $type)
- {
- $this->resetHeadDatum($type);
- }
- }
-
- if (\is_string($types))
- {
- $this->resetHeadDatum($types);
- }
-
- return $this;
- }
-
- /**
- * Reset a part the HTML document head data
- *
- * @param string $type type of the heads elements to reset
- *
- * @return void
- *
- * @since 3.7.0
- */
- private function resetHeadDatum($type)
- {
- switch ($type)
- {
- case 'title':
- case 'description':
- case 'link':
- $this->{$type} = '';
- break;
-
- case 'metaTags':
- case 'links':
- case 'styleSheets':
- case 'style':
- case 'scripts':
- case 'script':
- case 'custom':
- $realType = '_' . $type;
- $this->{$realType} = array();
- break;
-
- case 'scriptOptions':
- $this->{$type} = array();
- break;
- }
- }
-
- /**
- * Set the HTML document head data
- *
- * @param array $data The document head data in array form
- *
- * @return HtmlDocument|null instance of $this to allow chaining or null for empty input data
- *
- * @since 1.7.0
- */
- public function setHeadData($data)
- {
- if (empty($data) || !\is_array($data))
- {
- return null;
- }
-
- $this->title = $data['title'] ?? $this->title;
- $this->description = $data['description'] ?? $this->description;
- $this->link = $data['link'] ?? $this->link;
- $this->_metaTags = $data['metaTags'] ?? $this->_metaTags;
- $this->_links = $data['links'] ?? $this->_links;
- $this->_styleSheets = $data['styleSheets'] ?? $this->_styleSheets;
- $this->_style = $data['style'] ?? $this->_style;
- $this->_scripts = $data['scripts'] ?? $this->_scripts;
- $this->_script = $data['script'] ?? $this->_script;
- $this->_custom = $data['custom'] ?? $this->_custom;
- $this->scriptOptions = (isset($data['scriptOptions']) && !empty($data['scriptOptions'])) ? $data['scriptOptions'] : $this->scriptOptions;
-
- // Restore asset manager state
- $wa = $this->getWebAssetManager();
-
- if (!empty($data['assetManager']['registryFiles']))
- {
- $waRegistry = $wa->getRegistry();
-
- foreach ($data['assetManager']['registryFiles'] as $registryFile)
- {
- $waRegistry->addRegistryFile($registryFile);
- }
- }
-
- if (!empty($data['assetManager']['assets']))
- {
- foreach ($data['assetManager']['assets'] as $assetType => $assets)
- {
- foreach ($assets as $asset)
- {
- $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName());
- }
- }
- }
-
- return $this;
- }
-
- /**
- * Merge the HTML document head data
- *
- * @param array $data The document head data in array form
- *
- * @return HtmlDocument|void instance of $this to allow chaining or void for empty input data
- *
- * @since 1.7.0
- */
- public function mergeHeadData($data)
- {
- if (empty($data) || !\is_array($data))
- {
- return;
- }
-
- $this->title = (isset($data['title']) && !empty($data['title']) && !stristr($this->title, $data['title']))
- ? $this->title . $data['title']
- : $this->title;
- $this->description = (isset($data['description']) && !empty($data['description']) && !stristr($this->description, $data['description']))
- ? $this->description . $data['description']
- : $this->description;
- $this->link = $data['link'] ?? $this->link;
-
- if (isset($data['metaTags']))
- {
- foreach ($data['metaTags'] as $type1 => $data1)
- {
- $booldog = $type1 === 'http-equiv';
-
- foreach ($data1 as $name2 => $data2)
- {
- $this->setMetaData($name2, $data2, $booldog);
- }
- }
- }
-
- $this->_links = (isset($data['links']) && !empty($data['links']) && \is_array($data['links']))
- ? array_unique(array_merge($this->_links, $data['links']), SORT_REGULAR)
- : $this->_links;
- $this->_styleSheets = (isset($data['styleSheets']) && !empty($data['styleSheets']) && \is_array($data['styleSheets']))
- ? array_merge($this->_styleSheets, $data['styleSheets'])
- : $this->_styleSheets;
-
- if (isset($data['style']))
- {
- foreach ($data['style'] as $type => $styles)
- {
- foreach ($styles as $hash => $style)
- {
- if (!isset($this->_style[strtolower($type)][$hash]))
- {
- $this->addStyleDeclaration($style, $type);
- }
- }
- }
- }
-
- $this->_scripts = (isset($data['scripts']) && !empty($data['scripts']) && \is_array($data['scripts']))
- ? array_merge($this->_scripts, $data['scripts'])
- : $this->_scripts;
-
- if (isset($data['script']))
- {
- foreach ($data['script'] as $type => $scripts)
- {
- foreach ($scripts as $hash => $script)
- {
- if (!isset($this->_script[strtolower($type)][$hash]))
- {
- $this->addScriptDeclaration($script, $type);
- }
- }
- }
- }
-
- $this->_custom = (isset($data['custom']) && !empty($data['custom']) && \is_array($data['custom']))
- ? array_unique(array_merge($this->_custom, $data['custom']))
- : $this->_custom;
-
- if (!empty($data['scriptOptions']))
- {
- foreach ($data['scriptOptions'] as $key => $scriptOptions)
- {
- $this->addScriptOptions($key, $scriptOptions, true);
- }
- }
-
- // Restore asset manager state
- $wa = $this->getWebAssetManager();
-
- if (!empty($data['assetManager']['registryFiles']))
- {
- $waRegistry = $wa->getRegistry();
-
- foreach ($data['assetManager']['registryFiles'] as $registryFile)
- {
- $waRegistry->addRegistryFile($registryFile);
- }
- }
-
- if (!empty($data['assetManager']['assets']))
- {
- foreach ($data['assetManager']['assets'] as $assetType => $assets)
- {
- foreach ($assets as $asset)
- {
- $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName());
- }
- }
- }
-
- return $this;
- }
-
- /**
- * Adds ` ` tags to the head of the document
- *
- * $relType defaults to 'rel' as it is the most common relation type used.
- * ('rev' refers to reverse relation, 'rel' indicates normal, forward relation.)
- * Typical tag: ` `
- *
- * @param string $href The link that is being related.
- * @param string $relation Relation of link.
- * @param string $relType Relation type attribute. Either rel or rev (default: 'rel').
- * @param array $attribs Associative array of remaining attributes.
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addHeadLink($href, $relation, $relType = 'rel', $attribs = array())
- {
- $this->_links[$href]['relation'] = $relation;
- $this->_links[$href]['relType'] = $relType;
- $this->_links[$href]['attribs'] = $attribs;
-
- return $this;
- }
-
- /**
- * Adds a shortcut icon (favicon)
- *
- * This adds a link to the icon shown in the favorites list or on
- * the left of the url in the address bar. Some browsers display
- * it on the tab, as well.
- *
- * @param string $href The link that is being related.
- * @param string $type File type
- * @param string $relation Relation of link
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addFavicon($href, $type = 'image/vnd.microsoft.icon', $relation = 'shortcut icon')
- {
- $href = str_replace('\\', '/', $href);
- $this->addHeadLink($href, $relation, 'rel', array('type' => $type));
-
- return $this;
- }
-
- /**
- * Adds a custom HTML string to the head block
- *
- * @param string $html The HTML to add to the head
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addCustomTag($html)
- {
- $this->_custom[] = trim($html);
-
- return $this;
- }
-
- /**
- * Returns whether the document is set up to be output as HTML5
- *
- * @return boolean true when HTML5 is used
- *
- * @since 3.0.0
- */
- public function isHtml5()
- {
- return $this->html5;
- }
-
- /**
- * Sets whether the document should be output as HTML5
- *
- * @param bool $state True when HTML5 should be output
- *
- * @return void
- *
- * @since 3.0.0
- */
- public function setHtml5($state)
- {
- if (\is_bool($state))
- {
- $this->html5 = $state;
- }
- }
-
- /**
- * Get the contents of a document include
- *
- * @param string $type The type of renderer
- * @param string $name The name of the element to render
- * @param array $attribs Associative array of remaining attributes.
- *
- * @return mixed|string The output of the renderer
- *
- * @since 1.7.0
- */
- public function getBuffer($type = null, $name = null, $attribs = array())
- {
- // If no type is specified, return the whole buffer
- if ($type === null)
- {
- return parent::$_buffer;
- }
-
- $title = $attribs['title'] ?? null;
-
- if (isset(parent::$_buffer[$type][$name][$title]))
- {
- return parent::$_buffer[$type][$name][$title];
- }
-
- $renderer = $this->loadRenderer($type);
-
- if ($this->_caching == true && $type === 'modules' && $name !== 'debug')
- {
- /** @var \Joomla\CMS\Document\Renderer\Html\ModulesRenderer $renderer */
- /** @var \Joomla\CMS\Cache\Controller\OutputController $cache */
- $cache = $this->getCacheControllerFactory()->createCacheController('output', ['defaultgroup' => 'com_modules']);
- $itemId = (int) CmsFactory::getApplication()->input->get('Itemid', 0, 'int');
-
- $hash = md5(
- serialize(
- [
- $name,
- $attribs,
- \get_class($renderer),
- $itemId,
- ]
- )
- );
- $cbuffer = $cache->get('cbuffer_' . $type);
-
- if (isset($cbuffer[$hash]))
- {
- return Cache::getWorkarounds($cbuffer[$hash], array('mergehead' => 1));
- }
-
- $options = array();
- $options['nopathway'] = 1;
- $options['nomodules'] = 1;
- $options['modulemode'] = 1;
-
- $this->setBuffer($renderer->render($name, $attribs, null), $type, $name);
- $data = parent::$_buffer[$type][$name][$title];
-
- $tmpdata = Cache::setWorkarounds($data, $options);
-
- $cbuffer[$hash] = $tmpdata;
-
- $cache->store($cbuffer, 'cbuffer_' . $type);
- }
- else
- {
- $this->setBuffer($renderer->render($name, $attribs, null), $type, $name, $title);
- }
-
- return parent::$_buffer[$type][$name][$title];
- }
-
- /**
- * Set the contents a document includes
- *
- * @param string $content The content to be set in the buffer.
- * @param array $options Array of optional elements.
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setBuffer($content, $options = array())
- {
- // The following code is just for backward compatibility.
- if (\func_num_args() > 1 && !\is_array($options))
- {
- $args = \func_get_args();
- $options = array();
- $options['type'] = $args[1];
- $options['name'] = $args[2] ?? null;
- $options['title'] = $args[3] ?? null;
- }
-
- parent::$_buffer[$options['type']][$options['name']][$options['title']] = $content;
-
- return $this;
- }
-
- /**
- * Parses the template and populates the buffer
- *
- * @param array $params Parameters for fetching the template
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function parse($params = array())
- {
- return $this->_fetchTemplate($params)->_parseTemplate();
- }
-
- /**
- * Outputs the template to the browser.
- *
- * @param boolean $caching If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($caching = false, $params = array())
- {
- $this->_caching = $caching;
-
- if (empty($this->_template))
- {
- $this->parse($params);
- }
-
- if (\array_key_exists('csp_nonce', $params) && $params['csp_nonce'] !== null)
- {
- $this->cspNonce = $params['csp_nonce'];
- }
-
- $data = $this->_renderTemplate();
- parent::render($caching, $params);
-
- return $data;
- }
-
- /**
- * Count the modules in the given position
- *
- * @param string $positionName The position to use
- * @param boolean $withContentOnly Count only a modules which actually has a content
- *
- * @return integer Number of modules found
- *
- * @since 1.7.0
- */
- public function countModules(string $positionName, bool $withContentOnly = false)
- {
- if ((isset(parent::$_buffer['modules'][$positionName])) && (parent::$_buffer['modules'][$positionName] === false))
- {
- return 0;
- }
-
- $modules = ModuleHelper::getModules($positionName);
-
- if (!$withContentOnly)
- {
- return \count($modules);
- }
-
- // Now we need to count only modules which actually have a content
- $result = 0;
- $renderer = $this->loadRenderer('module');
-
- foreach ($modules as $module)
- {
- if (empty($module->contentRendered))
- {
- $renderer->render($module, ['contentOnly' => true]);
- }
-
- if (trim($module->content) !== '')
- {
- $result++;
- }
- }
-
- return $result;
- }
-
- /**
- * Count the number of child menu items of the current active menu item
- *
- * @return integer Number of child menu items
- *
- * @since 1.7.0
- */
- public function countMenuChildren()
- {
- static $children;
-
- if (!isset($children))
- {
- $db = CmsFactory::getDbo();
- $app = CmsFactory::getApplication();
- $menu = $app->getMenu();
- $active = $menu->getActive();
- $children = 0;
-
- if ($active)
- {
- $query = $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName('#__menu'))
- ->where(
- [
- $db->quoteName('parent_id') . ' = :id',
- $db->quoteName('published') . ' = 1',
- ]
- )
- ->bind(':id', $active->id, ParameterType::INTEGER);
- $db->setQuery($query);
- $children = $db->loadResult();
- }
- }
-
- return $children;
- }
-
- /**
- * Load a template file
- *
- * @param string $directory The name of the template
- * @param string $filename The actual filename
- *
- * @return string The contents of the template
- *
- * @since 1.7.0
- */
- protected function _loadTemplate($directory, $filename)
- {
- $contents = '';
-
- // Check to see if we have a valid template file
- if (is_file($directory . '/' . $filename))
- {
- // Store the file path
- $this->_file = $directory . '/' . $filename;
-
- // Get the file content
- ob_start();
- require $directory . '/' . $filename;
- $contents = ob_get_contents();
- ob_end_clean();
- }
-
- return $contents;
- }
-
- /**
- * Fetch the template, and initialise the params
- *
- * @param array $params Parameters to determine the template
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- protected function _fetchTemplate($params = array())
- {
- // Check
- $directory = $params['directory'] ?? 'templates';
- $filter = InputFilter::getInstance();
- $template = $filter->clean($params['template'], 'cmd');
- $file = $filter->clean($params['file'], 'cmd');
- $inherits = $params['templateInherits'] ?? '';
- $baseDir = $directory . '/' . $template;
-
- if (!is_file($directory . '/' . $template . '/' . $file))
- {
- if ($inherits !== '' && is_file($directory . '/' . $inherits . '/' . $file))
- {
- $baseDir = $directory . '/' . $inherits;
- }
- else
- {
- $baseDir = $directory . '/system';
- $template = 'system';
-
- if ($file !== 'index.php' && !is_file($baseDir . '/' . $file))
- {
- $file = 'index.php';
- }
- }
- }
-
- // Load the language file for the template
- $lang = CmsFactory::getLanguage();
-
- // 1.5 or core then 1.6
- $lang->load('tpl_' . $template, JPATH_BASE)
- || ($inherits !== '' && $lang->load('tpl_' . $inherits, $directory . '/' . $inherits))
- || $lang->load('tpl_' . $template, $directory . '/' . $template);
-
- // Assign the variables
- $this->baseurl = Uri::base(true);
- $this->params = $params['params'] ?? new Registry;
- $this->template = $template;
-
- // Load
- $this->_template = $this->_loadTemplate($baseDir, $file);
-
- return $this;
- }
-
- /**
- * Parse a document template
- *
- * @return HtmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- protected function _parseTemplate()
- {
- $matches = array();
-
- if (preg_match_all('# #iU', $this->_template, $matches))
- {
- $messages = [];
- $template_tags_first = [];
- $template_tags_last = [];
-
- // Step through the jdocs in reverse order.
- for ($i = \count($matches[0]) - 1; $i >= 0; $i--)
- {
- $type = $matches[1][$i];
- $attribs = empty($matches[2][$i]) ? array() : Utility::parseAttributes($matches[2][$i]);
- $name = $attribs['name'] ?? null;
-
- // Separate buffers to be executed first and last
- if ($type === 'module' || $type === 'modules')
- {
- $template_tags_first[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs];
- }
- elseif ($type === 'message')
- {
- $messages = [$matches[0][$i] => ['type' => $type, 'name' => $name, 'attribs' => $attribs]];
- }
- else
- {
- $template_tags_last[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs];
- }
- }
-
- $this->_template_tags = $template_tags_first + $messages + array_reverse($template_tags_last);
- }
-
- return $this;
- }
-
- /**
- * Render pre-parsed template
- *
- * @return string rendered template
- *
- * @since 1.7.0
- */
- protected function _renderTemplate()
- {
- $replace = [];
- $with = [];
-
- foreach ($this->_template_tags as $jdoc => $args)
- {
- $replace[] = $jdoc;
- $with[] = $this->getBuffer($args['type'], $args['name'], $args['attribs']);
- }
-
- return str_replace($replace, $with, $this->_template);
- }
+ use CacheControllerFactoryAwareTrait;
+
+ /**
+ * Array of Header ` ` tags
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $_links = array();
+
+ /**
+ * Array of custom tags
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $_custom = array();
+
+ /**
+ * Name of the template
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $template = null;
+
+ /**
+ * Base url
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $baseurl = null;
+
+ /**
+ * Array of template parameters
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $params = null;
+
+ /**
+ * File name
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ public $_file = null;
+
+ /**
+ * Script nonce (string if set, null otherwise)
+ *
+ * @var string|null
+ * @since 4.0.0
+ */
+ public $cspNonce = null;
+
+ /**
+ * String holding parsed template
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $_template = '';
+
+ /**
+ * Array of parsed template JDoc tags
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $_template_tags = array();
+
+ /**
+ * Integer with caching setting
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ protected $_caching = null;
+
+ /**
+ * Set to true when the document should be output as HTML5
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ private $html5 = true;
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set document type
+ $this->_type = 'html';
+
+ // Set default mime type and document metadata (metadata syncs with mime type by default)
+ $this->setMimeEncoding('text/html');
+ }
+
+ /**
+ * Get the HTML document head data
+ *
+ * @return array The document head data in array form
+ *
+ * @since 1.7.0
+ */
+ public function getHeadData()
+ {
+ $data = array();
+ $data['title'] = $this->title;
+ $data['description'] = $this->description;
+ $data['link'] = $this->link;
+ $data['metaTags'] = $this->_metaTags;
+ $data['links'] = $this->_links;
+ $data['styleSheets'] = $this->_styleSheets;
+ $data['style'] = $this->_style;
+ $data['scripts'] = $this->_scripts;
+ $data['script'] = $this->_script;
+ $data['custom'] = $this->_custom;
+
+ // @deprecated 5.0 This property is for backwards compatibility. Pass text through script options in the future
+ $data['scriptText'] = Text::getScriptStrings();
+
+ $data['scriptOptions'] = $this->scriptOptions;
+
+ // Get Asset manager state
+ $wa = $this->getWebAssetManager();
+ $waState = $wa->getManagerState();
+
+ // Get asset objects and filter only manually added/enabled assets,
+ // Dependencies will be picked up from registry files
+ $waState['assets'] = [];
+
+ foreach ($waState['activeAssets'] as $assetType => $assetNames) {
+ foreach ($assetNames as $assetName => $assetState) {
+ $waState['assets'][$assetType][] = $wa->getAsset($assetType, $assetName);
+ }
+ }
+
+ // We have loaded asset objects, now can remove unused stuff
+ unset($waState['activeAssets']);
+
+ $data['assetManager'] = $waState;
+
+ return $data;
+ }
+
+ /**
+ * Reset the HTML document head data
+ *
+ * @param mixed $types type or types of the heads elements to reset
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 3.7.0
+ */
+ public function resetHeadData($types = null)
+ {
+ if (\is_null($types)) {
+ $this->title = '';
+ $this->description = '';
+ $this->link = '';
+ $this->_metaTags = array();
+ $this->_links = array();
+ $this->_styleSheets = array();
+ $this->_style = array();
+ $this->_scripts = array();
+ $this->_script = array();
+ $this->_custom = array();
+ $this->scriptOptions = array();
+ }
+
+ if (\is_array($types)) {
+ foreach ($types as $type) {
+ $this->resetHeadDatum($type);
+ }
+ }
+
+ if (\is_string($types)) {
+ $this->resetHeadDatum($types);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Reset a part the HTML document head data
+ *
+ * @param string $type type of the heads elements to reset
+ *
+ * @return void
+ *
+ * @since 3.7.0
+ */
+ private function resetHeadDatum($type)
+ {
+ switch ($type) {
+ case 'title':
+ case 'description':
+ case 'link':
+ $this->{$type} = '';
+ break;
+
+ case 'metaTags':
+ case 'links':
+ case 'styleSheets':
+ case 'style':
+ case 'scripts':
+ case 'script':
+ case 'custom':
+ $realType = '_' . $type;
+ $this->{$realType} = array();
+ break;
+
+ case 'scriptOptions':
+ $this->{$type} = array();
+ break;
+ }
+ }
+
+ /**
+ * Set the HTML document head data
+ *
+ * @param array $data The document head data in array form
+ *
+ * @return HtmlDocument|null instance of $this to allow chaining or null for empty input data
+ *
+ * @since 1.7.0
+ */
+ public function setHeadData($data)
+ {
+ if (empty($data) || !\is_array($data)) {
+ return null;
+ }
+
+ $this->title = $data['title'] ?? $this->title;
+ $this->description = $data['description'] ?? $this->description;
+ $this->link = $data['link'] ?? $this->link;
+ $this->_metaTags = $data['metaTags'] ?? $this->_metaTags;
+ $this->_links = $data['links'] ?? $this->_links;
+ $this->_styleSheets = $data['styleSheets'] ?? $this->_styleSheets;
+ $this->_style = $data['style'] ?? $this->_style;
+ $this->_scripts = $data['scripts'] ?? $this->_scripts;
+ $this->_script = $data['script'] ?? $this->_script;
+ $this->_custom = $data['custom'] ?? $this->_custom;
+ $this->scriptOptions = (isset($data['scriptOptions']) && !empty($data['scriptOptions'])) ? $data['scriptOptions'] : $this->scriptOptions;
+
+ // Restore asset manager state
+ $wa = $this->getWebAssetManager();
+
+ if (!empty($data['assetManager']['registryFiles'])) {
+ $waRegistry = $wa->getRegistry();
+
+ foreach ($data['assetManager']['registryFiles'] as $registryFile) {
+ $waRegistry->addRegistryFile($registryFile);
+ }
+ }
+
+ if (!empty($data['assetManager']['assets'])) {
+ foreach ($data['assetManager']['assets'] as $assetType => $assets) {
+ foreach ($assets as $asset) {
+ $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName());
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Merge the HTML document head data
+ *
+ * @param array $data The document head data in array form
+ *
+ * @return HtmlDocument|void instance of $this to allow chaining or void for empty input data
+ *
+ * @since 1.7.0
+ */
+ public function mergeHeadData($data)
+ {
+ if (empty($data) || !\is_array($data)) {
+ return;
+ }
+
+ $this->title = (isset($data['title']) && !empty($data['title']) && !stristr($this->title, $data['title']))
+ ? $this->title . $data['title']
+ : $this->title;
+ $this->description = (isset($data['description']) && !empty($data['description']) && !stristr($this->description, $data['description']))
+ ? $this->description . $data['description']
+ : $this->description;
+ $this->link = $data['link'] ?? $this->link;
+
+ if (isset($data['metaTags'])) {
+ foreach ($data['metaTags'] as $type1 => $data1) {
+ $booldog = $type1 === 'http-equiv';
+
+ foreach ($data1 as $name2 => $data2) {
+ $this->setMetaData($name2, $data2, $booldog);
+ }
+ }
+ }
+
+ $this->_links = (isset($data['links']) && !empty($data['links']) && \is_array($data['links']))
+ ? array_unique(array_merge($this->_links, $data['links']), SORT_REGULAR)
+ : $this->_links;
+ $this->_styleSheets = (isset($data['styleSheets']) && !empty($data['styleSheets']) && \is_array($data['styleSheets']))
+ ? array_merge($this->_styleSheets, $data['styleSheets'])
+ : $this->_styleSheets;
+
+ if (isset($data['style'])) {
+ foreach ($data['style'] as $type => $styles) {
+ foreach ($styles as $hash => $style) {
+ if (!isset($this->_style[strtolower($type)][$hash])) {
+ $this->addStyleDeclaration($style, $type);
+ }
+ }
+ }
+ }
+
+ $this->_scripts = (isset($data['scripts']) && !empty($data['scripts']) && \is_array($data['scripts']))
+ ? array_merge($this->_scripts, $data['scripts'])
+ : $this->_scripts;
+
+ if (isset($data['script'])) {
+ foreach ($data['script'] as $type => $scripts) {
+ foreach ($scripts as $hash => $script) {
+ if (!isset($this->_script[strtolower($type)][$hash])) {
+ $this->addScriptDeclaration($script, $type);
+ }
+ }
+ }
+ }
+
+ $this->_custom = (isset($data['custom']) && !empty($data['custom']) && \is_array($data['custom']))
+ ? array_unique(array_merge($this->_custom, $data['custom']))
+ : $this->_custom;
+
+ if (!empty($data['scriptOptions'])) {
+ foreach ($data['scriptOptions'] as $key => $scriptOptions) {
+ $this->addScriptOptions($key, $scriptOptions, true);
+ }
+ }
+
+ // Restore asset manager state
+ $wa = $this->getWebAssetManager();
+
+ if (!empty($data['assetManager']['registryFiles'])) {
+ $waRegistry = $wa->getRegistry();
+
+ foreach ($data['assetManager']['registryFiles'] as $registryFile) {
+ $waRegistry->addRegistryFile($registryFile);
+ }
+ }
+
+ if (!empty($data['assetManager']['assets'])) {
+ foreach ($data['assetManager']['assets'] as $assetType => $assets) {
+ foreach ($assets as $asset) {
+ $wa->registerAsset($assetType, $asset)->useAsset($assetType, $asset->getName());
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds ` ` tags to the head of the document
+ *
+ * $relType defaults to 'rel' as it is the most common relation type used.
+ * ('rev' refers to reverse relation, 'rel' indicates normal, forward relation.)
+ * Typical tag: ` `
+ *
+ * @param string $href The link that is being related.
+ * @param string $relation Relation of link.
+ * @param string $relType Relation type attribute. Either rel or rev (default: 'rel').
+ * @param array $attribs Associative array of remaining attributes.
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addHeadLink($href, $relation, $relType = 'rel', $attribs = array())
+ {
+ $this->_links[$href]['relation'] = $relation;
+ $this->_links[$href]['relType'] = $relType;
+ $this->_links[$href]['attribs'] = $attribs;
+
+ return $this;
+ }
+
+ /**
+ * Adds a shortcut icon (favicon)
+ *
+ * This adds a link to the icon shown in the favorites list or on
+ * the left of the url in the address bar. Some browsers display
+ * it on the tab, as well.
+ *
+ * @param string $href The link that is being related.
+ * @param string $type File type
+ * @param string $relation Relation of link
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addFavicon($href, $type = 'image/vnd.microsoft.icon', $relation = 'shortcut icon')
+ {
+ $href = str_replace('\\', '/', $href);
+ $this->addHeadLink($href, $relation, 'rel', array('type' => $type));
+
+ return $this;
+ }
+
+ /**
+ * Adds a custom HTML string to the head block
+ *
+ * @param string $html The HTML to add to the head
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addCustomTag($html)
+ {
+ $this->_custom[] = trim($html);
+
+ return $this;
+ }
+
+ /**
+ * Returns whether the document is set up to be output as HTML5
+ *
+ * @return boolean true when HTML5 is used
+ *
+ * @since 3.0.0
+ */
+ public function isHtml5()
+ {
+ return $this->html5;
+ }
+
+ /**
+ * Sets whether the document should be output as HTML5
+ *
+ * @param bool $state True when HTML5 should be output
+ *
+ * @return void
+ *
+ * @since 3.0.0
+ */
+ public function setHtml5($state)
+ {
+ if (\is_bool($state)) {
+ $this->html5 = $state;
+ }
+ }
+
+ /**
+ * Get the contents of a document include
+ *
+ * @param string $type The type of renderer
+ * @param string $name The name of the element to render
+ * @param array $attribs Associative array of remaining attributes.
+ *
+ * @return mixed|string The output of the renderer
+ *
+ * @since 1.7.0
+ */
+ public function getBuffer($type = null, $name = null, $attribs = array())
+ {
+ // If no type is specified, return the whole buffer
+ if ($type === null) {
+ return parent::$_buffer;
+ }
+
+ $title = $attribs['title'] ?? null;
+
+ if (isset(parent::$_buffer[$type][$name][$title])) {
+ return parent::$_buffer[$type][$name][$title];
+ }
+
+ $renderer = $this->loadRenderer($type);
+
+ if ($this->_caching == true && $type === 'modules' && $name !== 'debug') {
+ /** @var \Joomla\CMS\Document\Renderer\Html\ModulesRenderer $renderer */
+ /** @var \Joomla\CMS\Cache\Controller\OutputController $cache */
+ $cache = $this->getCacheControllerFactory()->createCacheController('output', ['defaultgroup' => 'com_modules']);
+ $itemId = (int) CmsFactory::getApplication()->input->get('Itemid', 0, 'int');
+
+ $hash = md5(
+ serialize(
+ [
+ $name,
+ $attribs,
+ \get_class($renderer),
+ $itemId,
+ ]
+ )
+ );
+ $cbuffer = $cache->get('cbuffer_' . $type);
+
+ if (isset($cbuffer[$hash])) {
+ return Cache::getWorkarounds($cbuffer[$hash], array('mergehead' => 1));
+ }
+
+ $options = array();
+ $options['nopathway'] = 1;
+ $options['nomodules'] = 1;
+ $options['modulemode'] = 1;
+
+ $this->setBuffer($renderer->render($name, $attribs, null), $type, $name);
+ $data = parent::$_buffer[$type][$name][$title];
+
+ $tmpdata = Cache::setWorkarounds($data, $options);
+
+ $cbuffer[$hash] = $tmpdata;
+
+ $cache->store($cbuffer, 'cbuffer_' . $type);
+ } else {
+ $this->setBuffer($renderer->render($name, $attribs, null), $type, $name, $title);
+ }
+
+ return parent::$_buffer[$type][$name][$title];
+ }
+
+ /**
+ * Set the contents a document includes
+ *
+ * @param string $content The content to be set in the buffer.
+ * @param array $options Array of optional elements.
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setBuffer($content, $options = array())
+ {
+ // The following code is just for backward compatibility.
+ if (\func_num_args() > 1 && !\is_array($options)) {
+ $args = \func_get_args();
+ $options = array();
+ $options['type'] = $args[1];
+ $options['name'] = $args[2] ?? null;
+ $options['title'] = $args[3] ?? null;
+ }
+
+ parent::$_buffer[$options['type']][$options['name']][$options['title']] = $content;
+
+ return $this;
+ }
+
+ /**
+ * Parses the template and populates the buffer
+ *
+ * @param array $params Parameters for fetching the template
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function parse($params = array())
+ {
+ return $this->_fetchTemplate($params)->_parseTemplate();
+ }
+
+ /**
+ * Outputs the template to the browser.
+ *
+ * @param boolean $caching If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($caching = false, $params = array())
+ {
+ $this->_caching = $caching;
+
+ if (empty($this->_template)) {
+ $this->parse($params);
+ }
+
+ if (\array_key_exists('csp_nonce', $params) && $params['csp_nonce'] !== null) {
+ $this->cspNonce = $params['csp_nonce'];
+ }
+
+ $data = $this->_renderTemplate();
+ parent::render($caching, $params);
+
+ return $data;
+ }
+
+ /**
+ * Count the modules in the given position
+ *
+ * @param string $positionName The position to use
+ * @param boolean $withContentOnly Count only a modules which actually has a content
+ *
+ * @return integer Number of modules found
+ *
+ * @since 1.7.0
+ */
+ public function countModules(string $positionName, bool $withContentOnly = false)
+ {
+ if ((isset(parent::$_buffer['modules'][$positionName])) && (parent::$_buffer['modules'][$positionName] === false)) {
+ return 0;
+ }
+
+ $modules = ModuleHelper::getModules($positionName);
+
+ if (!$withContentOnly) {
+ return \count($modules);
+ }
+
+ // Now we need to count only modules which actually have a content
+ $result = 0;
+ $renderer = $this->loadRenderer('module');
+
+ foreach ($modules as $module) {
+ if (empty($module->contentRendered)) {
+ $renderer->render($module, ['contentOnly' => true]);
+ }
+
+ if (trim($module->content) !== '') {
+ $result++;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Count the number of child menu items of the current active menu item
+ *
+ * @return integer Number of child menu items
+ *
+ * @since 1.7.0
+ */
+ public function countMenuChildren()
+ {
+ static $children;
+
+ if (!isset($children)) {
+ $db = CmsFactory::getDbo();
+ $app = CmsFactory::getApplication();
+ $menu = $app->getMenu();
+ $active = $menu->getActive();
+ $children = 0;
+
+ if ($active) {
+ $query = $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName('#__menu'))
+ ->where(
+ [
+ $db->quoteName('parent_id') . ' = :id',
+ $db->quoteName('published') . ' = 1',
+ ]
+ )
+ ->bind(':id', $active->id, ParameterType::INTEGER);
+ $db->setQuery($query);
+ $children = $db->loadResult();
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * Load a template file
+ *
+ * @param string $directory The name of the template
+ * @param string $filename The actual filename
+ *
+ * @return string The contents of the template
+ *
+ * @since 1.7.0
+ */
+ protected function _loadTemplate($directory, $filename)
+ {
+ $contents = '';
+
+ // Check to see if we have a valid template file
+ if (is_file($directory . '/' . $filename)) {
+ // Store the file path
+ $this->_file = $directory . '/' . $filename;
+
+ // Get the file content
+ ob_start();
+ require $directory . '/' . $filename;
+ $contents = ob_get_contents();
+ ob_end_clean();
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Fetch the template, and initialise the params
+ *
+ * @param array $params Parameters to determine the template
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ protected function _fetchTemplate($params = array())
+ {
+ // Check
+ $directory = $params['directory'] ?? 'templates';
+ $filter = InputFilter::getInstance();
+ $template = $filter->clean($params['template'], 'cmd');
+ $file = $filter->clean($params['file'], 'cmd');
+ $inherits = $params['templateInherits'] ?? '';
+ $baseDir = $directory . '/' . $template;
+
+ if (!is_file($directory . '/' . $template . '/' . $file)) {
+ if ($inherits !== '' && is_file($directory . '/' . $inherits . '/' . $file)) {
+ $baseDir = $directory . '/' . $inherits;
+ } else {
+ $baseDir = $directory . '/system';
+ $template = 'system';
+
+ if ($file !== 'index.php' && !is_file($baseDir . '/' . $file)) {
+ $file = 'index.php';
+ }
+ }
+ }
+
+ // Load the language file for the template
+ $lang = CmsFactory::getLanguage();
+
+ // 1.5 or core then 1.6
+ $lang->load('tpl_' . $template, JPATH_BASE)
+ || ($inherits !== '' && $lang->load('tpl_' . $inherits, $directory . '/' . $inherits))
+ || $lang->load('tpl_' . $template, $directory . '/' . $template);
+
+ // Assign the variables
+ $this->baseurl = Uri::base(true);
+ $this->params = $params['params'] ?? new Registry();
+ $this->template = $template;
+
+ // Load
+ $this->_template = $this->_loadTemplate($baseDir, $file);
+
+ return $this;
+ }
+
+ /**
+ * Parse a document template
+ *
+ * @return HtmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ protected function _parseTemplate()
+ {
+ $matches = array();
+
+ if (preg_match_all('# #iU', $this->_template, $matches)) {
+ $messages = [];
+ $template_tags_first = [];
+ $template_tags_last = [];
+
+ // Step through the jdocs in reverse order.
+ for ($i = \count($matches[0]) - 1; $i >= 0; $i--) {
+ $type = $matches[1][$i];
+ $attribs = empty($matches[2][$i]) ? array() : Utility::parseAttributes($matches[2][$i]);
+ $name = $attribs['name'] ?? null;
+
+ // Separate buffers to be executed first and last
+ if ($type === 'module' || $type === 'modules') {
+ $template_tags_first[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs];
+ } elseif ($type === 'message') {
+ $messages = [$matches[0][$i] => ['type' => $type, 'name' => $name, 'attribs' => $attribs]];
+ } else {
+ $template_tags_last[$matches[0][$i]] = ['type' => $type, 'name' => $name, 'attribs' => $attribs];
+ }
+ }
+
+ $this->_template_tags = $template_tags_first + $messages + array_reverse($template_tags_last);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render pre-parsed template
+ *
+ * @return string rendered template
+ *
+ * @since 1.7.0
+ */
+ protected function _renderTemplate()
+ {
+ $replace = [];
+ $with = [];
+
+ foreach ($this->_template_tags as $jdoc => $args) {
+ $replace[] = $jdoc;
+ $with[] = $this->getBuffer($args['type'], $args['name'], $args['attribs']);
+ }
+
+ return str_replace($replace, $with, $this->_template);
+ }
}
diff --git a/libraries/src/Document/ImageDocument.php b/libraries/src/Document/ImageDocument.php
index de3ea4216be9b..651b48655ec6b 100644
--- a/libraries/src/Document/ImageDocument.php
+++ b/libraries/src/Document/ImageDocument.php
@@ -1,4 +1,5 @@
_mime = 'image/png';
+ // Set mime type
+ $this->_mime = 'image/png';
- // Set document type
- $this->_type = 'image';
- }
+ // Set document type
+ $this->_type = 'image';
+ }
- /**
- * Render the document.
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 3.0.0
- */
- public function render($cache = false, $params = array())
- {
- // Get the image type
- $type = Factory::getApplication()->input->get('type', 'png');
+ /**
+ * Render the document.
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 3.0.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ // Get the image type
+ $type = Factory::getApplication()->input->get('type', 'png');
- switch ($type)
- {
- case 'jpg':
- case 'jpeg':
- $this->_mime = 'image/jpeg';
- break;
- case 'gif':
- $this->_mime = 'image/gif';
- break;
- case 'png':
- default:
- $this->_mime = 'image/png';
- break;
- }
+ switch ($type) {
+ case 'jpg':
+ case 'jpeg':
+ $this->_mime = 'image/jpeg';
+ break;
+ case 'gif':
+ $this->_mime = 'image/gif';
+ break;
+ case 'png':
+ default:
+ $this->_mime = 'image/png';
+ break;
+ }
- $this->_charset = null;
+ $this->_charset = null;
- parent::render($cache, $params);
+ parent::render($cache, $params);
- return $this->getBuffer();
- }
+ return $this->getBuffer();
+ }
}
diff --git a/libraries/src/Document/JsonDocument.php b/libraries/src/Document/JsonDocument.php
index fbfcd60f901da..5f4469d88a54e 100644
--- a/libraries/src/Document/JsonDocument.php
+++ b/libraries/src/Document/JsonDocument.php
@@ -1,4 +1,5 @@
_mime = 'text/plain';
- }
- else
- {
- $this->_mime = 'application/json';
- }
+ // Set mime type
+ if (
+ isset($_SERVER['HTTP_ACCEPT'])
+ && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') === false
+ && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false
+ ) {
+ // Internet Explorer < 10
+ $this->_mime = 'text/plain';
+ } else {
+ $this->_mime = 'application/json';
+ }
- // Set document type
- $this->_type = 'json';
- }
+ // Set document type
+ $this->_type = 'json';
+ }
- /**
- * Render the document.
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- /** @var \Joomla\CMS\Application\CMSApplication $app */
- $app = CmsFactory::getApplication();
+ /**
+ * Render the document.
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ /** @var \Joomla\CMS\Application\CMSApplication $app */
+ $app = CmsFactory::getApplication();
- $app->allowCache($cache);
+ $app->allowCache($cache);
- if ($this->_mime === 'application/json')
- {
- // Browser other than Internet Explorer < 10
- $app->setHeader('Content-Disposition', 'attachment; filename="' . $this->getName() . '.json"', true);
- }
+ if ($this->_mime === 'application/json') {
+ // Browser other than Internet Explorer < 10
+ $app->setHeader('Content-Disposition', 'attachment; filename="' . $this->getName() . '.json"', true);
+ }
- parent::render($cache, $params);
+ parent::render($cache, $params);
- return $this->getBuffer();
- }
+ return $this->getBuffer();
+ }
- /**
- * Returns the document name
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getName()
- {
- return $this->_name;
- }
+ /**
+ * Returns the document name
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
- /**
- * Sets the document name
- *
- * @param string $name Document name
- *
- * @return JsonDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setName($name = 'joomla')
- {
- $this->_name = $name;
+ /**
+ * Sets the document name
+ *
+ * @param string $name Document name
+ *
+ * @return JsonDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setName($name = 'joomla')
+ {
+ $this->_name = $name;
- return $this;
- }
+ return $this;
+ }
}
diff --git a/libraries/src/Document/JsonapiDocument.php b/libraries/src/Document/JsonapiDocument.php
index 4ed9703a1f975..f782007b8ee75 100644
--- a/libraries/src/Document/JsonapiDocument.php
+++ b/libraries/src/Document/JsonapiDocument.php
@@ -1,4 +1,5 @@
_mime = 'application/vnd.api+json';
- $this->_type = 'jsonapi';
-
- if (\array_key_exists('api_document', $options) && $options['api_document'] instanceof Document)
- {
- $this->document = $options['api_document'];
- }
- else
- {
- $this->document = new Document;
- }
- }
-
- /**
- * Set the data object.
- *
- * @param ElementInterface $element Element interface.
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setData(ElementInterface $element)
- {
- $this->document->setData($element);
-
- return $this;
- }
-
- /**
- * Set the errors array.
- *
- * @param array $errors Error array.
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setErrors($errors)
- {
- $this->document->setErrors($errors);
-
- return $this;
- }
-
- /**
- * Set the JSON-API array.
- *
- * @param array $jsonapi JSON-API array.
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setJsonapi($jsonapi)
- {
- $this->document->setJsonapi($jsonapi);
-
- return $this;
- }
-
- /**
- * Map everything to arrays.
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function toArray()
- {
- return $this->document->toArray();
- }
-
- /**
- * Map to string.
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function __toString()
- {
- return json_encode($this->toArray());
- }
-
- /**
- * Outputs the document.
- *
- * @param boolean $cache If true, cache the output.
- * @param array $params Associative array of attributes.
- *
- * @return string The rendered data.
- *
- * @since 4.0.0
- */
- public function render($cache = false, $params = array())
- {
- $app = Factory::getApplication();
-
- if ($mdate = $this->getModifiedDate())
- {
- $app->modifiedDate = $mdate;
- }
-
- $app->mimeType = $this->_mime;
- $app->charSet = $this->_charset;
-
- return json_encode($this->document);
- }
-
- /**
- * Serialize for JSON usage.
- *
- * @return array
- *
- * @since 4.0.0
- */
- #[\ReturnTypeWillChange]
- public function jsonSerialize()
- {
- return $this->toArray();
- }
-
- /**
- * Add a link to the output.
- *
- * @param string $key The name of the link
- * @param string $value The link
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function addLink($key, $value)
- {
- $this->document->addLink($key, $value);
-
- return $this;
- }
-
- /**
- * Add a link to the output.
- *
- * @param string $key The name of the metadata key
- * @param string $value The value
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function addMeta($key, $value)
- {
- $this->document->addMeta($key, $value);
-
- return $this;
- }
+ /**
+ * The JsonApi Document object.
+ *
+ * @var Document
+ * @since 4.0.0
+ */
+ protected $document;
+
+ /**
+ * Class constructor.
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 4.0.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set mime type to JSON-API
+ $this->_mime = 'application/vnd.api+json';
+ $this->_type = 'jsonapi';
+
+ if (\array_key_exists('api_document', $options) && $options['api_document'] instanceof Document) {
+ $this->document = $options['api_document'];
+ } else {
+ $this->document = new Document();
+ }
+ }
+
+ /**
+ * Set the data object.
+ *
+ * @param ElementInterface $element Element interface.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setData(ElementInterface $element)
+ {
+ $this->document->setData($element);
+
+ return $this;
+ }
+
+ /**
+ * Set the errors array.
+ *
+ * @param array $errors Error array.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setErrors($errors)
+ {
+ $this->document->setErrors($errors);
+
+ return $this;
+ }
+
+ /**
+ * Set the JSON-API array.
+ *
+ * @param array $jsonapi JSON-API array.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setJsonapi($jsonapi)
+ {
+ $this->document->setJsonapi($jsonapi);
+
+ return $this;
+ }
+
+ /**
+ * Map everything to arrays.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function toArray()
+ {
+ return $this->document->toArray();
+ }
+
+ /**
+ * Map to string.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function __toString()
+ {
+ return json_encode($this->toArray());
+ }
+
+ /**
+ * Outputs the document.
+ *
+ * @param boolean $cache If true, cache the output.
+ * @param array $params Associative array of attributes.
+ *
+ * @return string The rendered data.
+ *
+ * @since 4.0.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ $app = Factory::getApplication();
+
+ if ($mdate = $this->getModifiedDate()) {
+ $app->modifiedDate = $mdate;
+ }
+
+ $app->mimeType = $this->_mime;
+ $app->charSet = $this->_charset;
+
+ return json_encode($this->document);
+ }
+
+ /**
+ * Serialize for JSON usage.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->toArray();
+ }
+
+ /**
+ * Add a link to the output.
+ *
+ * @param string $key The name of the link
+ * @param string $value The link
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function addLink($key, $value)
+ {
+ $this->document->addLink($key, $value);
+
+ return $this;
+ }
+
+ /**
+ * Add a link to the output.
+ *
+ * @param string $key The name of the metadata key
+ * @param string $value The value
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function addMeta($key, $value)
+ {
+ $this->document->addMeta($key, $value);
+
+ return $this;
+ }
}
diff --git a/libraries/src/Document/Opensearch/OpensearchImage.php b/libraries/src/Document/Opensearch/OpensearchImage.php
index c3d58d91f27c1..8acb2b0680f7e 100644
--- a/libraries/src/Document/Opensearch/OpensearchImage.php
+++ b/libraries/src/Document/Opensearch/OpensearchImage.php
@@ -1,4 +1,5 @@
_type = 'opensearch';
-
- // Set mime type
- $this->_mime = 'application/opensearchdescription+xml';
-
- // Add the URL for self updating
- $update = new OpensearchUrl;
- $update->type = 'application/opensearchdescription+xml';
- $update->rel = 'self';
- $update->template = Route::_(Uri::getInstance());
- $this->addUrl($update);
-
- // Add the favicon as the default image
- // Try to find a favicon by checking the template and root folder
- $app = Factory::getApplication();
- $dirs = array(JPATH_THEMES . '/' . $app->getTemplate(), JPATH_BASE);
-
- foreach ($dirs as $dir)
- {
- if (is_file($dir . '/favicon.ico'))
- {
- $path = str_replace(JPATH_BASE, '', $dir);
- $path = str_replace('\\', '/', $path);
- $favicon = new OpensearchImage;
-
- if ($path == '')
- {
- $favicon->data = Uri::base() . 'favicon.ico';
- }
- else
- {
- if ($path[0] == '/')
- {
- $path = substr($path, 1);
- }
-
- $favicon->data = Uri::base() . $path . '/favicon.ico';
- }
-
- $favicon->height = '16';
- $favicon->width = '16';
- $favicon->type = 'image/vnd.microsoft.icon';
-
- $this->addImage($favicon);
-
- break;
- }
- }
- }
-
- /**
- * Render the document
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- $xml = new \DOMDocument('1.0', 'utf-8');
-
- if (\defined('JDEBUG') && JDEBUG)
- {
- $xml->formatOutput = true;
- }
-
- // The Opensearch Namespace
- $osns = 'http://a9.com/-/spec/opensearch/1.1/';
-
- // Create the root element
- $elOs = $xml->createElementNS($osns, 'OpenSearchDescription');
-
- $elShortName = $xml->createElementNS($osns, 'ShortName');
- $elShortName->appendChild($xml->createTextNode(htmlspecialchars($this->_shortName)));
- $elOs->appendChild($elShortName);
-
- $elDescription = $xml->createElementNS($osns, 'Description');
- $elDescription->appendChild($xml->createTextNode(htmlspecialchars($this->description)));
- $elOs->appendChild($elDescription);
-
- // Always set the accepted input encoding to UTF-8
- $elInputEncoding = $xml->createElementNS($osns, 'InputEncoding');
- $elInputEncoding->appendChild($xml->createTextNode('UTF-8'));
- $elOs->appendChild($elInputEncoding);
-
- foreach ($this->_images as $image)
- {
- $elImage = $xml->createElementNS($osns, 'Image');
- $elImage->setAttribute('type', $image->type);
- $elImage->setAttribute('width', $image->width);
- $elImage->setAttribute('height', $image->height);
- $elImage->appendChild($xml->createTextNode(htmlspecialchars($image->data)));
- $elOs->appendChild($elImage);
- }
-
- foreach ($this->_urls as $url)
- {
- $elUrl = $xml->createElementNS($osns, 'Url');
- $elUrl->setAttribute('type', $url->type);
-
- // Results is the default value so we don't need to add it
- if ($url->rel !== 'results')
- {
- $elUrl->setAttribute('rel', $url->rel);
- }
-
- $elUrl->setAttribute('template', $url->template);
- $elOs->appendChild($elUrl);
- }
-
- $xml->appendChild($elOs);
- parent::render($cache, $params);
-
- return $xml->saveXML();
- }
-
- /**
- * Sets the short name
- *
- * @param string $name The name.
- *
- * @return OpensearchDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setShortName($name)
- {
- $this->_shortName = $name;
-
- return $this;
- }
-
- /**
- * Adds a URL to the Opensearch description.
- *
- * @param OpensearchUrl $url The url to add to the description.
- *
- * @return OpensearchDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addUrl(OpensearchUrl $url)
- {
- $this->_urls[] = $url;
-
- return $this;
- }
-
- /**
- * Adds an image to the Opensearch description.
- *
- * @param OpensearchImage $image The image to add to the description.
- *
- * @return OpensearchDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function addImage(OpensearchImage $image)
- {
- $this->_images[] = $image;
-
- return $this;
- }
+ /**
+ * ShortName element
+ *
+ * required
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ private $_shortName = '';
+
+ /**
+ * Images collection
+ *
+ * optional
+ *
+ * @var object
+ * @since 1.7.0
+ */
+ private $_images = array();
+
+ /**
+ * The url collection
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ private $_urls = array();
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set document type
+ $this->_type = 'opensearch';
+
+ // Set mime type
+ $this->_mime = 'application/opensearchdescription+xml';
+
+ // Add the URL for self updating
+ $update = new OpensearchUrl();
+ $update->type = 'application/opensearchdescription+xml';
+ $update->rel = 'self';
+ $update->template = Route::_(Uri::getInstance());
+ $this->addUrl($update);
+
+ // Add the favicon as the default image
+ // Try to find a favicon by checking the template and root folder
+ $app = Factory::getApplication();
+ $dirs = array(JPATH_THEMES . '/' . $app->getTemplate(), JPATH_BASE);
+
+ foreach ($dirs as $dir) {
+ if (is_file($dir . '/favicon.ico')) {
+ $path = str_replace(JPATH_BASE, '', $dir);
+ $path = str_replace('\\', '/', $path);
+ $favicon = new OpensearchImage();
+
+ if ($path == '') {
+ $favicon->data = Uri::base() . 'favicon.ico';
+ } else {
+ if ($path[0] == '/') {
+ $path = substr($path, 1);
+ }
+
+ $favicon->data = Uri::base() . $path . '/favicon.ico';
+ }
+
+ $favicon->height = '16';
+ $favicon->width = '16';
+ $favicon->type = 'image/vnd.microsoft.icon';
+
+ $this->addImage($favicon);
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Render the document
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ $xml = new \DOMDocument('1.0', 'utf-8');
+
+ if (\defined('JDEBUG') && JDEBUG) {
+ $xml->formatOutput = true;
+ }
+
+ // The Opensearch Namespace
+ $osns = 'http://a9.com/-/spec/opensearch/1.1/';
+
+ // Create the root element
+ $elOs = $xml->createElementNS($osns, 'OpenSearchDescription');
+
+ $elShortName = $xml->createElementNS($osns, 'ShortName');
+ $elShortName->appendChild($xml->createTextNode(htmlspecialchars($this->_shortName)));
+ $elOs->appendChild($elShortName);
+
+ $elDescription = $xml->createElementNS($osns, 'Description');
+ $elDescription->appendChild($xml->createTextNode(htmlspecialchars($this->description)));
+ $elOs->appendChild($elDescription);
+
+ // Always set the accepted input encoding to UTF-8
+ $elInputEncoding = $xml->createElementNS($osns, 'InputEncoding');
+ $elInputEncoding->appendChild($xml->createTextNode('UTF-8'));
+ $elOs->appendChild($elInputEncoding);
+
+ foreach ($this->_images as $image) {
+ $elImage = $xml->createElementNS($osns, 'Image');
+ $elImage->setAttribute('type', $image->type);
+ $elImage->setAttribute('width', $image->width);
+ $elImage->setAttribute('height', $image->height);
+ $elImage->appendChild($xml->createTextNode(htmlspecialchars($image->data)));
+ $elOs->appendChild($elImage);
+ }
+
+ foreach ($this->_urls as $url) {
+ $elUrl = $xml->createElementNS($osns, 'Url');
+ $elUrl->setAttribute('type', $url->type);
+
+ // Results is the default value so we don't need to add it
+ if ($url->rel !== 'results') {
+ $elUrl->setAttribute('rel', $url->rel);
+ }
+
+ $elUrl->setAttribute('template', $url->template);
+ $elOs->appendChild($elUrl);
+ }
+
+ $xml->appendChild($elOs);
+ parent::render($cache, $params);
+
+ return $xml->saveXML();
+ }
+
+ /**
+ * Sets the short name
+ *
+ * @param string $name The name.
+ *
+ * @return OpensearchDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setShortName($name)
+ {
+ $this->_shortName = $name;
+
+ return $this;
+ }
+
+ /**
+ * Adds a URL to the Opensearch description.
+ *
+ * @param OpensearchUrl $url The url to add to the description.
+ *
+ * @return OpensearchDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addUrl(OpensearchUrl $url)
+ {
+ $this->_urls[] = $url;
+
+ return $this;
+ }
+
+ /**
+ * Adds an image to the Opensearch description.
+ *
+ * @param OpensearchImage $image The image to add to the description.
+ *
+ * @return OpensearchDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function addImage(OpensearchImage $image)
+ {
+ $this->_images[] = $image;
+
+ return $this;
+ }
}
diff --git a/libraries/src/Document/PreloadManager.php b/libraries/src/Document/PreloadManager.php
index 1aae652996fa8..7a24f8481438c 100644
--- a/libraries/src/Document/PreloadManager.php
+++ b/libraries/src/Document/PreloadManager.php
@@ -1,4 +1,5 @@
linkProvider = $linkProvider ?: new GenericLinkProvider;
- }
-
- /**
- * Get the link provider
- *
- * @return EvolvableLinkProviderInterface
- *
- * @since 4.0.0
- */
- public function getLinkProvider(): EvolvableLinkProviderInterface
- {
- return $this->linkProvider;
- }
-
- /**
- * Set the link provider
- *
- * @param EvolvableLinkProviderInterface $linkProvider The link provider
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setLinkProvider(EvolvableLinkProviderInterface $linkProvider)
- {
- $this->linkProvider = $linkProvider;
-
- return $this;
- }
-
- /**
- * Preloads a resource.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('crossorigin' => 'use-credentials')")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function preload(string $uri, array $attributes = [])
- {
- $this->link($uri, 'preload', $attributes);
- }
-
- /**
- * Resolves a resource origin as early as possible.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dnsPrefetch(string $uri, array $attributes = [])
- {
- $this->link($uri, 'dns-prefetch', $attributes);
- }
-
- /**
- * Initiates an early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function preconnect(string $uri, array $attributes = [])
- {
- $this->link($uri, 'preconnect', $attributes);
- }
-
- /**
- * Indicates to the client that it should prefetch this resource.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function prefetch(string $uri, array $attributes = [])
- {
- $this->link($uri, 'prefetch', $attributes);
- }
-
- /**
- * Indicates to the client that it should prerender this resource.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function prerender(string $uri, array $attributes = [])
- {
- $this->link($uri, 'prerender', $attributes);
- }
-
- /**
- * Adds a "Link" HTTP header.
- *
- * @param string $uri The relation URI
- * @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- private function link(string $uri, string $rel, array $attributes = [])
- {
- $link = new Link($rel, $uri);
-
- foreach ($attributes as $key => $value)
- {
- $link = $link->withAttribute($key, $value);
- }
-
- $this->setLinkProvider($this->getLinkProvider()->withLink($link));
- }
+ /**
+ * The link provider
+ *
+ * @var EvolvableLinkProviderInterface
+ * @since 4.0.0
+ */
+ protected $linkProvider;
+
+ /**
+ * PreloadManager constructor
+ *
+ * @param EvolvableLinkProviderInterface $linkProvider The link provider
+ *
+ * @since 4.0.0
+ */
+ public function __construct(EvolvableLinkProviderInterface $linkProvider = null)
+ {
+ $this->linkProvider = $linkProvider ?: new GenericLinkProvider();
+ }
+
+ /**
+ * Get the link provider
+ *
+ * @return EvolvableLinkProviderInterface
+ *
+ * @since 4.0.0
+ */
+ public function getLinkProvider(): EvolvableLinkProviderInterface
+ {
+ return $this->linkProvider;
+ }
+
+ /**
+ * Set the link provider
+ *
+ * @param EvolvableLinkProviderInterface $linkProvider The link provider
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setLinkProvider(EvolvableLinkProviderInterface $linkProvider)
+ {
+ $this->linkProvider = $linkProvider;
+
+ return $this;
+ }
+
+ /**
+ * Preloads a resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('crossorigin' => 'use-credentials')")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function preload(string $uri, array $attributes = [])
+ {
+ $this->link($uri, 'preload', $attributes);
+ }
+
+ /**
+ * Resolves a resource origin as early as possible.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dnsPrefetch(string $uri, array $attributes = [])
+ {
+ $this->link($uri, 'dns-prefetch', $attributes);
+ }
+
+ /**
+ * Initiates an early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function preconnect(string $uri, array $attributes = [])
+ {
+ $this->link($uri, 'preconnect', $attributes);
+ }
+
+ /**
+ * Indicates to the client that it should prefetch this resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function prefetch(string $uri, array $attributes = [])
+ {
+ $this->link($uri, 'prefetch', $attributes);
+ }
+
+ /**
+ * Indicates to the client that it should prerender this resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function prerender(string $uri, array $attributes = [])
+ {
+ $this->link($uri, 'prerender', $attributes);
+ }
+
+ /**
+ * Adds a "Link" HTTP header.
+ *
+ * @param string $uri The relation URI
+ * @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ private function link(string $uri, string $rel, array $attributes = [])
+ {
+ $link = new Link($rel, $uri);
+
+ foreach ($attributes as $key => $value) {
+ $link = $link->withAttribute($key, $value);
+ }
+
+ $this->setLinkProvider($this->getLinkProvider()->withLink($link));
+ }
}
diff --git a/libraries/src/Document/PreloadManagerInterface.php b/libraries/src/Document/PreloadManagerInterface.php
index b0f8a05fe8ebd..9b6db2887829c 100644
--- a/libraries/src/Document/PreloadManagerInterface.php
+++ b/libraries/src/Document/PreloadManagerInterface.php
@@ -1,4 +1,5 @@
true)", "array('crossorigin' => 'use-credentials')")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function preload(string $uri, array $attributes = []);
+ /**
+ * Preloads a resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('crossorigin' => 'use-credentials')")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function preload(string $uri, array $attributes = []);
- /**
- * Resolves a resource origin as early as possible.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function dnsPrefetch(string $uri, array $attributes = []);
+ /**
+ * Resolves a resource origin as early as possible.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function dnsPrefetch(string $uri, array $attributes = []);
- /**
- * Initiates an early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function preconnect(string $uri, array $attributes = []);
+ /**
+ * Initiates an early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function preconnect(string $uri, array $attributes = []);
- /**
- * Indicates to the client that it should prefetch this resource.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function prefetch(string $uri, array $attributes = []);
+ /**
+ * Indicates to the client that it should prefetch this resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function prefetch(string $uri, array $attributes = []);
- /**
- * Indicates to the client that it should prerender this resource.
- *
- * @param string $uri A public path
- * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function prerender(string $uri, array $attributes = []);
+ /**
+ * Indicates to the client that it should prerender this resource.
+ *
+ * @param string $uri A public path
+ * @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function prerender(string $uri, array $attributes = []);
}
diff --git a/libraries/src/Document/RawDocument.php b/libraries/src/Document/RawDocument.php
index fe5a059fe7303..2f2d744876486 100644
--- a/libraries/src/Document/RawDocument.php
+++ b/libraries/src/Document/RawDocument.php
@@ -1,4 +1,5 @@
_mime = 'text/html';
+ // Set mime type
+ $this->_mime = 'text/html';
- // Set document type
- $this->_type = 'raw';
- }
+ // Set document type
+ $this->_type = 'raw';
+ }
- /**
- * Render the document.
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- parent::render($cache, $params);
+ /**
+ * Render the document.
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ parent::render($cache, $params);
- return $this->getBuffer();
- }
+ return $this->getBuffer();
+ }
}
diff --git a/libraries/src/Document/Renderer/Feed/AtomRenderer.php b/libraries/src/Document/Renderer/Feed/AtomRenderer.php
index 7c921809f5b80..40d81a165e624 100644
--- a/libraries/src/Document/Renderer/Feed/AtomRenderer.php
+++ b/libraries/src/Document/Renderer/Feed/AtomRenderer.php
@@ -1,4 +1,5 @@
get('offset'));
- $now = Factory::getDate();
- $now->setTimezone($tz);
-
- $data = $this->_doc;
-
- $url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
- $syndicationURL = Route::_('&format=feed&type=atom');
-
- $title = $data->getTitle();
-
- if ($app->get('sitename_pagetitles', 0) == 1)
- {
- $title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
- }
- elseif ($app->get('sitename_pagetitles', 0) == 2)
- {
- $title = Text::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
- }
-
- $feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
-
- $feed = "getLanguage() != '')
- {
- $feed .= " xml:lang=\"" . $data->getLanguage() . "\"";
- }
-
- $feed .= ">\n";
- $feed .= " " . $feed_title . " \n";
- $feed .= " " . htmlspecialchars($data->getDescription(), ENT_COMPAT, 'UTF-8') . " \n";
-
- if (!empty($data->category))
- {
- if (\is_array($data->category))
- {
- foreach ($data->category as $cat)
- {
- $feed .= " \n";
- }
- }
- else
- {
- $feed .= " category, ENT_COMPAT, 'UTF-8') . "\" />\n";
- }
- }
-
- $feed .= " \n";
- $feed .= " " . str_replace(' ', '%20', $data->getBase()) . " \n";
- $feed .= " " . htmlspecialchars($now->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
-
- if ($data->editor != '')
- {
- $feed .= " \n";
- $feed .= " " . $data->editor . " \n";
-
- if ($data->editorEmail != '')
- {
- $feed .= " " . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- $feed .= " \n";
- }
-
- $versionHtmlEscaped = '';
-
- if ($app->get('MetaVersion', 0))
- {
- $minorVersion = Version::MAJOR_VERSION . '.' . Version::MINOR_VERSION;
- $versionHtmlEscaped = ' version="' . htmlspecialchars($minorVersion, ENT_COMPAT, 'UTF-8') . '"';
- }
-
- $feed .= " " . $data->getGenerator() . " \n";
- $feed .= " \n";
-
- for ($i = 0, $count = \count($data->items); $i < $count; $i++)
- {
- $itemlink = $data->items[$i]->link;
-
- if (preg_match('/[\x80-\xFF]/', $itemlink))
- {
- $itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
- }
-
- $feed .= " \n";
- $feed .= " " . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " \n";
-
- if ($data->items[$i]->date == '')
- {
- $data->items[$i]->date = $now->toUnix();
- }
-
- $itemDate = Factory::getDate($data->items[$i]->date);
- $itemDate->setTimezone($tz);
- $feed .= " " . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " " . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
-
- if (empty($data->items[$i]->guid))
- {
- $itemGuid = str_replace(' ', '%20', $url . $itemlink);
- }
- else
- {
- $itemGuid = htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8');
- }
-
- $feed .= " " . $itemGuid . " \n";
-
- if ($data->items[$i]->author != '')
- {
- $feed .= " \n";
- $feed .= " " . htmlspecialchars($data->items[$i]->author, ENT_COMPAT, 'UTF-8') . " \n";
-
- if (!empty($data->items[$i]->authorEmail))
- {
- $feed .= " " . htmlspecialchars($data->items[$i]->authorEmail, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- $feed .= " \n";
- }
-
- if (!empty($data->items[$i]->description))
- {
- $feed .= " " . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " " . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if (!empty($data->items[$i]->category))
- {
- if (\is_array($data->items[$i]->category))
- {
- foreach ($data->items[$i]->category as $cat)
- {
- $feed .= " \n";
- }
- }
- else
- {
- $feed .= " items[$i]->category, ENT_COMPAT, 'UTF-8') . "\" />\n";
- }
- }
-
- if ($data->items[$i]->enclosure != null)
- {
- $feed .= " items[$i]->enclosure->url . "\" type=\""
- . $data->items[$i]->enclosure->type . "\" length=\"" . $data->items[$i]->enclosure->length . "\"/>\n";
- }
-
- $feed .= " \n";
- }
-
- $feed .= " \n";
-
- return $feed;
- }
+ /**
+ * Document mime type
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $_mime = 'application/atom+xml';
+
+ /**
+ * Render the feed.
+ *
+ * @param string $name The name of the element to render
+ * @param array $params Array of values
+ * @param string $content Override the output of the renderer
+ *
+ * @return string The output of the script
+ *
+ * @see DocumentRenderer::render()
+ * @since 3.5
+ */
+ public function render($name = '', $params = null, $content = null)
+ {
+ $app = Factory::getApplication();
+
+ // Gets and sets timezone offset from site configuration
+ $tz = new \DateTimeZone($app->get('offset'));
+ $now = Factory::getDate();
+ $now->setTimezone($tz);
+
+ $data = $this->_doc;
+
+ $url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
+ $syndicationURL = Route::_('&format=feed&type=atom');
+
+ $title = $data->getTitle();
+
+ if ($app->get('sitename_pagetitles', 0) == 1) {
+ $title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
+ } elseif ($app->get('sitename_pagetitles', 0) == 2) {
+ $title = Text::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
+ }
+
+ $feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
+
+ $feed = "getLanguage() != '') {
+ $feed .= " xml:lang=\"" . $data->getLanguage() . "\"";
+ }
+
+ $feed .= ">\n";
+ $feed .= " " . $feed_title . " \n";
+ $feed .= " " . htmlspecialchars($data->getDescription(), ENT_COMPAT, 'UTF-8') . " \n";
+
+ if (!empty($data->category)) {
+ if (\is_array($data->category)) {
+ foreach ($data->category as $cat) {
+ $feed .= " \n";
+ }
+ } else {
+ $feed .= " category, ENT_COMPAT, 'UTF-8') . "\" />\n";
+ }
+ }
+
+ $feed .= " \n";
+ $feed .= " " . str_replace(' ', '%20', $data->getBase()) . " \n";
+ $feed .= " " . htmlspecialchars($now->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
+
+ if ($data->editor != '') {
+ $feed .= " \n";
+ $feed .= " " . $data->editor . " \n";
+
+ if ($data->editorEmail != '') {
+ $feed .= " " . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ $feed .= " \n";
+ }
+
+ $versionHtmlEscaped = '';
+
+ if ($app->get('MetaVersion', 0)) {
+ $minorVersion = Version::MAJOR_VERSION . '.' . Version::MINOR_VERSION;
+ $versionHtmlEscaped = ' version="' . htmlspecialchars($minorVersion, ENT_COMPAT, 'UTF-8') . '"';
+ }
+
+ $feed .= " " . $data->getGenerator() . " \n";
+ $feed .= " \n";
+
+ for ($i = 0, $count = \count($data->items); $i < $count; $i++) {
+ $itemlink = $data->items[$i]->link;
+
+ if (preg_match('/[\x80-\xFF]/', $itemlink)) {
+ $itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
+ }
+
+ $feed .= " \n";
+ $feed .= " " . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " \n";
+
+ if ($data->items[$i]->date == '') {
+ $data->items[$i]->date = $now->toUnix();
+ }
+
+ $itemDate = Factory::getDate($data->items[$i]->date);
+ $itemDate->setTimezone($tz);
+ $feed .= " " . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " " . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . " \n";
+
+ if (empty($data->items[$i]->guid)) {
+ $itemGuid = str_replace(' ', '%20', $url . $itemlink);
+ } else {
+ $itemGuid = htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8');
+ }
+
+ $feed .= " " . $itemGuid . " \n";
+
+ if ($data->items[$i]->author != '') {
+ $feed .= " \n";
+ $feed .= " " . htmlspecialchars($data->items[$i]->author, ENT_COMPAT, 'UTF-8') . " \n";
+
+ if (!empty($data->items[$i]->authorEmail)) {
+ $feed .= " " . htmlspecialchars($data->items[$i]->authorEmail, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ $feed .= " \n";
+ }
+
+ if (!empty($data->items[$i]->description)) {
+ $feed .= " " . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " " . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if (!empty($data->items[$i]->category)) {
+ if (\is_array($data->items[$i]->category)) {
+ foreach ($data->items[$i]->category as $cat) {
+ $feed .= " \n";
+ }
+ } else {
+ $feed .= " items[$i]->category, ENT_COMPAT, 'UTF-8') . "\" />\n";
+ }
+ }
+
+ if ($data->items[$i]->enclosure != null) {
+ $feed .= " items[$i]->enclosure->url . "\" type=\""
+ . $data->items[$i]->enclosure->type . "\" length=\"" . $data->items[$i]->enclosure->length . "\"/>\n";
+ }
+
+ $feed .= " \n";
+ }
+
+ $feed .= " \n";
+
+ return $feed;
+ }
}
diff --git a/libraries/src/Document/Renderer/Feed/RssRenderer.php b/libraries/src/Document/Renderer/Feed/RssRenderer.php
index baf35e3be8f50..0f3f00f537004 100644
--- a/libraries/src/Document/Renderer/Feed/RssRenderer.php
+++ b/libraries/src/Document/Renderer/Feed/RssRenderer.php
@@ -1,4 +1,5 @@
get('offset'));
-
- $data = $this->_doc;
-
- // If the last build date from the document isn't a Date object, create one
- if (!($data->lastBuildDate instanceof Date))
- {
- // Gets and sets timezone offset from site configuration
- $data->lastBuildDate = Factory::getDate();
- $data->lastBuildDate->setTimezone(new \DateTimeZone($app->get('offset')));
- }
-
- $url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
- $syndicationURL = Route::_('&format=feed&type=rss');
-
- $title = $data->getTitle();
-
- if ($app->get('sitename_pagetitles', 0) == 1)
- {
- $title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
- }
- elseif ($app->get('sitename_pagetitles', 0) == 2)
- {
- $title = Text::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
- }
-
- $feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
-
- $datalink = $data->getLink();
-
- if (preg_match('/[\x80-\xFF]/', $datalink))
- {
- $datalink = implode('/', array_map('rawurlencode', explode('/', $datalink)));
- }
-
- $feed = "\n";
- $feed .= " \n";
- $feed .= " " . $feed_title . " \n";
- $feed .= " getDescription() . "]]> \n";
- $feed .= " " . str_replace(' ', '%20', $url . $datalink) . "\n";
- $feed .= " " . htmlspecialchars($data->lastBuildDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " " . $data->getGenerator() . " \n";
- $feed .= " \n";
-
- if ($data->image != null)
- {
- $feed .= " \n";
- $feed .= " " . $data->image->url . " \n";
- $feed .= " " . htmlspecialchars($data->image->title, ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " " . str_replace(' ', '%20', $data->image->link) . "\n";
-
- if ($data->image->width != '')
- {
- $feed .= " " . $data->image->width . " \n";
- }
-
- if ($data->image->height != '')
- {
- $feed .= " " . $data->image->height . " \n";
- }
-
- if ($data->image->description != '')
- {
- $feed .= " image->description . "]]> \n";
- }
-
- $feed .= " \n";
- }
-
- if ($data->getLanguage() !== '')
- {
- $feed .= " " . $data->getLanguage() . " \n";
- }
-
- if ($data->copyright != '')
- {
- $feed .= " " . htmlspecialchars($data->copyright, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->editorEmail != '')
- {
- $feed .= " " . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . ' ('
- . htmlspecialchars($data->editor, ENT_COMPAT, 'UTF-8') . ") \n";
- }
-
- if ($data->webmaster != '')
- {
- $feed .= " " . htmlspecialchars($data->webmaster, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->pubDate != '')
- {
- $pubDate = Factory::getDate($data->pubDate);
- $pubDate->setTimezone($tz);
- $feed .= " " . htmlspecialchars($pubDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if (!empty($data->category))
- {
- if (\is_array($data->category))
- {
- foreach ($data->category as $cat)
- {
- $feed .= " " . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . " \n";
- }
- }
- else
- {
- $feed .= " " . htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') . " \n";
- }
- }
-
- if ($data->docs != '')
- {
- $feed .= " " . htmlspecialchars($data->docs, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->ttl != '')
- {
- $feed .= " " . htmlspecialchars($data->ttl, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->rating != '')
- {
- $feed .= " " . htmlspecialchars($data->rating, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->skipHours != '')
- {
- $feed .= " " . htmlspecialchars($data->skipHours, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->skipDays != '')
- {
- $feed .= " " . htmlspecialchars($data->skipDays, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- for ($i = 0, $count = \count($data->items); $i < $count; $i++)
- {
- $itemlink = $data->items[$i]->link;
-
- if (preg_match('/[\x80-\xFF]/', $itemlink))
- {
- $itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
- }
-
- if ((strpos($itemlink, 'http://') === false) && (strpos($itemlink, 'https://') === false))
- {
- $itemlink = str_replace(' ', '%20', $url . $itemlink);
- }
-
- $feed .= " - \n";
- $feed .= "
" . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . " \n";
- $feed .= " " . str_replace(' ', '%20', $itemlink) . "\n";
-
- if (empty($data->items[$i]->guid))
- {
- $feed .= " " . str_replace(' ', '%20', $itemlink) . " \n";
- }
- else
- {
- $feed .= " " . htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- $feed .= " _relToAbs($data->items[$i]->description) . "]]> \n";
-
- if ($data->items[$i]->authorEmail != '')
- {
- $feed .= ' '
- . htmlspecialchars($data->items[$i]->authorEmail . ' (' . $data->items[$i]->author . ')', ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- /*
- * @todo: On hold
- * if ($data->items[$i]->source!='')
- * {
- * $data.= " " . htmlspecialchars($data->items[$i]->source, ENT_COMPAT, 'UTF-8') . " \n";
- * }
- */
-
- if (empty($data->items[$i]->category) === false)
- {
- if (\is_array($data->items[$i]->category))
- {
- foreach ($data->items[$i]->category as $cat)
- {
- $feed .= " " . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . " \n";
- }
- }
- else
- {
- $feed .= " " . htmlspecialchars($data->items[$i]->category, ENT_COMPAT, 'UTF-8') . " \n";
- }
- }
-
- if ($data->items[$i]->comments != '')
- {
- $feed .= " " . htmlspecialchars($data->items[$i]->comments, ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->items[$i]->date != '')
- {
- $itemDate = Factory::getDate($data->items[$i]->date);
- $itemDate->setTimezone($tz);
- $feed .= " " . htmlspecialchars($itemDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
- }
-
- if ($data->items[$i]->enclosure != null)
- {
- $feed .= " items[$i]->enclosure->url;
- $feed .= "\" length=\"";
- $feed .= $data->items[$i]->enclosure->length;
- $feed .= "\" type=\"";
- $feed .= $data->items[$i]->enclosure->type;
- $feed .= "\"/>\n";
- }
-
- $feed .= " \n";
- }
-
- $feed .= " \n";
- $feed .= " \n";
-
- return $feed;
- }
+ /**
+ * Renderer mime type
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $_mime = 'application/rss+xml';
+
+ /**
+ * Render the feed.
+ *
+ * @param string $name The name of the element to render
+ * @param array $params Array of values
+ * @param string $content Override the output of the renderer
+ *
+ * @return string The output of the script
+ *
+ * @see DocumentRenderer::render()
+ * @since 3.5
+ */
+ public function render($name = '', $params = null, $content = null)
+ {
+ $app = Factory::getApplication();
+ $tz = new \DateTimeZone($app->get('offset'));
+
+ $data = $this->_doc;
+
+ // If the last build date from the document isn't a Date object, create one
+ if (!($data->lastBuildDate instanceof Date)) {
+ // Gets and sets timezone offset from site configuration
+ $data->lastBuildDate = Factory::getDate();
+ $data->lastBuildDate->setTimezone(new \DateTimeZone($app->get('offset')));
+ }
+
+ $url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
+ $syndicationURL = Route::_('&format=feed&type=rss');
+
+ $title = $data->getTitle();
+
+ if ($app->get('sitename_pagetitles', 0) == 1) {
+ $title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
+ } elseif ($app->get('sitename_pagetitles', 0) == 2) {
+ $title = Text::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
+ }
+
+ $feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
+
+ $datalink = $data->getLink();
+
+ if (preg_match('/[\x80-\xFF]/', $datalink)) {
+ $datalink = implode('/', array_map('rawurlencode', explode('/', $datalink)));
+ }
+
+ $feed = "\n";
+ $feed .= " \n";
+ $feed .= " " . $feed_title . " \n";
+ $feed .= " getDescription() . "]]> \n";
+ $feed .= " " . str_replace(' ', '%20', $url . $datalink) . "\n";
+ $feed .= " " . htmlspecialchars($data->lastBuildDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " " . $data->getGenerator() . " \n";
+ $feed .= " \n";
+
+ if ($data->image != null) {
+ $feed .= " \n";
+ $feed .= " " . $data->image->url . " \n";
+ $feed .= " " . htmlspecialchars($data->image->title, ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " " . str_replace(' ', '%20', $data->image->link) . "\n";
+
+ if ($data->image->width != '') {
+ $feed .= " " . $data->image->width . " \n";
+ }
+
+ if ($data->image->height != '') {
+ $feed .= " " . $data->image->height . " \n";
+ }
+
+ if ($data->image->description != '') {
+ $feed .= " image->description . "]]> \n";
+ }
+
+ $feed .= " \n";
+ }
+
+ if ($data->getLanguage() !== '') {
+ $feed .= " " . $data->getLanguage() . " \n";
+ }
+
+ if ($data->copyright != '') {
+ $feed .= " " . htmlspecialchars($data->copyright, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->editorEmail != '') {
+ $feed .= " " . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . ' ('
+ . htmlspecialchars($data->editor, ENT_COMPAT, 'UTF-8') . ") \n";
+ }
+
+ if ($data->webmaster != '') {
+ $feed .= " " . htmlspecialchars($data->webmaster, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->pubDate != '') {
+ $pubDate = Factory::getDate($data->pubDate);
+ $pubDate->setTimezone($tz);
+ $feed .= " " . htmlspecialchars($pubDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if (!empty($data->category)) {
+ if (\is_array($data->category)) {
+ foreach ($data->category as $cat) {
+ $feed .= " " . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+ } else {
+ $feed .= " " . htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+ }
+
+ if ($data->docs != '') {
+ $feed .= " " . htmlspecialchars($data->docs, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->ttl != '') {
+ $feed .= " " . htmlspecialchars($data->ttl, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->rating != '') {
+ $feed .= " " . htmlspecialchars($data->rating, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->skipHours != '') {
+ $feed .= " " . htmlspecialchars($data->skipHours, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->skipDays != '') {
+ $feed .= " " . htmlspecialchars($data->skipDays, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ for ($i = 0, $count = \count($data->items); $i < $count; $i++) {
+ $itemlink = $data->items[$i]->link;
+
+ if (preg_match('/[\x80-\xFF]/', $itemlink)) {
+ $itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
+ }
+
+ if ((strpos($itemlink, 'http://') === false) && (strpos($itemlink, 'https://') === false)) {
+ $itemlink = str_replace(' ', '%20', $url . $itemlink);
+ }
+
+ $feed .= " - \n";
+ $feed .= "
" . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . " \n";
+ $feed .= " " . str_replace(' ', '%20', $itemlink) . "\n";
+
+ if (empty($data->items[$i]->guid)) {
+ $feed .= " " . str_replace(' ', '%20', $itemlink) . " \n";
+ } else {
+ $feed .= " " . htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ $feed .= " _relToAbs($data->items[$i]->description) . "]]> \n";
+
+ if ($data->items[$i]->authorEmail != '') {
+ $feed .= ' '
+ . htmlspecialchars($data->items[$i]->authorEmail . ' (' . $data->items[$i]->author . ')', ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ /*
+ * @todo: On hold
+ * if ($data->items[$i]->source!='')
+ * {
+ * $data.= " " . htmlspecialchars($data->items[$i]->source, ENT_COMPAT, 'UTF-8') . " \n";
+ * }
+ */
+
+ if (empty($data->items[$i]->category) === false) {
+ if (\is_array($data->items[$i]->category)) {
+ foreach ($data->items[$i]->category as $cat) {
+ $feed .= " " . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+ } else {
+ $feed .= " " . htmlspecialchars($data->items[$i]->category, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+ }
+
+ if ($data->items[$i]->comments != '') {
+ $feed .= " " . htmlspecialchars($data->items[$i]->comments, ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->items[$i]->date != '') {
+ $itemDate = Factory::getDate($data->items[$i]->date);
+ $itemDate->setTimezone($tz);
+ $feed .= " " . htmlspecialchars($itemDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . " \n";
+ }
+
+ if ($data->items[$i]->enclosure != null) {
+ $feed .= " items[$i]->enclosure->url;
+ $feed .= "\" length=\"";
+ $feed .= $data->items[$i]->enclosure->length;
+ $feed .= "\" type=\"";
+ $feed .= $data->items[$i]->enclosure->type;
+ $feed .= "\"/>\n";
+ }
+
+ $feed .= " \n";
+ }
+
+ $feed .= " \n";
+ $feed .= " \n";
+
+ return $feed;
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/ComponentRenderer.php b/libraries/src/Document/Renderer/Html/ComponentRenderer.php
index c4a461b8f5180..5b48074fe283b 100644
--- a/libraries/src/Document/Renderer/Html/ComponentRenderer.php
+++ b/libraries/src/Document/Renderer/Html/ComponentRenderer.php
@@ -1,4 +1,5 @@
_doc->loadRenderer('metas')->render($head, $params, $content);
- $buffer .= $this->_doc->loadRenderer('styles')->render($head, $params, $content);
- $buffer .= $this->_doc->loadRenderer('scripts')->render($head, $params, $content);
+ /**
+ * Renders the document head and returns the results as a string
+ *
+ * @param string $head (unused)
+ * @param array $params Associative array of values
+ * @param string $content The script
+ *
+ * @return string The output of the script
+ *
+ * @since 3.5
+ */
+ public function render($head, $params = array(), $content = null)
+ {
+ $buffer = '';
+ $buffer .= $this->_doc->loadRenderer('metas')->render($head, $params, $content);
+ $buffer .= $this->_doc->loadRenderer('styles')->render($head, $params, $content);
+ $buffer .= $this->_doc->loadRenderer('scripts')->render($head, $params, $content);
- return $buffer;
- }
+ return $buffer;
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/MessageRenderer.php b/libraries/src/Document/Renderer/Html/MessageRenderer.php
index 4198c9be9d780..97c7d843cc493 100644
--- a/libraries/src/Document/Renderer/Html/MessageRenderer.php
+++ b/libraries/src/Document/Renderer/Html/MessageRenderer.php
@@ -1,4 +1,5 @@
getData();
- $displayData = array(
- 'msgList' => $msgList,
- 'name' => $name,
- 'params' => $params,
- 'content' => $content,
- );
+ /**
+ * Renders the error stack and returns the results as a string
+ *
+ * @param string $name Not used.
+ * @param array $params Associative array of values
+ * @param string $content Not used.
+ *
+ * @return string The output of the script
+ *
+ * @since 3.5
+ */
+ public function render($name, $params = array(), $content = null)
+ {
+ $msgList = $this->getData();
+ $displayData = array(
+ 'msgList' => $msgList,
+ 'name' => $name,
+ 'params' => $params,
+ 'content' => $content,
+ );
- $app = Factory::getApplication();
- $chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/message.php';
+ $app = Factory::getApplication();
+ $chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/message.php';
- if (is_file($chromePath))
- {
- include_once $chromePath;
- }
+ if (is_file($chromePath)) {
+ include_once $chromePath;
+ }
- if (\function_exists('renderMessage'))
- {
- @trigger_error(
- 'renderMessage() is deprecated. Override system message rendering with layouts instead.',
- E_USER_DEPRECATED
- );
+ if (\function_exists('renderMessage')) {
+ @trigger_error(
+ 'renderMessage() is deprecated. Override system message rendering with layouts instead.',
+ E_USER_DEPRECATED
+ );
- return renderMessage($msgList);
- }
+ return renderMessage($msgList);
+ }
- return LayoutHelper::render('joomla.system.message', $displayData);
- }
+ return LayoutHelper::render('joomla.system.message', $displayData);
+ }
- /**
- * Get and prepare system message data for output
- *
- * @return array An array contains system message
- *
- * @since 3.5
- */
- private function getData()
- {
- // Initialise variables.
- $lists = array();
+ /**
+ * Get and prepare system message data for output
+ *
+ * @return array An array contains system message
+ *
+ * @since 3.5
+ */
+ private function getData()
+ {
+ // Initialise variables.
+ $lists = array();
- // Get the message queue
- $messages = Factory::getApplication()->getMessageQueue();
+ // Get the message queue
+ $messages = Factory::getApplication()->getMessageQueue();
- // Build the sorted message list
- if (\is_array($messages) && !empty($messages))
- {
- foreach ($messages as $msg)
- {
- if (isset($msg['type']) && isset($msg['message']))
- {
- $lists[$msg['type']][] = $msg['message'];
- }
- }
- }
+ // Build the sorted message list
+ if (\is_array($messages) && !empty($messages)) {
+ foreach ($messages as $msg) {
+ if (isset($msg['type']) && isset($msg['message'])) {
+ $lists[$msg['type']][] = $msg['message'];
+ }
+ }
+ }
- return $lists;
- }
+ return $lists;
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/MetasRenderer.php b/libraries/src/Document/Renderer/Html/MetasRenderer.php
index 371437c0cde93..d2bdcd8dbc607 100644
--- a/libraries/src/Document/Renderer/Html/MetasRenderer.php
+++ b/libraries/src/Document/Renderer/Html/MetasRenderer.php
@@ -1,4 +1,5 @@
_doc->_metaTags['name']['tags']))
- {
- $tagsHelper = new TagsHelper;
- $this->_doc->_metaTags['name']['tags'] = implode(', ', $tagsHelper->getTagNames($this->_doc->_metaTags['name']['tags']));
- }
-
- /** @var \Joomla\CMS\Application\CMSApplication $app */
- $app = Factory::getApplication();
- $wa = $this->_doc->getWebAssetManager();
-
- // Check for AttachBehavior and web components
- foreach ($wa->getAssets('script', true) as $asset)
- {
- if ($asset instanceof WebAssetAttachBehaviorInterface)
- {
- $asset->onAttachCallback($this->_doc);
- }
- }
-
- // Trigger the onBeforeCompileHead event
- $app->triggerEvent('onBeforeCompileHead');
-
- // Add Script Options as inline asset
- $scriptOptions = $this->_doc->getScriptOptions();
-
- if ($scriptOptions)
- {
- $prettyPrint = (JDEBUG && \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : false);
- $jsonOptions = json_encode($scriptOptions, $prettyPrint);
- $jsonOptions = $jsonOptions ?: '{}';
-
- $wa->addInlineScript(
- $jsonOptions,
- ['name' => 'joomla.script.options', 'position' => 'before'],
- ['type' => 'application/json', 'class' => 'joomla-script-options new'],
- ['core']
- );
- }
-
- // Lock the AssetManager
- $wa->lock();
-
- // Get line endings
- $lnEnd = $this->_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
- $buffer = '';
-
- // Generate charset when using HTML5 (should happen first)
- if ($this->_doc->isHtml5())
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
-
- // Generate base tag (need to happen early)
- $base = $this->_doc->getBase();
-
- if (!empty($base))
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
-
- $noFavicon = true;
- $searchFor = 'image/vnd.microsoft.icon';
+ /**
+ * Renders the document metas and returns the results as a string
+ *
+ * @param string $head (unused)
+ * @param array $params Associative array of values
+ * @param string $content The script
+ *
+ * @return string The output of the script
+ *
+ * @since 4.0.0
+ */
+ public function render($head, $params = array(), $content = null)
+ {
+ // Convert the tagids to titles
+ if (isset($this->_doc->_metaTags['name']['tags'])) {
+ $tagsHelper = new TagsHelper();
+ $this->_doc->_metaTags['name']['tags'] = implode(', ', $tagsHelper->getTagNames($this->_doc->_metaTags['name']['tags']));
+ }
+
+ /** @var \Joomla\CMS\Application\CMSApplication $app */
+ $app = Factory::getApplication();
+ $wa = $this->_doc->getWebAssetManager();
+
+ // Check for AttachBehavior and web components
+ foreach ($wa->getAssets('script', true) as $asset) {
+ if ($asset instanceof WebAssetAttachBehaviorInterface) {
+ $asset->onAttachCallback($this->_doc);
+ }
+ }
+
+ // Trigger the onBeforeCompileHead event
+ $app->triggerEvent('onBeforeCompileHead');
+
+ // Add Script Options as inline asset
+ $scriptOptions = $this->_doc->getScriptOptions();
+
+ if ($scriptOptions) {
+ $prettyPrint = (JDEBUG && \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : false);
+ $jsonOptions = json_encode($scriptOptions, $prettyPrint);
+ $jsonOptions = $jsonOptions ?: '{}';
+
+ $wa->addInlineScript(
+ $jsonOptions,
+ ['name' => 'joomla.script.options', 'position' => 'before'],
+ ['type' => 'application/json', 'class' => 'joomla-script-options new'],
+ ['core']
+ );
+ }
+
+ // Lock the AssetManager
+ $wa->lock();
+
+ // Get line endings
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+ $buffer = '';
+
+ // Generate charset when using HTML5 (should happen first)
+ if ($this->_doc->isHtml5()) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ }
+
+ // Generate base tag (need to happen early)
+ $base = $this->_doc->getBase();
+
+ if (!empty($base)) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ }
+
+ $noFavicon = true;
+ $searchFor = 'image/vnd.microsoft.icon';
// @codingStandardsIgnoreStart
array_map(function($value) use(&$noFavicon, $searchFor) {
@@ -111,92 +104,80 @@ public function render($head, $params = array(), $content = null)
}, array_values((array)$this->_doc->_links));
// @codingStandardsIgnoreEnd
- if ($noFavicon)
- {
- $client = $app->isClient('administrator') === true ? 'administrator/' : 'site/';
- $template = $app->getTemplate(true);
-
- // Try to find a favicon by checking the template and root folder
- $icon = '/favicon.ico';
- $foldersToCheck = [
- JPATH_BASE,
- JPATH_ROOT . '/media/templates/' . $client . $template->template,
- JPATH_BASE . '/templates/' . $template->template,
- ];
-
- foreach ($foldersToCheck as $base => $dir)
- {
- if ($template->parent !== ''
- && $base === 1
- && !is_file(JPATH_ROOT . '/media/templates/' . $client . $template->template . $icon))
- {
- $dir = JPATH_ROOT . '/media/templates/' . $client . $template->parent;
- }
-
- if (is_file($dir . $icon))
- {
- $urlBase = in_array($base, [0, 2]) ? Uri::base(true) : Uri::root(true);
- $base = in_array($base, [0, 2]) ? JPATH_BASE : JPATH_ROOT;
- $path = str_replace($base, '', $dir);
- $path = str_replace('\\', '/', $path);
- $this->_doc->addFavicon($urlBase . $path . $icon);
- break;
- }
- }
- }
-
- // Generate META tags (needs to happen as early as possible in the head)
- foreach ($this->_doc->_metaTags as $type => $tag)
- {
- foreach ($tag as $name => $contents)
- {
- if ($type === 'http-equiv' && !($this->_doc->isHtml5() && $name === 'content-type'))
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
- elseif ($type !== 'http-equiv' && !empty($contents))
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
- }
- }
-
- // Don't add empty descriptions
- $documentDescription = $this->_doc->getDescription();
-
- if ($documentDescription)
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
-
- // Don't add empty generators
- $generator = $this->_doc->getGenerator();
-
- if ($generator)
- {
- $buffer .= $tab . ' ' . $lnEnd;
- }
-
- $buffer .= $tab . '' . htmlspecialchars($this->_doc->getTitle(), ENT_COMPAT, 'UTF-8') . ' ' . $lnEnd;
-
- // Generate link declarations
- foreach ($this->_doc->_links as $link => $linkAtrr)
- {
- $buffer .= $tab . ' isClient('administrator') === true ? 'administrator/' : 'site/';
+ $template = $app->getTemplate(true);
+
+ // Try to find a favicon by checking the template and root folder
+ $icon = '/favicon.ico';
+ $foldersToCheck = [
+ JPATH_BASE,
+ JPATH_ROOT . '/media/templates/' . $client . $template->template,
+ JPATH_BASE . '/templates/' . $template->template,
+ ];
+
+ foreach ($foldersToCheck as $base => $dir) {
+ if (
+ $template->parent !== ''
+ && $base === 1
+ && !is_file(JPATH_ROOT . '/media/templates/' . $client . $template->template . $icon)
+ ) {
+ $dir = JPATH_ROOT . '/media/templates/' . $client . $template->parent;
+ }
+
+ if (is_file($dir . $icon)) {
+ $urlBase = in_array($base, [0, 2]) ? Uri::base(true) : Uri::root(true);
+ $base = in_array($base, [0, 2]) ? JPATH_BASE : JPATH_ROOT;
+ $path = str_replace($base, '', $dir);
+ $path = str_replace('\\', '/', $path);
+ $this->_doc->addFavicon($urlBase . $path . $icon);
+ break;
+ }
+ }
+ }
+
+ // Generate META tags (needs to happen as early as possible in the head)
+ foreach ($this->_doc->_metaTags as $type => $tag) {
+ foreach ($tag as $name => $contents) {
+ if ($type === 'http-equiv' && !($this->_doc->isHtml5() && $name === 'content-type')) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ } elseif ($type !== 'http-equiv' && !empty($contents)) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ }
+ }
+ }
+
+ // Don't add empty descriptions
+ $documentDescription = $this->_doc->getDescription();
+
+ if ($documentDescription) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ }
+
+ // Don't add empty generators
+ $generator = $this->_doc->getGenerator();
+
+ if ($generator) {
+ $buffer .= $tab . ' ' . $lnEnd;
+ }
+
+ $buffer .= $tab . '' . htmlspecialchars($this->_doc->getTitle(), ENT_COMPAT, 'UTF-8') . ' ' . $lnEnd;
+
+ // Generate link declarations
+ foreach ($this->_doc->_links as $link => $linkAtrr) {
+ $buffer .= $tab . ' params = null;
- $module->module = $tmp;
- $module->id = 0;
- $module->user = 0;
- }
- }
+ /**
+ * If module isn't found in the database but data has been pushed in the buffer
+ * we want to render it
+ */
+ $tmp = $module;
+ $module = new \stdClass();
+ $module->params = null;
+ $module->module = $tmp;
+ $module->id = 0;
+ $module->user = 0;
+ }
+ }
- // Set the module content
- if (!\is_null($content))
- {
- $module->content = $content;
- }
+ // Set the module content
+ if (!\is_null($content)) {
+ $module->content = $content;
+ }
- // Get module parameters
- $params = new Registry($module->params);
+ // Get module parameters
+ $params = new Registry($module->params);
- // Use parameters from template
- if (isset($attribs['params']))
- {
- $template_params = new Registry(html_entity_decode($attribs['params'], ENT_COMPAT, 'UTF-8'));
- $params->merge($template_params);
- $module = clone $module;
- $module->params = (string) $params;
- }
+ // Use parameters from template
+ if (isset($attribs['params'])) {
+ $template_params = new Registry(html_entity_decode($attribs['params'], ENT_COMPAT, 'UTF-8'));
+ $params->merge($template_params);
+ $module = clone $module;
+ $module->params = (string) $params;
+ }
- // Set cachemode parameter or use JModuleHelper::moduleCache from within the module instead
- $cachemode = $params->get('cachemode', 'static');
+ // Set cachemode parameter or use JModuleHelper::moduleCache from within the module instead
+ $cachemode = $params->get('cachemode', 'static');
- if ($params->get('cache', 0) == 1 && Factory::getApplication()->get('caching') >= 1 && $cachemode !== 'id' && $cachemode !== 'safeuri')
- {
- // Default to itemid creating method and workarounds on
- $cacheparams = new \stdClass;
- $cacheparams->cachemode = $cachemode;
- $cacheparams->class = ModuleHelper::class;
- $cacheparams->method = 'renderModule';
- $cacheparams->methodparams = array($module, $attribs);
- $cacheparams->cachesuffix = $attribs['contentOnly'] ?? false;
+ if ($params->get('cache', 0) == 1 && Factory::getApplication()->get('caching') >= 1 && $cachemode !== 'id' && $cachemode !== 'safeuri') {
+ // Default to itemid creating method and workarounds on
+ $cacheparams = new \stdClass();
+ $cacheparams->cachemode = $cachemode;
+ $cacheparams->class = ModuleHelper::class;
+ $cacheparams->method = 'renderModule';
+ $cacheparams->methodparams = array($module, $attribs);
+ $cacheparams->cachesuffix = $attribs['contentOnly'] ?? false;
- // It need to be done here because the cache controller does not keep reference to the module object
- $module->content = ModuleHelper::moduleCache($module, $params, $cacheparams);
- $module->contentRendered = true;
+ // It need to be done here because the cache controller does not keep reference to the module object
+ $module->content = ModuleHelper::moduleCache($module, $params, $cacheparams);
+ $module->contentRendered = true;
- return $module->content;
- }
+ return $module->content;
+ }
- return ModuleHelper::renderModule($module, $attribs);
- }
+ return ModuleHelper::renderModule($module, $attribs);
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/ModulesRenderer.php b/libraries/src/Document/Renderer/Html/ModulesRenderer.php
index 70629c347e1fa..5b10ed36cd5d8 100644
--- a/libraries/src/Document/Renderer/Html/ModulesRenderer.php
+++ b/libraries/src/Document/Renderer/Html/ModulesRenderer.php
@@ -1,4 +1,5 @@
_doc->loadRenderer('module');
- $buffer = '';
-
- $app = Factory::getApplication();
- $user = Factory::getUser();
- $frontediting = ($app->isClient('site') && $app->get('frontediting', 1) && !$user->guest);
- $menusEditing = ($app->get('frontediting', 1) == 2) && $user->authorise('core.edit', 'com_menus');
-
- foreach (ModuleHelper::getModules($position) as $mod)
- {
- $moduleHtml = $renderer->render($mod, $params, $content);
-
- if ($frontediting && trim($moduleHtml) != '' && $user->authorise('module.edit.frontend', 'com_modules.module.' . $mod->id))
- {
- $displayData = array('moduleHtml' => &$moduleHtml, 'module' => $mod, 'position' => $position, 'menusediting' => $menusEditing);
- LayoutHelper::render('joomla.edit.frontediting_modules', $displayData);
- }
-
- $buffer .= $moduleHtml;
- }
-
- $app->triggerEvent('onAfterRenderModules', array(&$buffer, &$params));
-
- return $buffer;
- }
+ /**
+ * Renders multiple modules script and returns the results as a string
+ *
+ * @param string $position The position of the modules to render
+ * @param array $params Associative array of values
+ * @param string $content Module content
+ *
+ * @return string The output of the script
+ *
+ * @since 3.5
+ */
+ public function render($position, $params = array(), $content = null)
+ {
+ $renderer = $this->_doc->loadRenderer('module');
+ $buffer = '';
+
+ $app = Factory::getApplication();
+ $user = Factory::getUser();
+ $frontediting = ($app->isClient('site') && $app->get('frontediting', 1) && !$user->guest);
+ $menusEditing = ($app->get('frontediting', 1) == 2) && $user->authorise('core.edit', 'com_menus');
+
+ foreach (ModuleHelper::getModules($position) as $mod) {
+ $moduleHtml = $renderer->render($mod, $params, $content);
+
+ if ($frontediting && trim($moduleHtml) != '' && $user->authorise('module.edit.frontend', 'com_modules.module.' . $mod->id)) {
+ $displayData = array('moduleHtml' => &$moduleHtml, 'module' => $mod, 'position' => $position, 'menusediting' => $menusEditing);
+ LayoutHelper::render('joomla.edit.frontediting_modules', $displayData);
+ }
+
+ $buffer .= $moduleHtml;
+ }
+
+ $app->triggerEvent('onAfterRenderModules', array(&$buffer, &$params));
+
+ return $buffer;
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/ScriptsRenderer.php b/libraries/src/Document/Renderer/Html/ScriptsRenderer.php
index f647b7c7e59b4..151c35413bd1d 100644
--- a/libraries/src/Document/Renderer/Html/ScriptsRenderer.php
+++ b/libraries/src/Document/Renderer/Html/ScriptsRenderer.php
@@ -1,4 +1,5 @@
_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
- $buffer = '';
- $wam = $this->_doc->getWebAssetManager();
- $assets = $wam->getAssets('script', true);
-
- // Get a list of inline assets and their relation with regular assets
- $inlineAssets = $wam->filterOutInlineAssets($assets);
- $inlineRelation = $wam->getInlineRelation($inlineAssets);
-
- // Merge with existing scripts, for rendering
- $assets = array_merge(array_values($assets), $this->_doc->_scripts);
-
- // Generate script file links
- foreach ($assets as $key => $item)
- {
- // Check whether we have an Asset instance, or old array with attributes
- $asset = $item instanceof WebAssetItemInterface ? $item : null;
-
- // Add src attribute for non Asset item
- if (!$asset)
- {
- $item['src'] = $key;
- }
-
- // Check for inline content "before"
- if ($asset && !empty($inlineRelation[$asset->getName()]['before']))
- {
- foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore)
- {
- $buffer .= $this->renderInlineElement($itemBefore);
-
- // Remove this item from inline queue
- unset($inlineAssets[$itemBefore->getName()]);
- }
- }
-
- $buffer .= $this->renderElement($item);
-
- // Check for inline content "after"
- if ($asset && !empty($inlineRelation[$asset->getName()]['after']))
- {
- foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore)
- {
- $buffer .= $this->renderInlineElement($itemBefore);
-
- // Remove this item from inline queue
- unset($inlineAssets[$itemBefore->getName()]);
- }
- }
- }
-
- // Generate script declarations for assets
- foreach ($inlineAssets as $item)
- {
- $buffer .= $this->renderInlineElement($item);
- }
-
- // Generate script declarations for old scripts
- foreach ($this->_doc->_script as $type => $contents)
- {
- // Test for B.C. in case someone still store script declarations as single string
- if (\is_string($contents))
- {
- $contents = [$contents];
- }
-
- foreach ($contents as $content)
- {
- $buffer .= $this->renderInlineElement(
- [
- 'type' => $type,
- 'content' => $content,
- ]
- );
- }
- }
-
- // Output the custom tags - array_unique makes sure that we don't output the same tags twice
- foreach (array_unique($this->_doc->_custom) as $custom)
- {
- $buffer .= $tab . $custom . $lnEnd;
- }
-
- return ltrim($buffer, $tab);
- }
-
- /**
- * Renders the element
- *
- * @param WebAssetItemInterface|array $item The element
- *
- * @return string The resulting string
- *
- * @since 4.0.0
- */
- private function renderElement($item) : string
- {
- $buffer = '';
- $asset = $item instanceof WebAssetItemInterface ? $item : null;
- $src = $asset ? $asset->getUri() : ($item['src'] ?? '');
-
- // Make sure we have a src, and it not already rendered
- if (!$src || !empty($this->renderedSrc[$src]) || ($asset && $asset->getOption('webcomponent')))
- {
- return '';
- }
-
- $lnEnd = $this->_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
- $mediaVersion = $this->_doc->getMediaVersion();
-
- // Get the attributes and other options
- if ($asset)
- {
- $attribs = $asset->getAttributes();
- $version = $asset->getVersion();
- $conditional = $asset->getOption('conditional');
-
- // Add an asset info for debugging
- if (JDEBUG)
- {
- $attribs['data-asset-name'] = $asset->getName();
-
- if ($asset->getDependencies())
- {
- $attribs['data-asset-dependencies'] = implode(',', $asset->getDependencies());
- }
- }
- }
- else
- {
- $attribs = $item;
- $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : '';
- $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;
- }
-
- // Add "nonce" attribute if exist
- if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce))
- {
- $attribs['nonce'] = $this->_doc->cspNonce;
- }
-
- // To prevent double rendering
- $this->renderedSrc[$src] = true;
-
- // Check if script uses media version.
- if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto'))
- {
- $src .= '?' . ($version === 'auto' ? $mediaVersion : $version);
- }
-
- $buffer .= $tab;
-
- // This is for IE conditional statements support.
- if (!\is_null($conditional))
- {
- $buffer .= '';
- }
-
- $buffer .= $lnEnd;
-
- return $buffer;
- }
-
- /**
- * Renders the inline element
- *
- * @param WebAssetItemInterface|array $item The element
- *
- * @return string The resulting string
- *
- * @since 4.0.0
- */
- private function renderInlineElement($item) : string
- {
- $buffer = '';
- $lnEnd = $this->_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
-
- if ($item instanceof WebAssetItemInterface)
- {
- $attribs = $item->getAttributes();
- $content = $item->getOption('content');
- }
- else
- {
- $attribs = $item;
- $content = $item['content'] ?? '';
-
- unset($attribs['content']);
- }
-
- // Do not produce empty elements
- if (!$content)
- {
- return '';
- }
-
- // Add "nonce" attribute if exist
- if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce))
- {
- $attribs['nonce'] = $this->_doc->cspNonce;
- }
-
- $buffer .= $tab . '' . $lnEnd;
-
- return $buffer;
- }
-
- /**
- * Renders the element attributes
- *
- * @param array $attributes The element attributes
- *
- * @return string The attributes string
- *
- * @since 4.0.0
- */
- private function renderAttributes(array $attributes) : string
- {
- $buffer = '';
-
- $defaultJsMimes = array('text/javascript', 'application/javascript', 'text/x-javascript', 'application/x-javascript');
- $html5NoValueAttributes = array('defer', 'async', 'nomodule');
-
- foreach ($attributes as $attrib => $value)
- {
- // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
- if ($attrib === 'options' || $attrib === 'src')
- {
- continue;
- }
-
- // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
- if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultJsMimes))
- {
- continue;
- }
-
- // B/C: If defer and async is false or empty don't render the attribute. Also skip if value is bool:false.
- if (\in_array($attrib, array('defer', 'async')) && !$value || $value === false)
- {
- continue;
- }
-
- // NoValue attribute, if it have bool:true
- $isNoValueAttrib = $value === true || \in_array($attrib, $html5NoValueAttributes);
-
- // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
- if ($attrib === 'mime')
- {
- $attrib = 'type';
- }
- // NoValue attribute in non HTML5 should contain a value, set it equal to attribute name.
- elseif ($isNoValueAttrib)
- {
- $value = $attrib;
- }
-
- // Add attribute to script tag output.
- $buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');
-
- if (!($this->_doc->isHtml5() && $isNoValueAttrib))
- {
- // Json encode value if it's an array.
- $value = !is_scalar($value) ? json_encode($value) : $value;
-
- $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
- }
- }
-
- return $buffer;
- }
+ /**
+ * List of already rendered src
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ private $renderedSrc = [];
+
+ /**
+ * Renders the document script tags and returns the results as a string
+ *
+ * @param string $head (unused)
+ * @param array $params Associative array of values
+ * @param string $content The script
+ *
+ * @return string The output of the script
+ *
+ * @since 4.0.0
+ */
+ public function render($head, $params = array(), $content = null)
+ {
+ // Get line endings
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+ $buffer = '';
+ $wam = $this->_doc->getWebAssetManager();
+ $assets = $wam->getAssets('script', true);
+
+ // Get a list of inline assets and their relation with regular assets
+ $inlineAssets = $wam->filterOutInlineAssets($assets);
+ $inlineRelation = $wam->getInlineRelation($inlineAssets);
+
+ // Merge with existing scripts, for rendering
+ $assets = array_merge(array_values($assets), $this->_doc->_scripts);
+
+ // Generate script file links
+ foreach ($assets as $key => $item) {
+ // Check whether we have an Asset instance, or old array with attributes
+ $asset = $item instanceof WebAssetItemInterface ? $item : null;
+
+ // Add src attribute for non Asset item
+ if (!$asset) {
+ $item['src'] = $key;
+ }
+
+ // Check for inline content "before"
+ if ($asset && !empty($inlineRelation[$asset->getName()]['before'])) {
+ foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore) {
+ $buffer .= $this->renderInlineElement($itemBefore);
+
+ // Remove this item from inline queue
+ unset($inlineAssets[$itemBefore->getName()]);
+ }
+ }
+
+ $buffer .= $this->renderElement($item);
+
+ // Check for inline content "after"
+ if ($asset && !empty($inlineRelation[$asset->getName()]['after'])) {
+ foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore) {
+ $buffer .= $this->renderInlineElement($itemBefore);
+
+ // Remove this item from inline queue
+ unset($inlineAssets[$itemBefore->getName()]);
+ }
+ }
+ }
+
+ // Generate script declarations for assets
+ foreach ($inlineAssets as $item) {
+ $buffer .= $this->renderInlineElement($item);
+ }
+
+ // Generate script declarations for old scripts
+ foreach ($this->_doc->_script as $type => $contents) {
+ // Test for B.C. in case someone still store script declarations as single string
+ if (\is_string($contents)) {
+ $contents = [$contents];
+ }
+
+ foreach ($contents as $content) {
+ $buffer .= $this->renderInlineElement(
+ [
+ 'type' => $type,
+ 'content' => $content,
+ ]
+ );
+ }
+ }
+
+ // Output the custom tags - array_unique makes sure that we don't output the same tags twice
+ foreach (array_unique($this->_doc->_custom) as $custom) {
+ $buffer .= $tab . $custom . $lnEnd;
+ }
+
+ return ltrim($buffer, $tab);
+ }
+
+ /**
+ * Renders the element
+ *
+ * @param WebAssetItemInterface|array $item The element
+ *
+ * @return string The resulting string
+ *
+ * @since 4.0.0
+ */
+ private function renderElement($item): string
+ {
+ $buffer = '';
+ $asset = $item instanceof WebAssetItemInterface ? $item : null;
+ $src = $asset ? $asset->getUri() : ($item['src'] ?? '');
+
+ // Make sure we have a src, and it not already rendered
+ if (!$src || !empty($this->renderedSrc[$src]) || ($asset && $asset->getOption('webcomponent'))) {
+ return '';
+ }
+
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+ $mediaVersion = $this->_doc->getMediaVersion();
+
+ // Get the attributes and other options
+ if ($asset) {
+ $attribs = $asset->getAttributes();
+ $version = $asset->getVersion();
+ $conditional = $asset->getOption('conditional');
+
+ // Add an asset info for debugging
+ if (JDEBUG) {
+ $attribs['data-asset-name'] = $asset->getName();
+
+ if ($asset->getDependencies()) {
+ $attribs['data-asset-dependencies'] = implode(',', $asset->getDependencies());
+ }
+ }
+ } else {
+ $attribs = $item;
+ $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : '';
+ $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;
+ }
+
+ // Add "nonce" attribute if exist
+ if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce)) {
+ $attribs['nonce'] = $this->_doc->cspNonce;
+ }
+
+ // To prevent double rendering
+ $this->renderedSrc[$src] = true;
+
+ // Check if script uses media version.
+ if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto')) {
+ $src .= '?' . ($version === 'auto' ? $mediaVersion : $version);
+ }
+
+ $buffer .= $tab;
+
+ // This is for IE conditional statements support.
+ if (!\is_null($conditional)) {
+ $buffer .= '';
+ }
+
+ $buffer .= $lnEnd;
+
+ return $buffer;
+ }
+
+ /**
+ * Renders the inline element
+ *
+ * @param WebAssetItemInterface|array $item The element
+ *
+ * @return string The resulting string
+ *
+ * @since 4.0.0
+ */
+ private function renderInlineElement($item): string
+ {
+ $buffer = '';
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+
+ if ($item instanceof WebAssetItemInterface) {
+ $attribs = $item->getAttributes();
+ $content = $item->getOption('content');
+ } else {
+ $attribs = $item;
+ $content = $item['content'] ?? '';
+
+ unset($attribs['content']);
+ }
+
+ // Do not produce empty elements
+ if (!$content) {
+ return '';
+ }
+
+ // Add "nonce" attribute if exist
+ if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce)) {
+ $attribs['nonce'] = $this->_doc->cspNonce;
+ }
+
+ $buffer .= $tab . '' . $lnEnd;
+
+ return $buffer;
+ }
+
+ /**
+ * Renders the element attributes
+ *
+ * @param array $attributes The element attributes
+ *
+ * @return string The attributes string
+ *
+ * @since 4.0.0
+ */
+ private function renderAttributes(array $attributes): string
+ {
+ $buffer = '';
+
+ $defaultJsMimes = array('text/javascript', 'application/javascript', 'text/x-javascript', 'application/x-javascript');
+ $html5NoValueAttributes = array('defer', 'async', 'nomodule');
+
+ foreach ($attributes as $attrib => $value) {
+ // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
+ if ($attrib === 'options' || $attrib === 'src') {
+ continue;
+ }
+
+ // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
+ if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultJsMimes)) {
+ continue;
+ }
+
+ // B/C: If defer and async is false or empty don't render the attribute. Also skip if value is bool:false.
+ if (\in_array($attrib, array('defer', 'async')) && !$value || $value === false) {
+ continue;
+ }
+
+ // NoValue attribute, if it have bool:true
+ $isNoValueAttrib = $value === true || \in_array($attrib, $html5NoValueAttributes);
+
+ // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
+ if ($attrib === 'mime') {
+ $attrib = 'type';
+ } elseif ($isNoValueAttrib) {
+ // NoValue attribute in non HTML5 should contain a value, set it equal to attribute name.
+ $value = $attrib;
+ }
+
+ // Add attribute to script tag output.
+ $buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');
+
+ if (!($this->_doc->isHtml5() && $isNoValueAttrib)) {
+ // Json encode value if it's an array.
+ $value = !is_scalar($value) ? json_encode($value) : $value;
+
+ $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
+ }
+ }
+
+ return $buffer;
+ }
}
diff --git a/libraries/src/Document/Renderer/Html/StylesRenderer.php b/libraries/src/Document/Renderer/Html/StylesRenderer.php
index 47858b4476281..5c4c83c98c063 100644
--- a/libraries/src/Document/Renderer/Html/StylesRenderer.php
+++ b/libraries/src/Document/Renderer/Html/StylesRenderer.php
@@ -1,4 +1,5 @@
_doc->_getTab();
- $buffer = '';
- $wam = $this->_doc->getWebAssetManager();
- $assets = $wam->getAssets('style', true);
-
- // Get a list of inline assets and their relation with regular assets
- $inlineAssets = $wam->filterOutInlineAssets($assets);
- $inlineRelation = $wam->getInlineRelation($inlineAssets);
-
- // Merge with existing styleSheets, for rendering
- $assets = array_merge(array_values($assets), $this->_doc->_styleSheets);
-
- // Generate stylesheet links
- foreach ($assets as $key => $item)
- {
- $asset = $item instanceof WebAssetItemInterface ? $item : null;
-
- // Add href attribute for non Asset item
- if (!$asset)
- {
- $item['href'] = $key;
- }
-
- // Check for inline content "before"
- if ($asset && !empty($inlineRelation[$asset->getName()]['before']))
- {
- foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore)
- {
- $buffer .= $this->renderInlineElement($itemBefore);
-
- // Remove this item from inline queue
- unset($inlineAssets[$itemBefore->getName()]);
- }
- }
-
- $buffer .= $this->renderElement($item);
-
- // Check for inline content "after"
- if ($asset && !empty($inlineRelation[$asset->getName()]['after']))
- {
- foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore)
- {
- $buffer .= $this->renderInlineElement($itemBefore);
-
- // Remove this item from inline queue
- unset($inlineAssets[$itemBefore->getName()]);
- }
- }
- }
-
- // Generate script declarations for assets
- foreach ($inlineAssets as $item)
- {
- $buffer .= $this->renderInlineElement($item);
- }
-
- // Generate stylesheet declarations
- foreach ($this->_doc->_style as $type => $contents)
- {
- // Test for B.C. in case someone still store stylesheet declarations as single string
- if (\is_string($contents))
- {
- $contents = [$contents];
- }
-
- foreach ($contents as $content)
- {
- $buffer .= $this->renderInlineElement(
- [
- 'type' => $type,
- 'content' => $content,
- ]
- );
- }
- }
-
- return ltrim($buffer, $tab);
- }
-
- /**
- * Renders the element
- *
- * @param WebAssetItemInterface|array $item The element
- *
- * @return string The resulting string
- *
- * @since 4.0.0
- */
- private function renderElement($item) : string
- {
- $buffer = '';
- $asset = $item instanceof WebAssetItemInterface ? $item : null;
- $src = $asset ? $asset->getUri() : ($item['href'] ?? '');
-
- // Make sure we have a src, and it not already rendered
- if (!$src || !empty($this->renderedSrc[$src]))
- {
- return '';
- }
-
- $lnEnd = $this->_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
- $mediaVersion = $this->_doc->getMediaVersion();
-
- // Get the attributes and other options
- if ($asset)
- {
- $attribs = $asset->getAttributes();
- $version = $asset->getVersion();
- $conditional = $asset->getOption('conditional');
-
- // Add an asset info for debugging
- if (JDEBUG)
- {
- $attribs['data-asset-name'] = $asset->getName();
-
- if ($asset->getDependencies())
- {
- $attribs['data-asset-dependencies'] = implode(',', $asset->getDependencies());
- }
- }
- }
- else
- {
- $attribs = $item;
- $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : '';
- $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;
- }
-
- // Add "nonce" attribute if exist
- if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce))
- {
- $attribs['nonce'] = $this->_doc->cspNonce;
- }
-
- // To prevent double rendering
- $this->renderedSrc[$src] = true;
-
- // Check if script uses media version.
- if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto'))
- {
- $src .= '?' . ($version === 'auto' ? $mediaVersion : $version);
- }
-
- $buffer .= $tab;
-
- // This is for IE conditional statements support.
- if (!\is_null($conditional))
- {
- $buffer .= '';
- }
-
- $buffer .= $lnEnd;
-
- return $buffer;
- }
-
- /**
- * Renders the inline element
- *
- * @param WebAssetItemInterface|array $item The element
- *
- * @return string The resulting string
- *
- * @since 4.0.0
- */
- private function renderInlineElement($item) : string
- {
- $buffer = '';
- $lnEnd = $this->_doc->_getLineEnd();
- $tab = $this->_doc->_getTab();
-
- if ($item instanceof WebAssetItemInterface)
- {
- $attribs = $item->getAttributes();
- $content = $item->getOption('content');
- }
- else
- {
- $attribs = $item;
- $content = $item['content'] ?? '';
-
- unset($attribs['content']);
- }
-
- // Do not produce empty elements
- if (!$content)
- {
- return '';
- }
-
- // Add "nonce" attribute if exist
- if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce))
- {
- $attribs['nonce'] = $this->_doc->cspNonce;
- }
-
- $buffer .= $tab . '' . $lnEnd;
-
- return $buffer;
- }
-
- /**
- * Renders the element attributes
- *
- * @param array $attributes The element attributes
- *
- * @return string The attributes string
- *
- * @since 4.0.0
- */
- private function renderAttributes(array $attributes) : string
- {
- $buffer = '';
-
- $defaultCssMimes = array('text/css');
-
- foreach ($attributes as $attrib => $value)
- {
- // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
- if ($attrib === 'options' || $attrib === 'href')
- {
- continue;
- }
-
- // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
- if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultCssMimes))
- {
- continue;
- }
-
- // Skip the attribute if value is bool:false.
- if ($value === false)
- {
- continue;
- }
-
- // NoValue attribute, if it have bool:true
- $isNoValueAttrib = $value === true;
-
- // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
- if ($attrib === 'mime')
- {
- $attrib = 'type';
- }
- // NoValue attribute in non HTML5 should contain a value, set it equal to attribute name.
- elseif ($isNoValueAttrib)
- {
- $value = $attrib;
- }
-
- // Add attribute to script tag output.
- $buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');
-
- if (!($this->_doc->isHtml5() && $isNoValueAttrib))
- {
- // Json encode value if it's an array.
- $value = !is_scalar($value) ? json_encode($value) : $value;
-
- $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
- }
- }
-
- return $buffer;
- }
+ /**
+ * List of already rendered src
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ private $renderedSrc = [];
+
+ /**
+ * Renders the document stylesheets and style tags and returns the results as a string
+ *
+ * @param string $head (unused)
+ * @param array $params Associative array of values
+ * @param string $content The script
+ *
+ * @return string The output of the script
+ *
+ * @since 4.0.0
+ */
+ public function render($head, $params = array(), $content = null)
+ {
+ $tab = $this->_doc->_getTab();
+ $buffer = '';
+ $wam = $this->_doc->getWebAssetManager();
+ $assets = $wam->getAssets('style', true);
+
+ // Get a list of inline assets and their relation with regular assets
+ $inlineAssets = $wam->filterOutInlineAssets($assets);
+ $inlineRelation = $wam->getInlineRelation($inlineAssets);
+
+ // Merge with existing styleSheets, for rendering
+ $assets = array_merge(array_values($assets), $this->_doc->_styleSheets);
+
+ // Generate stylesheet links
+ foreach ($assets as $key => $item) {
+ $asset = $item instanceof WebAssetItemInterface ? $item : null;
+
+ // Add href attribute for non Asset item
+ if (!$asset) {
+ $item['href'] = $key;
+ }
+
+ // Check for inline content "before"
+ if ($asset && !empty($inlineRelation[$asset->getName()]['before'])) {
+ foreach ($inlineRelation[$asset->getName()]['before'] as $itemBefore) {
+ $buffer .= $this->renderInlineElement($itemBefore);
+
+ // Remove this item from inline queue
+ unset($inlineAssets[$itemBefore->getName()]);
+ }
+ }
+
+ $buffer .= $this->renderElement($item);
+
+ // Check for inline content "after"
+ if ($asset && !empty($inlineRelation[$asset->getName()]['after'])) {
+ foreach ($inlineRelation[$asset->getName()]['after'] as $itemBefore) {
+ $buffer .= $this->renderInlineElement($itemBefore);
+
+ // Remove this item from inline queue
+ unset($inlineAssets[$itemBefore->getName()]);
+ }
+ }
+ }
+
+ // Generate script declarations for assets
+ foreach ($inlineAssets as $item) {
+ $buffer .= $this->renderInlineElement($item);
+ }
+
+ // Generate stylesheet declarations
+ foreach ($this->_doc->_style as $type => $contents) {
+ // Test for B.C. in case someone still store stylesheet declarations as single string
+ if (\is_string($contents)) {
+ $contents = [$contents];
+ }
+
+ foreach ($contents as $content) {
+ $buffer .= $this->renderInlineElement(
+ [
+ 'type' => $type,
+ 'content' => $content,
+ ]
+ );
+ }
+ }
+
+ return ltrim($buffer, $tab);
+ }
+
+ /**
+ * Renders the element
+ *
+ * @param WebAssetItemInterface|array $item The element
+ *
+ * @return string The resulting string
+ *
+ * @since 4.0.0
+ */
+ private function renderElement($item): string
+ {
+ $buffer = '';
+ $asset = $item instanceof WebAssetItemInterface ? $item : null;
+ $src = $asset ? $asset->getUri() : ($item['href'] ?? '');
+
+ // Make sure we have a src, and it not already rendered
+ if (!$src || !empty($this->renderedSrc[$src])) {
+ return '';
+ }
+
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+ $mediaVersion = $this->_doc->getMediaVersion();
+
+ // Get the attributes and other options
+ if ($asset) {
+ $attribs = $asset->getAttributes();
+ $version = $asset->getVersion();
+ $conditional = $asset->getOption('conditional');
+
+ // Add an asset info for debugging
+ if (JDEBUG) {
+ $attribs['data-asset-name'] = $asset->getName();
+
+ if ($asset->getDependencies()) {
+ $attribs['data-asset-dependencies'] = implode(',', $asset->getDependencies());
+ }
+ }
+ } else {
+ $attribs = $item;
+ $version = isset($attribs['options']['version']) ? $attribs['options']['version'] : '';
+ $conditional = !empty($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;
+ }
+
+ // Add "nonce" attribute if exist
+ if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce)) {
+ $attribs['nonce'] = $this->_doc->cspNonce;
+ }
+
+ // To prevent double rendering
+ $this->renderedSrc[$src] = true;
+
+ // Check if script uses media version.
+ if ($version && strpos($src, '?') === false && ($mediaVersion || $version !== 'auto')) {
+ $src .= '?' . ($version === 'auto' ? $mediaVersion : $version);
+ }
+
+ $buffer .= $tab;
+
+ // This is for IE conditional statements support.
+ if (!\is_null($conditional)) {
+ $buffer .= '';
+ }
+
+ $buffer .= $lnEnd;
+
+ return $buffer;
+ }
+
+ /**
+ * Renders the inline element
+ *
+ * @param WebAssetItemInterface|array $item The element
+ *
+ * @return string The resulting string
+ *
+ * @since 4.0.0
+ */
+ private function renderInlineElement($item): string
+ {
+ $buffer = '';
+ $lnEnd = $this->_doc->_getLineEnd();
+ $tab = $this->_doc->_getTab();
+
+ if ($item instanceof WebAssetItemInterface) {
+ $attribs = $item->getAttributes();
+ $content = $item->getOption('content');
+ } else {
+ $attribs = $item;
+ $content = $item['content'] ?? '';
+
+ unset($attribs['content']);
+ }
+
+ // Do not produce empty elements
+ if (!$content) {
+ return '';
+ }
+
+ // Add "nonce" attribute if exist
+ if ($this->_doc->cspNonce && !is_null($this->_doc->cspNonce)) {
+ $attribs['nonce'] = $this->_doc->cspNonce;
+ }
+
+ $buffer .= $tab . '' . $lnEnd;
+
+ return $buffer;
+ }
+
+ /**
+ * Renders the element attributes
+ *
+ * @param array $attributes The element attributes
+ *
+ * @return string The attributes string
+ *
+ * @since 4.0.0
+ */
+ private function renderAttributes(array $attributes): string
+ {
+ $buffer = '';
+
+ $defaultCssMimes = array('text/css');
+
+ foreach ($attributes as $attrib => $value) {
+ // Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
+ if ($attrib === 'options' || $attrib === 'href') {
+ continue;
+ }
+
+ // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
+ if (\in_array($attrib, array('type', 'mime')) && $this->_doc->isHtml5() && \in_array($value, $defaultCssMimes)) {
+ continue;
+ }
+
+ // Skip the attribute if value is bool:false.
+ if ($value === false) {
+ continue;
+ }
+
+ // NoValue attribute, if it have bool:true
+ $isNoValueAttrib = $value === true;
+
+ // Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
+ if ($attrib === 'mime') {
+ $attrib = 'type';
+ } elseif ($isNoValueAttrib) {
+ // NoValue attribute in non HTML5 should contain a value, set it equal to attribute name.
+ $value = $attrib;
+ }
+
+ // Add attribute to script tag output.
+ $buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');
+
+ if (!($this->_doc->isHtml5() && $isNoValueAttrib)) {
+ // Json encode value if it's an array.
+ $value = !is_scalar($value) ? json_encode($value) : $value;
+
+ $buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
+ }
+ }
+
+ return $buffer;
+ }
}
diff --git a/libraries/src/Document/RendererInterface.php b/libraries/src/Document/RendererInterface.php
index 4add9b8983f3d..b25bab21c9be0 100644
--- a/libraries/src/Document/RendererInterface.php
+++ b/libraries/src/Document/RendererInterface.php
@@ -1,4 +1,5 @@
_mime = 'application/xml';
-
- // Set document type
- $this->_type = 'xml';
- }
-
- /**
- * Render the document.
- *
- * @param boolean $cache If true, cache the output
- * @param array $params Associative array of attributes
- *
- * @return string The rendered data
- *
- * @since 1.7.0
- */
- public function render($cache = false, $params = array())
- {
- parent::render($cache, $params);
-
- $disposition = $this->isDownload ? 'attachment' : 'inline';
-
- CmsFactory::getApplication()->setHeader('Content-disposition', $disposition . '; filename="' . $this->getName() . '.xml"', true);
-
- return $this->getBuffer();
- }
-
- /**
- * Returns the document name
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * Sets the document name
- *
- * @param string $name Document name
- *
- * @return XmlDocument instance of $this to allow chaining
- *
- * @since 1.7.0
- */
- public function setName($name = 'joomla')
- {
- $this->name = $name;
-
- return $this;
- }
-
- /**
- * Check if this document is intended for download
- *
- * @return string
- *
- * @since 3.9.0
- */
- public function isDownload()
- {
- return $this->isDownload;
- }
-
- /**
- * Sets the document's download state
- *
- * @param boolean $download If true, this document will be downloaded; if false, this document will be displayed inline
- *
- * @return XmlDocument instance of $this to allow chaining
- *
- * @since 3.9.0
- */
- public function setDownload($download = false)
- {
- $this->isDownload = $download;
-
- return $this;
- }
+ /**
+ * Document name
+ *
+ * @var string
+ * @since 3.0.0
+ */
+ protected $name = 'joomla';
+
+ /**
+ * Flag indicating the document should be downloaded (Content-Disposition = attachment) versus displayed inline
+ *
+ * @var boolean
+ * @since 3.9.0
+ */
+ protected $isDownload = false;
+
+ /**
+ * Class constructor
+ *
+ * @param array $options Associative array of options
+ *
+ * @since 1.7.0
+ */
+ public function __construct($options = array())
+ {
+ parent::__construct($options);
+
+ // Set mime type
+ $this->_mime = 'application/xml';
+
+ // Set document type
+ $this->_type = 'xml';
+ }
+
+ /**
+ * Render the document.
+ *
+ * @param boolean $cache If true, cache the output
+ * @param array $params Associative array of attributes
+ *
+ * @return string The rendered data
+ *
+ * @since 1.7.0
+ */
+ public function render($cache = false, $params = array())
+ {
+ parent::render($cache, $params);
+
+ $disposition = $this->isDownload ? 'attachment' : 'inline';
+
+ CmsFactory::getApplication()->setHeader('Content-disposition', $disposition . '; filename="' . $this->getName() . '.xml"', true);
+
+ return $this->getBuffer();
+ }
+
+ /**
+ * Returns the document name
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Sets the document name
+ *
+ * @param string $name Document name
+ *
+ * @return XmlDocument instance of $this to allow chaining
+ *
+ * @since 1.7.0
+ */
+ public function setName($name = 'joomla')
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Check if this document is intended for download
+ *
+ * @return string
+ *
+ * @since 3.9.0
+ */
+ public function isDownload()
+ {
+ return $this->isDownload;
+ }
+
+ /**
+ * Sets the document's download state
+ *
+ * @param boolean $download If true, this document will be downloaded; if false, this document will be displayed inline
+ *
+ * @return XmlDocument instance of $this to allow chaining
+ *
+ * @since 3.9.0
+ */
+ public function setDownload($download = false)
+ {
+ $this->isDownload = $download;
+
+ return $this;
+ }
}
diff --git a/libraries/src/Editor/Editor.php b/libraries/src/Editor/Editor.php
index bea85b316a403..4bdc3f64beafe 100644
--- a/libraries/src/Editor/Editor.php
+++ b/libraries/src/Editor/Editor.php
@@ -1,4 +1,5 @@
_name = $editor;
-
- // Set the dispatcher
- if (!\is_object($dispatcher))
- {
- $dispatcher = Factory::getContainer()->get('dispatcher');
- }
-
- $this->setDispatcher($dispatcher);
-
- // Register the getButtons event
- $this->getDispatcher()->addListener(
- 'getButtons',
- function (AbstractEvent $event) {
- $editor = $event->getArgument('editor', null);
- $buttons = $event->getArgument('buttons', null);
- $result = $event->getArgument('result', []);
- $newResult = $this->getButtons($editor, $buttons);
- $newResult = (array) $newResult;
- $event['result'] = array_merge($result, $newResult);
- }
- );
- }
-
- /**
- * Returns the global Editor object, only creating it
- * if it doesn't already exist.
- *
- * @param string $editor The editor to use.
- *
- * @return Editor The Editor object.
- *
- * @since 1.5
- */
- public static function getInstance($editor = 'none')
- {
- $signature = serialize($editor);
-
- if (empty(self::$instances[$signature]))
- {
- self::$instances[$signature] = new static($editor);
- }
-
- return self::$instances[$signature];
- }
-
- /**
- * Initialise the editor
- *
- * @return void
- *
- * @since 1.5
- */
- public function initialise()
- {
- // Check if editor is already loaded
- if ($this->_editor === null)
- {
- return;
- }
-
- if (method_exists($this->_editor, 'onInit'))
- {
- \call_user_func(array($this->_editor, 'onInit'));
- }
- }
-
- /**
- * Display the editor area.
- *
- * @param string $name The control name.
- * @param string $html The contents of the text area.
- * @param string $width The width of the text area (px or %).
- * @param string $height The height of the text area (px or %).
- * @param integer $col The number of columns for the textarea.
- * @param integer $row The number of rows for the textarea.
- * @param boolean $buttons True and the editor buttons will be displayed.
- * @param string $id An optional ID for the textarea (note: since 1.6). If not supplied the name is used.
- * @param string $asset The object asset
- * @param object $author The author.
- * @param array $params Associative array of editor parameters.
- *
- * @return string
- *
- * @since 1.5
- */
- public function display($name, $html, $width, $height, $col, $row, $buttons = true, $id = null, $asset = null, $author = null, $params = array())
- {
- $this->asset = $asset;
- $this->author = $author;
- $this->_loadEditor($params);
-
- // Check whether editor is already loaded
- if ($this->_editor === null)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JLIB_NO_EDITOR_PLUGIN_PUBLISHED'), 'danger');
-
- return;
- }
-
- // Backwards compatibility. Width and height should be passed without a semicolon from now on.
- // If editor plugins need a unit like "px" for CSS styling, they need to take care of that
- $width = str_replace(';', '', $width);
- $height = str_replace(';', '', $height);
-
- $args['name'] = $name;
- $args['content'] = $html;
- $args['width'] = $width;
- $args['height'] = $height;
- $args['col'] = $col;
- $args['row'] = $row;
- $args['buttons'] = $buttons;
- $args['id'] = $id ?: $name;
- $args['asset'] = $asset;
- $args['author'] = $author;
- $args['params'] = $params;
-
- return \call_user_func_array(array($this->_editor, 'onDisplay'), $args);
- }
-
- /**
- * Get the editor extended buttons (usually from plugins)
- *
- * @param string $editor The name of the editor.
- * @param mixed $buttons Can be boolean or array, if boolean defines if the buttons are
- * displayed, if array defines a list of buttons not to show.
- *
- * @return array
- *
- * @since 1.5
- */
- public function getButtons($editor, $buttons = true)
- {
- $result = array();
-
- if (\is_bool($buttons) && !$buttons)
- {
- return $result;
- }
-
- // Get plugins
- $plugins = PluginHelper::getPlugin('editors-xtd');
-
- foreach ($plugins as $plugin)
- {
- if (\is_array($buttons) && \in_array($plugin->name, $buttons))
- {
- continue;
- }
-
- PluginHelper::importPlugin('editors-xtd', $plugin->name, false);
- $className = 'PlgEditorsXtd' . $plugin->name;
-
- if (!class_exists($className))
- {
- $className = 'PlgButton' . $plugin->name;
- }
-
- if (class_exists($className))
- {
- $dispatcher = $this->getDispatcher();
- $plugin = new $className($dispatcher, (array) $plugin);
- }
-
- // Try to authenticate
- if (!method_exists($plugin, 'onDisplay'))
- {
- continue;
- }
-
- $button = $plugin->onDisplay($editor, $this->asset, $this->author);
-
- if (empty($button))
- {
- continue;
- }
-
- if (\is_array($button))
- {
- $result = array_merge($result, $button);
- continue;
- }
-
- $result[] = $button;
- }
-
- return $result;
- }
-
- /**
- * Load the editor
- *
- * @param array $config Associative array of editor config parameters
- *
- * @return mixed
- *
- * @since 1.5
- */
- protected function _loadEditor($config = array())
- {
- // Check whether editor is already loaded
- if ($this->_editor !== null)
- {
- return false;
- }
-
- // Build the path to the needed editor plugin
- $name = InputFilter::getInstance()->clean($this->_name, 'cmd');
- $path = JPATH_PLUGINS . '/editors/' . $name . '/' . $name . '.php';
-
- if (!is_file($path))
- {
- Log::add(Text::_('JLIB_HTML_EDITOR_CANNOT_LOAD'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- // Require plugin file
- require_once $path;
-
- // Get the plugin
- $plugin = PluginHelper::getPlugin('editors', $this->_name);
-
- // If no plugin is published we get an empty array and there not so much to do with it
- if (empty($plugin))
- {
- return false;
- }
-
- $params = new Registry($plugin->params);
- $params->loadArray($config);
- $plugin->params = $params;
-
- // Build editor plugin classname
- $name = 'PlgEditor' . $this->_name;
-
- $dispatcher = $this->getDispatcher();
-
- if ($this->_editor = new $name($dispatcher, (array) $plugin))
- {
- // Load plugin parameters
- $this->initialise();
- PluginHelper::importPlugin('editors-xtd');
- }
- }
+ use DispatcherAwareTrait;
+
+ /**
+ * Editor Plugin object
+ *
+ * @var object
+ * @since 1.5
+ */
+ protected $_editor = null;
+
+ /**
+ * Editor Plugin name
+ *
+ * @var string
+ * @since 1.5
+ */
+ protected $_name = null;
+
+ /**
+ * Object asset
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $asset = null;
+
+ /**
+ * Object author
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $author = null;
+
+ /**
+ * Editor instances container.
+ *
+ * @var Editor[]
+ * @since 2.5
+ */
+ protected static $instances = array();
+
+ /**
+ * Constructor
+ *
+ * @param string $editor The editor name
+ * @param DispatcherInterface $dispatcher The event dispatcher we're going to use
+ */
+ public function __construct($editor = 'none', DispatcherInterface $dispatcher = null)
+ {
+ $this->_name = $editor;
+
+ // Set the dispatcher
+ if (!\is_object($dispatcher)) {
+ $dispatcher = Factory::getContainer()->get('dispatcher');
+ }
+
+ $this->setDispatcher($dispatcher);
+
+ // Register the getButtons event
+ $this->getDispatcher()->addListener(
+ 'getButtons',
+ function (AbstractEvent $event) {
+ $editor = $event->getArgument('editor', null);
+ $buttons = $event->getArgument('buttons', null);
+ $result = $event->getArgument('result', []);
+ $newResult = $this->getButtons($editor, $buttons);
+ $newResult = (array) $newResult;
+ $event['result'] = array_merge($result, $newResult);
+ }
+ );
+ }
+
+ /**
+ * Returns the global Editor object, only creating it
+ * if it doesn't already exist.
+ *
+ * @param string $editor The editor to use.
+ *
+ * @return Editor The Editor object.
+ *
+ * @since 1.5
+ */
+ public static function getInstance($editor = 'none')
+ {
+ $signature = serialize($editor);
+
+ if (empty(self::$instances[$signature])) {
+ self::$instances[$signature] = new static($editor);
+ }
+
+ return self::$instances[$signature];
+ }
+
+ /**
+ * Initialise the editor
+ *
+ * @return void
+ *
+ * @since 1.5
+ */
+ public function initialise()
+ {
+ // Check if editor is already loaded
+ if ($this->_editor === null) {
+ return;
+ }
+
+ if (method_exists($this->_editor, 'onInit')) {
+ \call_user_func(array($this->_editor, 'onInit'));
+ }
+ }
+
+ /**
+ * Display the editor area.
+ *
+ * @param string $name The control name.
+ * @param string $html The contents of the text area.
+ * @param string $width The width of the text area (px or %).
+ * @param string $height The height of the text area (px or %).
+ * @param integer $col The number of columns for the textarea.
+ * @param integer $row The number of rows for the textarea.
+ * @param boolean $buttons True and the editor buttons will be displayed.
+ * @param string $id An optional ID for the textarea (note: since 1.6). If not supplied the name is used.
+ * @param string $asset The object asset
+ * @param object $author The author.
+ * @param array $params Associative array of editor parameters.
+ *
+ * @return string
+ *
+ * @since 1.5
+ */
+ public function display($name, $html, $width, $height, $col, $row, $buttons = true, $id = null, $asset = null, $author = null, $params = array())
+ {
+ $this->asset = $asset;
+ $this->author = $author;
+ $this->_loadEditor($params);
+
+ // Check whether editor is already loaded
+ if ($this->_editor === null) {
+ Factory::getApplication()->enqueueMessage(Text::_('JLIB_NO_EDITOR_PLUGIN_PUBLISHED'), 'danger');
+
+ return;
+ }
+
+ // Backwards compatibility. Width and height should be passed without a semicolon from now on.
+ // If editor plugins need a unit like "px" for CSS styling, they need to take care of that
+ $width = str_replace(';', '', $width);
+ $height = str_replace(';', '', $height);
+
+ $args['name'] = $name;
+ $args['content'] = $html;
+ $args['width'] = $width;
+ $args['height'] = $height;
+ $args['col'] = $col;
+ $args['row'] = $row;
+ $args['buttons'] = $buttons;
+ $args['id'] = $id ?: $name;
+ $args['asset'] = $asset;
+ $args['author'] = $author;
+ $args['params'] = $params;
+
+ return \call_user_func_array(array($this->_editor, 'onDisplay'), $args);
+ }
+
+ /**
+ * Get the editor extended buttons (usually from plugins)
+ *
+ * @param string $editor The name of the editor.
+ * @param mixed $buttons Can be boolean or array, if boolean defines if the buttons are
+ * displayed, if array defines a list of buttons not to show.
+ *
+ * @return array
+ *
+ * @since 1.5
+ */
+ public function getButtons($editor, $buttons = true)
+ {
+ $result = array();
+
+ if (\is_bool($buttons) && !$buttons) {
+ return $result;
+ }
+
+ // Get plugins
+ $plugins = PluginHelper::getPlugin('editors-xtd');
+
+ foreach ($plugins as $plugin) {
+ if (\is_array($buttons) && \in_array($plugin->name, $buttons)) {
+ continue;
+ }
+
+ PluginHelper::importPlugin('editors-xtd', $plugin->name, false);
+ $className = 'PlgEditorsXtd' . $plugin->name;
+
+ if (!class_exists($className)) {
+ $className = 'PlgButton' . $plugin->name;
+ }
+
+ if (class_exists($className)) {
+ $dispatcher = $this->getDispatcher();
+ $plugin = new $className($dispatcher, (array) $plugin);
+ }
+
+ // Try to authenticate
+ if (!method_exists($plugin, 'onDisplay')) {
+ continue;
+ }
+
+ $button = $plugin->onDisplay($editor, $this->asset, $this->author);
+
+ if (empty($button)) {
+ continue;
+ }
+
+ if (\is_array($button)) {
+ $result = array_merge($result, $button);
+ continue;
+ }
+
+ $result[] = $button;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Load the editor
+ *
+ * @param array $config Associative array of editor config parameters
+ *
+ * @return mixed
+ *
+ * @since 1.5
+ */
+ protected function _loadEditor($config = array())
+ {
+ // Check whether editor is already loaded
+ if ($this->_editor !== null) {
+ return false;
+ }
+
+ // Build the path to the needed editor plugin
+ $name = InputFilter::getInstance()->clean($this->_name, 'cmd');
+ $path = JPATH_PLUGINS . '/editors/' . $name . '/' . $name . '.php';
+
+ if (!is_file($path)) {
+ Log::add(Text::_('JLIB_HTML_EDITOR_CANNOT_LOAD'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ // Require plugin file
+ require_once $path;
+
+ // Get the plugin
+ $plugin = PluginHelper::getPlugin('editors', $this->_name);
+
+ // If no plugin is published we get an empty array and there not so much to do with it
+ if (empty($plugin)) {
+ return false;
+ }
+
+ $params = new Registry($plugin->params);
+ $params->loadArray($config);
+ $plugin->params = $params;
+
+ // Build editor plugin classname
+ $name = 'PlgEditor' . $this->_name;
+
+ $dispatcher = $this->getDispatcher();
+
+ if ($this->_editor = new $name($dispatcher, (array) $plugin)) {
+ // Load plugin parameters
+ $this->initialise();
+ PluginHelper::importPlugin('editors-xtd');
+ }
+ }
}
diff --git a/libraries/src/Encrypt/AES/AbstractAES.php b/libraries/src/Encrypt/AES/AbstractAES.php
index 135383f0c2162..32f025909ed1b 100644
--- a/libraries/src/Encrypt/AES/AbstractAES.php
+++ b/libraries/src/Encrypt/AES/AbstractAES.php
@@ -1,4 +1,5 @@
$size)
- {
- if (\function_exists('mb_substr'))
- {
- return mb_substr($key, 0, $size, 'ASCII');
- }
+ if ($keyLength > $size) {
+ if (\function_exists('mb_substr')) {
+ return mb_substr($key, 0, $size, 'ASCII');
+ }
- return substr($key, 0, $size);
- }
+ return substr($key, 0, $size);
+ }
- return $key . str_repeat("\0", ($size - $keyLength));
- }
+ return $key . str_repeat("\0", ($size - $keyLength));
+ }
- /**
- * Returns null bytes to append to the string so that it's zero padded to the specified block size
- *
- * @param string $string The binary string which will be zero padded
- * @param int $blockSize The block size
- *
- * @return string The zero bytes to append to the string to zero pad it to $blockSize
- */
- protected function getZeroPadding($string, $blockSize)
- {
- $stringSize = \strlen($string);
+ /**
+ * Returns null bytes to append to the string so that it's zero padded to the specified block size
+ *
+ * @param string $string The binary string which will be zero padded
+ * @param int $blockSize The block size
+ *
+ * @return string The zero bytes to append to the string to zero pad it to $blockSize
+ */
+ protected function getZeroPadding($string, $blockSize)
+ {
+ $stringSize = \strlen($string);
- if (\function_exists('mb_strlen'))
- {
- $stringSize = mb_strlen($string, 'ASCII');
- }
+ if (\function_exists('mb_strlen')) {
+ $stringSize = mb_strlen($string, 'ASCII');
+ }
- if ($stringSize == $blockSize)
- {
- return '';
- }
+ if ($stringSize == $blockSize) {
+ return '';
+ }
- if ($stringSize < $blockSize)
- {
- return str_repeat("\0", $blockSize - $stringSize);
- }
+ if ($stringSize < $blockSize) {
+ return str_repeat("\0", $blockSize - $stringSize);
+ }
- $paddingBytes = $stringSize % $blockSize;
+ $paddingBytes = $stringSize % $blockSize;
- return str_repeat("\0", $blockSize - $paddingBytes);
- }
+ return str_repeat("\0", $blockSize - $paddingBytes);
+ }
}
diff --git a/libraries/src/Encrypt/AES/AesInterface.php b/libraries/src/Encrypt/AES/AesInterface.php
index 55aa9ecf776f0..888163fc99674 100644
--- a/libraries/src/Encrypt/AES/AesInterface.php
+++ b/libraries/src/Encrypt/AES/AesInterface.php
@@ -1,4 +1,5 @@
cipherType = MCRYPT_RIJNDAEL_128;
- break;
-
- case '192':
- $this->cipherType = MCRYPT_RIJNDAEL_192;
- break;
-
- case '256':
- $this->cipherType = MCRYPT_RIJNDAEL_256;
- break;
- }
-
- switch (strtolower($mode))
- {
- case 'ecb':
- $this->cipherMode = MCRYPT_MODE_ECB;
- break;
-
- default:
- case 'cbc':
- $this->cipherMode = MCRYPT_MODE_CBC;
- break;
- }
-
- }
-
- /**
- * Encrypt the data
- *
- * @param string $plainText Plaintext data
- * @param string $key Encryption key
- * @param string $iv IV for the encryption
- *
- * @return string Encrypted data
- */
- public function encrypt($plainText, $key, $iv = null)
- {
- $iv_size = $this->getBlockSize();
- $key = $this->resizeKey($key, $iv_size);
- $iv = $this->resizeKey($iv, $iv_size);
-
- if (empty($iv))
- {
- $randVal = new Randval;
- $iv = $randVal->generate($iv_size);
- }
-
- $cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv);
- $cipherText = $iv . $cipherText;
-
- return $cipherText;
- }
-
- /**
- * Decrypt encrypted data
- *
- * @param string $cipherText Encrypted data
- * @param string $key Encryptionkey
- *
- * @return string Plaintext data
- */
- public function decrypt($cipherText, $key)
- {
- $iv_size = $this->getBlockSize();
- $key = $this->resizeKey($key, $iv_size);
- $iv = substr($cipherText, 0, $iv_size);
- $cipherText = substr($cipherText, $iv_size);
- $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);
-
- return $plainText;
- }
-
- /**
- * Is this adapter supported?
- *
- * @return boolean
- */
- public function isSupported()
- {
- if (!\function_exists('mcrypt_get_key_size'))
- {
- return false;
- }
-
- if (!\function_exists('mcrypt_get_iv_size'))
- {
- return false;
- }
-
- if (!\function_exists('mcrypt_create_iv'))
- {
- return false;
- }
-
- if (!\function_exists('mcrypt_encrypt'))
- {
- return false;
- }
-
- if (!\function_exists('mcrypt_decrypt'))
- {
- return false;
- }
-
- if (!\function_exists('mcrypt_list_algorithms'))
- {
- return false;
- }
-
- if (!\function_exists('hash'))
- {
- return false;
- }
-
- if (!\function_exists('hash_algos'))
- {
- return false;
- }
-
- $algorigthms = mcrypt_list_algorithms();
-
- if (!\in_array('rijndael-128', $algorigthms))
- {
- return false;
- }
-
- if (!\in_array('rijndael-192', $algorigthms))
- {
- return false;
- }
-
- if (!\in_array('rijndael-256', $algorigthms))
- {
- return false;
- }
-
- $algorigthms = hash_algos();
-
- if (!\in_array('sha256', $algorigthms))
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Get the block size
- *
- * @return integer
- */
- public function getBlockSize()
- {
- return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
- }
+ /**
+ * Cypher Type
+ *
+ * @var string
+ */
+ protected $cipherType = MCRYPT_RIJNDAEL_128;
+
+ /**
+ * Cypher Mode
+ *
+ * @var string
+ */
+ protected $cipherMode = MCRYPT_MODE_CBC;
+
+ /**
+ * Set the encryption mode
+ *
+ * @param string $mode Encryption Mode
+ * @param integer $strength Encryption Strength
+ *
+ * @return void
+ */
+ public function setEncryptionMode($mode = 'cbc', $strength = 128)
+ {
+ switch ((int) $strength) {
+ default:
+ case '128':
+ $this->cipherType = MCRYPT_RIJNDAEL_128;
+ break;
+
+ case '192':
+ $this->cipherType = MCRYPT_RIJNDAEL_192;
+ break;
+
+ case '256':
+ $this->cipherType = MCRYPT_RIJNDAEL_256;
+ break;
+ }
+
+ switch (strtolower($mode)) {
+ case 'ecb':
+ $this->cipherMode = MCRYPT_MODE_ECB;
+ break;
+
+ default:
+ case 'cbc':
+ $this->cipherMode = MCRYPT_MODE_CBC;
+ break;
+ }
+ }
+
+ /**
+ * Encrypt the data
+ *
+ * @param string $plainText Plaintext data
+ * @param string $key Encryption key
+ * @param string $iv IV for the encryption
+ *
+ * @return string Encrypted data
+ */
+ public function encrypt($plainText, $key, $iv = null)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = $this->resizeKey($iv, $iv_size);
+
+ if (empty($iv)) {
+ $randVal = new Randval();
+ $iv = $randVal->generate($iv_size);
+ }
+
+ $cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv);
+ $cipherText = $iv . $cipherText;
+
+ return $cipherText;
+ }
+
+ /**
+ * Decrypt encrypted data
+ *
+ * @param string $cipherText Encrypted data
+ * @param string $key Encryptionkey
+ *
+ * @return string Plaintext data
+ */
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);
+
+ return $plainText;
+ }
+
+ /**
+ * Is this adapter supported?
+ *
+ * @return boolean
+ */
+ public function isSupported()
+ {
+ if (!\function_exists('mcrypt_get_key_size')) {
+ return false;
+ }
+
+ if (!\function_exists('mcrypt_get_iv_size')) {
+ return false;
+ }
+
+ if (!\function_exists('mcrypt_create_iv')) {
+ return false;
+ }
+
+ if (!\function_exists('mcrypt_encrypt')) {
+ return false;
+ }
+
+ if (!\function_exists('mcrypt_decrypt')) {
+ return false;
+ }
+
+ if (!\function_exists('mcrypt_list_algorithms')) {
+ return false;
+ }
+
+ if (!\function_exists('hash')) {
+ return false;
+ }
+
+ if (!\function_exists('hash_algos')) {
+ return false;
+ }
+
+ $algorigthms = mcrypt_list_algorithms();
+
+ if (!\in_array('rijndael-128', $algorigthms)) {
+ return false;
+ }
+
+ if (!\in_array('rijndael-192', $algorigthms)) {
+ return false;
+ }
+
+ if (!\in_array('rijndael-256', $algorigthms)) {
+ return false;
+ }
+
+ $algorigthms = hash_algos();
+
+ if (!\in_array('sha256', $algorigthms)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the block size
+ *
+ * @return integer
+ */
+ public function getBlockSize()
+ {
+ return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
+ }
}
diff --git a/libraries/src/Encrypt/AES/OpenSSL.php b/libraries/src/Encrypt/AES/OpenSSL.php
index 4356c860a727d..084431dedc785 100644
--- a/libraries/src/Encrypt/AES/OpenSSL.php
+++ b/libraries/src/Encrypt/AES/OpenSSL.php
@@ -1,4 +1,5 @@
openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
- }
-
- /**
- * Sets the AES encryption mode.
- *
- * WARNING: The strength is deprecated as it has a different effect in MCrypt and OpenSSL. MCrypt was abandoned in
- * 2003 before the Rijndael-128 algorithm was officially the Advanced Encryption Standard (AES). MCrypt also offered
- * Rijndael-192 and Rijndael-256 algorithms with different block sizes. These are NOT used in AES. OpenSSL, however,
- * implements AES correctly. It always uses a 128-bit (16 byte) block. The 192 and 256 bit strengths refer to the
- * key size, not the block size. Therefore using different strengths in MCrypt and OpenSSL will result in different
- * and incompatible ciphertexts.
- *
- * TL;DR: Always use $strength = 128!
- *
- * @param string $mode Choose between CBC (recommended) or ECB
- * @param int $strength Bit strength of the key (128, 192 or 256 bits). DEPRECATED. READ NOTES ABOVE.
- *
- * @return mixed
- */
- public function setEncryptionMode($mode = 'cbc', $strength = 128)
- {
- static $availableAlgorithms = null;
- static $defaultAlgo = 'aes-128-cbc';
-
- if (!\is_array($availableAlgorithms))
- {
- $availableAlgorithms = openssl_get_cipher_methods();
-
- foreach (array('aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
- 'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb') as $algo
- )
- {
- if (\in_array($algo, $availableAlgorithms))
- {
- $defaultAlgo = $algo;
- break;
- }
- }
- }
-
- $strength = (int) $strength;
- $mode = strtolower($mode);
-
- if (!\in_array($strength, array(128, 192, 256)))
- {
- $strength = 256;
- }
-
- if (!\in_array($mode, array('cbc', 'ebc')))
- {
- $mode = 'cbc';
- }
-
- $algo = 'aes-' . $strength . '-' . $mode;
-
- if (!\in_array($algo, $availableAlgorithms))
- {
- $algo = $defaultAlgo;
- }
-
- $this->method = $algo;
- }
-
- /**
- * Encrypts a string. Returns the raw binary ciphertext.
- *
- * WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
- * plaintext and trim the string to that length upon decryption.
- *
- * @param string $plainText The plaintext to encrypt
- * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
- * @param null|string $iv The initialization vector (for CBC mode algorithms)
- *
- * @return string The raw encrypted binary string.
- */
- public function encrypt($plainText, $key, $iv = null)
- {
- $iv_size = $this->getBlockSize();
- $key = $this->resizeKey($key, $iv_size);
- $iv = $this->resizeKey($iv, $iv_size);
-
- if (empty($iv))
- {
- $randVal = new Randval;
- $iv = $randVal->generate($iv_size);
- }
-
- $plainText .= $this->getZeroPadding($plainText, $iv_size);
- $cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
- $cipherText = $iv . $cipherText;
-
- return $cipherText;
- }
-
- /**
- * Decrypts a string. Returns the raw binary plaintext.
- *
- * $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
- * dropped in EBC mode since there is no concept of IV in EBC).
- *
- * WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
- * to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
- * appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
- * just padding!).
- *
- * @param string $cipherText The ciphertext to encrypt
- * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
- *
- * @return string The raw unencrypted binary string.
- */
- public function decrypt($cipherText, $key)
- {
- $iv_size = $this->getBlockSize();
- $key = $this->resizeKey($key, $iv_size);
- $iv = substr($cipherText, 0, $iv_size);
- $cipherText = substr($cipherText, $iv_size);
- $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
-
- // Remove the zero padding
- return rtrim($plainText, "\0");
- }
-
- /**
- * Is this adapter supported?
- *
- * @return boolean
- */
- public function isSupported()
- {
- if (!\function_exists('openssl_get_cipher_methods'))
- {
- return false;
- }
-
- if (!\function_exists('openssl_random_pseudo_bytes'))
- {
- return false;
- }
-
- if (!\function_exists('openssl_cipher_iv_length'))
- {
- return false;
- }
-
- if (!\function_exists('openssl_encrypt'))
- {
- return false;
- }
-
- if (!\function_exists('openssl_decrypt'))
- {
- return false;
- }
-
- if (!\function_exists('hash'))
- {
- return false;
- }
-
- if (!\function_exists('hash_algos'))
- {
- return false;
- }
-
- $algorithms = openssl_get_cipher_methods();
-
- if (!\in_array('aes-128-cbc', $algorithms))
- {
- return false;
- }
-
- $algorithms = hash_algos();
-
- if (!\in_array('sha256', $algorithms))
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns the encryption block size in bytes
- *
- * @return integer
- */
- public function getBlockSize()
- {
- return openssl_cipher_iv_length($this->method);
- }
+ /**
+ * The OpenSSL options for encryption / decryption
+ *
+ * @var integer
+ */
+ protected $openSSLOptions = 0;
+
+ /**
+ * The encryption method to use
+ *
+ * @var string
+ */
+ protected $method = 'aes-128-cbc';
+
+ /**
+ * Constructor for this class
+ */
+ public function __construct()
+ {
+ $this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
+ }
+
+ /**
+ * Sets the AES encryption mode.
+ *
+ * WARNING: The strength is deprecated as it has a different effect in MCrypt and OpenSSL. MCrypt was abandoned in
+ * 2003 before the Rijndael-128 algorithm was officially the Advanced Encryption Standard (AES). MCrypt also offered
+ * Rijndael-192 and Rijndael-256 algorithms with different block sizes. These are NOT used in AES. OpenSSL, however,
+ * implements AES correctly. It always uses a 128-bit (16 byte) block. The 192 and 256 bit strengths refer to the
+ * key size, not the block size. Therefore using different strengths in MCrypt and OpenSSL will result in different
+ * and incompatible ciphertexts.
+ *
+ * TL;DR: Always use $strength = 128!
+ *
+ * @param string $mode Choose between CBC (recommended) or ECB
+ * @param int $strength Bit strength of the key (128, 192 or 256 bits). DEPRECATED. READ NOTES ABOVE.
+ *
+ * @return mixed
+ */
+ public function setEncryptionMode($mode = 'cbc', $strength = 128)
+ {
+ static $availableAlgorithms = null;
+ static $defaultAlgo = 'aes-128-cbc';
+
+ if (!\is_array($availableAlgorithms)) {
+ $availableAlgorithms = openssl_get_cipher_methods();
+
+ foreach (
+ array('aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
+ 'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb') as $algo
+ ) {
+ if (\in_array($algo, $availableAlgorithms)) {
+ $defaultAlgo = $algo;
+ break;
+ }
+ }
+ }
+
+ $strength = (int) $strength;
+ $mode = strtolower($mode);
+
+ if (!\in_array($strength, array(128, 192, 256))) {
+ $strength = 256;
+ }
+
+ if (!\in_array($mode, array('cbc', 'ebc'))) {
+ $mode = 'cbc';
+ }
+
+ $algo = 'aes-' . $strength . '-' . $mode;
+
+ if (!\in_array($algo, $availableAlgorithms)) {
+ $algo = $defaultAlgo;
+ }
+
+ $this->method = $algo;
+ }
+
+ /**
+ * Encrypts a string. Returns the raw binary ciphertext.
+ *
+ * WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
+ * plaintext and trim the string to that length upon decryption.
+ *
+ * @param string $plainText The plaintext to encrypt
+ * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
+ * @param null|string $iv The initialization vector (for CBC mode algorithms)
+ *
+ * @return string The raw encrypted binary string.
+ */
+ public function encrypt($plainText, $key, $iv = null)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = $this->resizeKey($iv, $iv_size);
+
+ if (empty($iv)) {
+ $randVal = new Randval();
+ $iv = $randVal->generate($iv_size);
+ }
+
+ $plainText .= $this->getZeroPadding($plainText, $iv_size);
+ $cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
+ $cipherText = $iv . $cipherText;
+
+ return $cipherText;
+ }
+
+ /**
+ * Decrypts a string. Returns the raw binary plaintext.
+ *
+ * $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
+ * dropped in EBC mode since there is no concept of IV in EBC).
+ *
+ * WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
+ * to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
+ * appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
+ * just padding!).
+ *
+ * @param string $cipherText The ciphertext to encrypt
+ * @param string $key The raw binary key (will be zero-padded or chopped if its size is different than the block size)
+ *
+ * @return string The raw unencrypted binary string.
+ */
+ public function decrypt($cipherText, $key)
+ {
+ $iv_size = $this->getBlockSize();
+ $key = $this->resizeKey($key, $iv_size);
+ $iv = substr($cipherText, 0, $iv_size);
+ $cipherText = substr($cipherText, $iv_size);
+ $plainText = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);
+
+ // Remove the zero padding
+ return rtrim($plainText, "\0");
+ }
+
+ /**
+ * Is this adapter supported?
+ *
+ * @return boolean
+ */
+ public function isSupported()
+ {
+ if (!\function_exists('openssl_get_cipher_methods')) {
+ return false;
+ }
+
+ if (!\function_exists('openssl_random_pseudo_bytes')) {
+ return false;
+ }
+
+ if (!\function_exists('openssl_cipher_iv_length')) {
+ return false;
+ }
+
+ if (!\function_exists('openssl_encrypt')) {
+ return false;
+ }
+
+ if (!\function_exists('openssl_decrypt')) {
+ return false;
+ }
+
+ if (!\function_exists('hash')) {
+ return false;
+ }
+
+ if (!\function_exists('hash_algos')) {
+ return false;
+ }
+
+ $algorithms = openssl_get_cipher_methods();
+
+ if (!\in_array('aes-128-cbc', $algorithms)) {
+ return false;
+ }
+
+ $algorithms = hash_algos();
+
+ if (!\in_array('sha256', $algorithms)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the encryption block size in bytes
+ *
+ * @return integer
+ */
+ public function getBlockSize()
+ {
+ return openssl_cipher_iv_length($this->method);
+ }
}
diff --git a/libraries/src/Encrypt/Aes.php b/libraries/src/Encrypt/Aes.php
index 5db57b223e255..e27d2b093c816 100644
--- a/libraries/src/Encrypt/Aes.php
+++ b/libraries/src/Encrypt/Aes.php
@@ -1,10 +1,11 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
- * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
+ * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
*/
namespace Joomla\CMS\Encrypt;
@@ -13,8 +14,6 @@
use Joomla\CMS\Encrypt\AES\Mcrypt;
use Joomla\CMS\Encrypt\AES\OpenSSL;
-\defined('JPATH_PLATFORM') or die;
-
/**
* A simple implementation of AES-128, AES-192 and AES-256 encryption using the
* high performance mcrypt library.
@@ -23,281 +22,255 @@
*/
class Aes
{
- /**
- * The cipher key.
- *
- * @var string
- */
- protected $key = '';
-
- /**
- * The AES encryption adapter in use.
- *
- * @var AesInterface
- */
- protected $adapter;
-
- /**
- * Initialise the AES encryption object.
- *
- * Note: If the key is not 16 bytes this class will do a stupid key expansion for legacy reasons (produce the
- * SHA-256 of the key string and throw away half of it).
- *
- * @param string $key The encryption key (password). It can be a raw key (16 bytes) or a passphrase.
- * @param int $strength Bit strength (128, 192 or 256) – ALWAYS USE 128 BITS. THIS PARAMETER IS DEPRECATED.
- * @param string $mode Encryption mode. Can be ebc or cbc. We recommend using cbc.
- * @param string $priority Priority which adapter we should try first
- *
- * @deprecated 5.0 $strength will be removed
- */
- public function __construct($key, $strength = 128, $mode = 'cbc', $priority = 'openssl')
- {
- if ($priority === 'openssl')
- {
- $this->adapter = new OpenSSL;
-
- if (!$this->adapter->isSupported())
- {
- $this->adapter = new Mcrypt;
- }
- }
- else
- {
- $this->adapter = new Mcrypt;
-
- if (!$this->adapter->isSupported())
- {
- $this->adapter = new OpenSSL;
- }
- }
-
- $this->adapter->setEncryptionMode($mode, $strength);
- $this->setPassword($key, true);
- }
-
- /**
- * Sets the password for this instance.
- *
- * WARNING: Do not use the legacy mode, it's insecure
- *
- * @param string $password The password (either user-provided password or binary encryption key) to use
- * @param bool $legacyMode True to use the legacy key expansion. We recommend against using it.
- *
- * @since 4.0.0
- * @return void
- */
- public function setPassword($password, $legacyMode = false)
- {
- $this->key = $password;
-
- $passLength = \strlen($password);
-
- if (\function_exists('mb_strlen'))
- {
- $passLength = mb_strlen($password, 'ASCII');
- }
-
- // Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
- if ($legacyMode && ($passLength != 32))
- {
- // Legacy mode: use the sha256 of the password
- $this->key = hash('sha256', $password, true);
-
- // We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
- $this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
- }
- }
-
- /**
- * Encrypts a string using AES
- *
- * @param string $stringToEncrypt The plaintext to encrypt
- * @param bool $base64encoded Should I Base64-encode the result?
- *
- * @return string The cryptotext. Please note that the first 16 bytes of
- * the raw string is the IV (initialisation vector) which
- * is necessary for decoding the string.
- */
- public function encryptString($stringToEncrypt, $base64encoded = true)
- {
- $blockSize = $this->adapter->getBlockSize();
- $randVal = new Randval;
- $iv = $randVal->generate($blockSize);
-
- $key = $this->getExpandedKey($blockSize, $iv);
- $cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);
-
- // Optionally pass the result through Base64 encoding
- if ($base64encoded)
- {
- $cipherText = base64_encode($cipherText);
- }
-
- // Return the result
- return $cipherText;
- }
-
- /**
- * Decrypts a ciphertext into a plaintext string using AES
- *
- * @param string $stringToDecrypt The ciphertext to decrypt. The first 16 bytes of the raw string must contain
- * the IV (initialisation vector).
- * @param bool $base64encoded Should I Base64-decode the data before decryption?
- *
- * @return string The plain text string
- */
- public function decryptString($stringToDecrypt, $base64encoded = true)
- {
- if ($base64encoded)
- {
- $stringToDecrypt = base64_decode($stringToDecrypt);
- }
-
- // Extract IV
- $iv_size = $this->adapter->getBlockSize();
- $iv = substr($stringToDecrypt, 0, $iv_size);
- $key = $this->getExpandedKey($iv_size, $iv);
-
- // Decrypt the data
- $plainText = $this->adapter->decrypt($stringToDecrypt, $key);
-
- return $plainText;
- }
-
- /**
- * Is AES encryption supported by this PHP installation?
- *
- * @return boolean
- */
- public static function isSupported()
- {
- $adapter = new OpenSSL;
-
- if (!$adapter->isSupported())
- {
- $adapter = new Mcrypt;
-
- if (!$adapter->isSupported())
- {
- return false;
- }
- }
-
- if (!\function_exists('base64_encode'))
- {
- return false;
- }
-
- if (!\function_exists('base64_decode'))
- {
- return false;
- }
-
- if (!\function_exists('hash_algos'))
- {
- return false;
- }
-
- $algorithms = hash_algos();
-
- if (!\in_array('sha256', $algorithms))
- {
- return false;
- }
-
- return true;
- }
-
- /**
- * Get the expanded key
- *
- * @param integer $blockSize Blocksize to process
- * @param string $iv IV
- *
- * @return string
- */
- public function getExpandedKey($blockSize, $iv)
- {
- $key = $this->key;
- $passLength = \strlen($key);
-
- if (\function_exists('mb_strlen'))
- {
- $passLength = mb_strlen($key, 'ASCII');
- }
-
- if ($passLength != $blockSize)
- {
- $iterations = 1000;
- $salt = $this->adapter->resizeKey($iv, 16);
- $key = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
- }
-
- return $key;
- }
+ /**
+ * The cipher key.
+ *
+ * @var string
+ */
+ protected $key = '';
+
+ /**
+ * The AES encryption adapter in use.
+ *
+ * @var AesInterface
+ */
+ protected $adapter;
+
+ /**
+ * Initialise the AES encryption object.
+ *
+ * Note: If the key is not 16 bytes this class will do a stupid key expansion for legacy reasons (produce the
+ * SHA-256 of the key string and throw away half of it).
+ *
+ * @param string $key The encryption key (password). It can be a raw key (16 bytes) or a passphrase.
+ * @param int $strength Bit strength (128, 192 or 256) – ALWAYS USE 128 BITS. THIS PARAMETER IS DEPRECATED.
+ * @param string $mode Encryption mode. Can be ebc or cbc. We recommend using cbc.
+ * @param string $priority Priority which adapter we should try first
+ *
+ * @deprecated 5.0 $strength will be removed
+ */
+ public function __construct($key, $strength = 128, $mode = 'cbc', $priority = 'openssl')
+ {
+ if ($priority === 'openssl') {
+ $this->adapter = new OpenSSL();
+
+ if (!$this->adapter->isSupported()) {
+ $this->adapter = new Mcrypt();
+ }
+ } else {
+ $this->adapter = new Mcrypt();
+
+ if (!$this->adapter->isSupported()) {
+ $this->adapter = new OpenSSL();
+ }
+ }
+
+ $this->adapter->setEncryptionMode($mode, $strength);
+ $this->setPassword($key, true);
+ }
+
+ /**
+ * Sets the password for this instance.
+ *
+ * WARNING: Do not use the legacy mode, it's insecure
+ *
+ * @param string $password The password (either user-provided password or binary encryption key) to use
+ * @param bool $legacyMode True to use the legacy key expansion. We recommend against using it.
+ *
+ * @since 4.0.0
+ * @return void
+ */
+ public function setPassword($password, $legacyMode = false)
+ {
+ $this->key = $password;
+
+ $passLength = \strlen($password);
+
+ if (\function_exists('mb_strlen')) {
+ $passLength = mb_strlen($password, 'ASCII');
+ }
+
+ // Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
+ if ($legacyMode && ($passLength != 32)) {
+ // Legacy mode: use the sha256 of the password
+ $this->key = hash('sha256', $password, true);
+
+ // We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
+ $this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
+ }
+ }
+
+ /**
+ * Encrypts a string using AES
+ *
+ * @param string $stringToEncrypt The plaintext to encrypt
+ * @param bool $base64encoded Should I Base64-encode the result?
+ *
+ * @return string The cryptotext. Please note that the first 16 bytes of
+ * the raw string is the IV (initialisation vector) which
+ * is necessary for decoding the string.
+ */
+ public function encryptString($stringToEncrypt, $base64encoded = true)
+ {
+ $blockSize = $this->adapter->getBlockSize();
+ $randVal = new Randval();
+ $iv = $randVal->generate($blockSize);
+
+ $key = $this->getExpandedKey($blockSize, $iv);
+ $cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);
+
+ // Optionally pass the result through Base64 encoding
+ if ($base64encoded) {
+ $cipherText = base64_encode($cipherText);
+ }
+
+ // Return the result
+ return $cipherText;
+ }
+
+ /**
+ * Decrypts a ciphertext into a plaintext string using AES
+ *
+ * @param string $stringToDecrypt The ciphertext to decrypt. The first 16 bytes of the raw string must contain
+ * the IV (initialisation vector).
+ * @param bool $base64encoded Should I Base64-decode the data before decryption?
+ *
+ * @return string The plain text string
+ */
+ public function decryptString($stringToDecrypt, $base64encoded = true)
+ {
+ if ($base64encoded) {
+ $stringToDecrypt = base64_decode($stringToDecrypt);
+ }
+
+ // Extract IV
+ $iv_size = $this->adapter->getBlockSize();
+ $iv = substr($stringToDecrypt, 0, $iv_size);
+ $key = $this->getExpandedKey($iv_size, $iv);
+
+ // Decrypt the data
+ $plainText = $this->adapter->decrypt($stringToDecrypt, $key);
+
+ return $plainText;
+ }
+
+ /**
+ * Is AES encryption supported by this PHP installation?
+ *
+ * @return boolean
+ */
+ public static function isSupported()
+ {
+ $adapter = new OpenSSL();
+
+ if (!$adapter->isSupported()) {
+ $adapter = new Mcrypt();
+
+ if (!$adapter->isSupported()) {
+ return false;
+ }
+ }
+
+ if (!\function_exists('base64_encode')) {
+ return false;
+ }
+
+ if (!\function_exists('base64_decode')) {
+ return false;
+ }
+
+ if (!\function_exists('hash_algos')) {
+ return false;
+ }
+
+ $algorithms = hash_algos();
+
+ if (!\in_array('sha256', $algorithms)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the expanded key
+ *
+ * @param integer $blockSize Blocksize to process
+ * @param string $iv IV
+ *
+ * @return string
+ */
+ public function getExpandedKey($blockSize, $iv)
+ {
+ $key = $this->key;
+ $passLength = \strlen($key);
+
+ if (\function_exists('mb_strlen')) {
+ $passLength = mb_strlen($key, 'ASCII');
+ }
+
+ if ($passLength != $blockSize) {
+ $iterations = 1000;
+ $salt = $this->adapter->resizeKey($iv, 16);
+ $key = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
+ }
+
+ return $key;
+ }
}
-if (!\function_exists('hash_pbkdf2'))
-{
- /**
- * Shim for missing hash_pbkdf2
- *
- * @param string $algo Algorithm to use
- * @param string $password Plaintext password
- * @param string $salt Salt for the hash
- * @param integer $count Number of iterations
- * @param integer $length Length
- * @param boolean $rawOutput Raw output
- *
- * @return string Hashed string
- */
- function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $rawOutput = false)
- {
- if (!\in_array(strtolower($algo), hash_algos()))
- {
- trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
- }
-
- if (!is_numeric($count))
- {
- trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . \gettype($count) . ' given', E_USER_WARNING);
- }
-
- if (!is_numeric($length))
- {
- trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . \gettype($length) . ' given', E_USER_WARNING);
- }
-
- if ($count <= 0)
- {
- trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
- }
-
- if ($length < 0)
- {
- trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
- }
-
- $output = '';
- $block_count = $length ? ceil($length / \strlen(hash($algo, '', $rawOutput))) : 1;
-
- for ($i = 1; $i <= $block_count; $i++)
- {
- $last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);
-
- for ($j = 1; $j < $count; $j++)
- {
- $xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
- }
-
- $output .= $xorsum;
- }
-
- if (!$rawOutput)
- {
- $output = bin2hex($output);
- }
-
- return $length ? substr($output, 0, $length) : $output;
- }
+if (!\function_exists('hash_pbkdf2')) {
+ /**
+ * Shim for missing hash_pbkdf2
+ *
+ * @param string $algo Algorithm to use
+ * @param string $password Plaintext password
+ * @param string $salt Salt for the hash
+ * @param integer $count Number of iterations
+ * @param integer $length Length
+ * @param boolean $rawOutput Raw output
+ *
+ * @return string Hashed string
+ */
+ function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $rawOutput = false)
+ {
+ if (!\in_array(strtolower($algo), hash_algos())) {
+ trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
+ }
+
+ if (!is_numeric($count)) {
+ trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . \gettype($count) . ' given', E_USER_WARNING);
+ }
+
+ if (!is_numeric($length)) {
+ trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . \gettype($length) . ' given', E_USER_WARNING);
+ }
+
+ if ($count <= 0) {
+ trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
+ }
+
+ if ($length < 0) {
+ trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
+ }
+
+ $output = '';
+ $block_count = $length ? ceil($length / \strlen(hash($algo, '', $rawOutput))) : 1;
+
+ for ($i = 1; $i <= $block_count; $i++) {
+ $last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);
+
+ for ($j = 1; $j < $count; $j++) {
+ $xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
+ }
+
+ $output .= $xorsum;
+ }
+
+ if (!$rawOutput) {
+ $output = bin2hex($output);
+ }
+
+ return $length ? substr($output, 0, $length) : $output;
+ }
}
diff --git a/libraries/src/Encrypt/Base32.php b/libraries/src/Encrypt/Base32.php
index 3ec3fc59ca2ea..3f31ff7736c24 100644
--- a/libraries/src/Encrypt/Base32.php
+++ b/libraries/src/Encrypt/Base32.php
@@ -1,4 +1,5 @@
0)
- {
- throw new \Exception('Length must be divisible by 8');
- }
-
- if (!preg_match('/^[01]+$/', $str))
- {
- throw new \Exception('Only 0\'s and 1\'s are permitted');
- }
-
- preg_match_all('/.{8}/', $str, $chrs);
- $chrs = array_map('bindec', $chrs[0]);
-
- // I'm just being slack here
- array_unshift($chrs, 'C*');
-
- return \call_user_func_array('pack', $chrs);
- }
-
- /**
- * fromBin
- *
- * Converts a correct binary string to base32
- *
- * @param string $str The string of 0's and 1's you want to convert
- *
- * @return string String encoded as base32
- *
- * @throws \Exception
- */
- private function fromBin($str)
- {
- if (\strlen($str) % 8 > 0)
- {
- throw new \Exception('Length must be divisible by 8');
- }
-
- if (!preg_match('/^[01]+$/', $str))
- {
- throw new \Exception('Only 0\'s and 1\'s are permitted');
- }
-
- // Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
- $str = preg_replace('/(.{5})/', '000$1', $str);
-
- // We need a string divisible by 5
- $length = \strlen($str);
- $rbits = $length & 7;
-
- if ($rbits > 0)
- {
- // Excessive bits need to be padded
- $ebits = substr($str, $length - $rbits);
- $str = substr($str, 0, $length - $rbits);
- $str .= "000$ebits" . str_repeat('0', 5 - \strlen($ebits));
- }
-
- preg_match_all('/.{8}/', $str, $chrs);
- $chrs = array_map(array($this, '_mapcharset'), $chrs[0]);
-
- return implode('', $chrs);
- }
-
- /**
- * toBin
- *
- * Accepts a base32 string and returns an ascii binary string
- *
- * @param string $str The base32 string to convert
- *
- * @return string Ascii binary string
- *
- * @throws \Exception
- */
- private function toBin($str)
- {
- if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str))
- {
- throw new \Exception('Must match character set');
- }
-
- // Convert the base32 string back to a binary string
- $str = implode('', array_map(array($this, '_mapbin'), str_split($str)));
-
- // Remove the extra 0's we added
- $str = preg_replace('/000(.{5})/', '$1', $str);
-
- // Unpad if necessary
- $length = \strlen($str);
- $rbits = $length & 7;
-
- if ($rbits > 0)
- {
- $str = substr($str, 0, $length - $rbits);
- }
-
- return $str;
- }
-
- /**
- * fromString
- *
- * Convert any string to a base32 string
- * This should be binary safe...
- *
- * @param string $str The string to convert
- *
- * @return string The converted base32 string
- */
- public function encode($str)
- {
- return $this->fromBin($this->str2bin($str));
- }
-
- /**
- * toString
- *
- * Convert any base32 string to a normal sctring
- * This should be binary safe...
- *
- * @param string $str The base32 string to convert
- *
- * @return string The normal string
- */
- public function decode($str)
- {
- $str = strtoupper($str);
-
- return $this->bin2str($this->toBin($str));
- }
-
- /**
- * _mapcharset
- *
- * Used with array_map to map the bits from a binary string
- * directly into a base32 character set
- *
- * @param string $str The string of 0's and 1's you want to convert
- *
- * @return string Resulting base32 character
- *
- * @access private
- */
- private function _mapcharset($str)
- {
- // Huh!
- $x = self::CSRFC3548;
-
- return $x[bindec($str)];
- }
-
- /**
- * _mapbin
- *
- * Used with array_map to map the characters from a base32
- * character set directly into a binary string
- *
- * @param string $chr The character to map
- *
- * @return string String of 0's and 1's
- *
- * @access private
- */
- private function _mapbin($chr)
- {
- return sprintf('%08b', strpos(self::CSRFC3548, $chr));
- }
+ /**
+ * CSRFC3548
+ *
+ * The character set as defined by RFC3548
+ * @link http://www.ietf.org/rfc/rfc3548.txt
+ */
+ public const CSRFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
+
+ /**
+ * str2bin
+ *
+ * Converts any ascii string to a binary string
+ *
+ * @param string $str The string you want to convert
+ *
+ * @return string String of 0's and 1's
+ */
+ private function str2bin($str)
+ {
+ $chrs = unpack('C*', $str);
+
+ return vsprintf(str_repeat('%08b', \count($chrs)), $chrs);
+ }
+
+ /**
+ * bin2str
+ *
+ * Converts a binary string to an ascii string
+ *
+ * @param string $str The string of 0's and 1's you want to convert
+ *
+ * @return string The ascii output
+ *
+ * @throws \Exception
+ */
+ private function bin2str($str)
+ {
+ if (\strlen($str) % 8 > 0) {
+ throw new \Exception('Length must be divisible by 8');
+ }
+
+ if (!preg_match('/^[01]+$/', $str)) {
+ throw new \Exception('Only 0\'s and 1\'s are permitted');
+ }
+
+ preg_match_all('/.{8}/', $str, $chrs);
+ $chrs = array_map('bindec', $chrs[0]);
+
+ // I'm just being slack here
+ array_unshift($chrs, 'C*');
+
+ return \call_user_func_array('pack', $chrs);
+ }
+
+ /**
+ * fromBin
+ *
+ * Converts a correct binary string to base32
+ *
+ * @param string $str The string of 0's and 1's you want to convert
+ *
+ * @return string String encoded as base32
+ *
+ * @throws \Exception
+ */
+ private function fromBin($str)
+ {
+ if (\strlen($str) % 8 > 0) {
+ throw new \Exception('Length must be divisible by 8');
+ }
+
+ if (!preg_match('/^[01]+$/', $str)) {
+ throw new \Exception('Only 0\'s and 1\'s are permitted');
+ }
+
+ // Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
+ $str = preg_replace('/(.{5})/', '000$1', $str);
+
+ // We need a string divisible by 5
+ $length = \strlen($str);
+ $rbits = $length & 7;
+
+ if ($rbits > 0) {
+ // Excessive bits need to be padded
+ $ebits = substr($str, $length - $rbits);
+ $str = substr($str, 0, $length - $rbits);
+ $str .= "000$ebits" . str_repeat('0', 5 - \strlen($ebits));
+ }
+
+ preg_match_all('/.{8}/', $str, $chrs);
+ $chrs = array_map(array($this, '_mapcharset'), $chrs[0]);
+
+ return implode('', $chrs);
+ }
+
+ /**
+ * toBin
+ *
+ * Accepts a base32 string and returns an ascii binary string
+ *
+ * @param string $str The base32 string to convert
+ *
+ * @return string Ascii binary string
+ *
+ * @throws \Exception
+ */
+ private function toBin($str)
+ {
+ if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str)) {
+ throw new \Exception('Must match character set');
+ }
+
+ // Convert the base32 string back to a binary string
+ $str = implode('', array_map(array($this, '_mapbin'), str_split($str)));
+
+ // Remove the extra 0's we added
+ $str = preg_replace('/000(.{5})/', '$1', $str);
+
+ // Unpad if necessary
+ $length = \strlen($str);
+ $rbits = $length & 7;
+
+ if ($rbits > 0) {
+ $str = substr($str, 0, $length - $rbits);
+ }
+
+ return $str;
+ }
+
+ /**
+ * fromString
+ *
+ * Convert any string to a base32 string
+ * This should be binary safe...
+ *
+ * @param string $str The string to convert
+ *
+ * @return string The converted base32 string
+ */
+ public function encode($str)
+ {
+ return $this->fromBin($this->str2bin($str));
+ }
+
+ /**
+ * toString
+ *
+ * Convert any base32 string to a normal sctring
+ * This should be binary safe...
+ *
+ * @param string $str The base32 string to convert
+ *
+ * @return string The normal string
+ */
+ public function decode($str)
+ {
+ $str = strtoupper($str);
+
+ return $this->bin2str($this->toBin($str));
+ }
+
+ /**
+ * _mapcharset
+ *
+ * Used with array_map to map the bits from a binary string
+ * directly into a base32 character set
+ *
+ * @param string $str The string of 0's and 1's you want to convert
+ *
+ * @return string Resulting base32 character
+ *
+ * @access private
+ */
+ private function _mapcharset($str)
+ {
+ // Huh!
+ $x = self::CSRFC3548;
+
+ return $x[bindec($str)];
+ }
+
+ /**
+ * _mapbin
+ *
+ * Used with array_map to map the characters from a base32
+ * character set directly into a binary string
+ *
+ * @param string $chr The character to map
+ *
+ * @return string String of 0's and 1's
+ *
+ * @access private
+ */
+ private function _mapbin($chr)
+ {
+ return sprintf('%08b', strpos(self::CSRFC3548, $chr));
+ }
}
diff --git a/libraries/src/Encrypt/RandValInterface.php b/libraries/src/Encrypt/RandValInterface.php
index 373b33cef96f9..d3ba451d9c1fe 100644
--- a/libraries/src/Encrypt/RandValInterface.php
+++ b/libraries/src/Encrypt/RandValInterface.php
@@ -1,4 +1,5 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
- * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
+ * @note This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
*/
namespace Joomla\CMS\Encrypt;
-\defined('JPATH_PLATFORM') or die;
-
/**
* This class provides an RFC6238-compliant Time-based One Time Passwords,
* compatible with Google Authenticator (with PassCodeLength = 6 and TimePeriod = 30).
@@ -19,189 +18,183 @@
*/
class Totp
{
- /**
- * Passcode length
- *
- * @var integer
- */
- private $_passCodeLength = 6;
-
- /**
- * Pin modulo
- *
- * @var integer
- */
- private $_pinModulo;
-
- /**
- * The length of the secret in bytes.
- * RFC 4226: "The length of the shared secret MUST be at least 128 bits. This document RECOMMENDs a shared secret length of 160 bits."
- * The original value was 10 bytes (80 bits) this value has been increased to 20 (160 bits) with Joomla! 3.9.25
- *
- * @var integer
- */
- private $_secretLength = 20;
-
- /**
- * Timestep
- *
- * @var integer
- */
- private $_timeStep = 30;
-
- /**
- * Base32
- *
- * @var integer
- */
- private $_base32 = null;
-
- /**
- * Initialises an RFC6238-compatible TOTP generator. Please note that this
- * class does not implement the constraint in the last paragraph of §5.2
- * of RFC6238. It's up to you to ensure that the same user/device does not
- * retry validation within the same Time Step.
- *
- * @param int $timeStep The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
- * @param int $passCodeLength The generated passcode length. Default: 6 digits.
- * @param int $secretLength The length of the secret key. Default: 10 bytes (80 bits).
- * @param Object $base32 The base32 en/decrypter
- */
- public function __construct($timeStep = 30, $passCodeLength = 6, $secretLength = 10, $base32=null)
- {
- $this->_timeStep = $timeStep;
- $this->_passCodeLength = $passCodeLength;
- $this->_secretLength = $secretLength;
- $this->_pinModulo = pow(10, $this->_passCodeLength);
-
- if (\is_null($base32))
- {
- $this->_base32 = new Base32;
- }
- else
- {
- $this->_base32 = $base32;
- }
- }
-
- /**
- * Get the time period based on the $time timestamp and the Time Step
- * defined. If $time is skipped or set to null the current timestamp will
- * be used.
- *
- * @param int|null $time Timestamp
- *
- * @return integer The time period since the UNIX Epoch
- */
- public function getPeriod($time = null)
- {
- if (\is_null($time))
- {
- $time = time();
- }
-
- $period = floor($time / $this->_timeStep);
-
- return $period;
- }
-
- /**
- * Check is the given passcode $code is a valid TOTP generated using secret
- * key $secret
- *
- * @param string $secret The Base32-encoded secret key
- * @param string $code The passcode to check
- *
- * @return boolean True if the code is valid
- */
- public function checkCode($secret, $code)
- {
- $time = $this->getPeriod();
-
- for ($i = -1; $i <= 1; $i++)
- {
- if ($this->getCode($secret, ($time + $i) * $this->_timeStep) == $code)
- {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Gets the TOTP passcode for a given secret key $secret and a given UNIX
- * timestamp $time
- *
- * @param string $secret The Base32-encoded secret key
- * @param int $time UNIX timestamp
- *
- * @return string
- */
- public function getCode($secret, $time = null)
- {
- $period = $this->getPeriod($time);
- $secret = $this->_base32->decode($secret);
-
- $time = pack("N", $period);
- $time = str_pad($time, 8, \chr(0), STR_PAD_LEFT);
-
- $hash = hash_hmac('sha1', $time, $secret, true);
- $offset = \ord(substr($hash, -1));
- $offset = $offset & 0xF;
-
- $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
- $pinValue = str_pad($truncatedHash % $this->_pinModulo, $this->_passCodeLength, "0", STR_PAD_LEFT);
-
- return $pinValue;
- }
-
- /**
- * Extracts a part of a hash as an integer
- *
- * @param string $bytes The hash
- * @param string $start The char to start from (0 = first char)
- *
- * @return string
- */
- protected function hashToInt($bytes, $start)
- {
- $input = substr($bytes, $start, \strlen($bytes) - $start);
- $val2 = unpack("N", substr($input, 0, 4));
-
- return $val2[1];
- }
-
- /**
- * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
- *
- * @param string $user User
- * @param string $hostname Hostname
- * @param string $secret Secret string
- *
- * @return string
- */
- public function getUrl($user, $hostname, $secret)
- {
- $url = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
- $encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
- $encoderURL = $encoder . urlencode($url);
-
- return $encoderURL;
- }
-
- /**
- * Generates a (semi-)random Secret Key for TOTP generation
- *
- * @return string
- *
- * @note Since 3.9.25 we use the secure method "random_bytes" over the original insecure "rand" function.
- * The random_bytes function has been backported to outdated PHP versions by the core shipped library paragonie/random_compat
- */
- public function generateSecret()
- {
- $secret = random_bytes($this->_secretLength);
-
- return $this->_base32->encode($secret);
- }
+ /**
+ * Passcode length
+ *
+ * @var integer
+ */
+ private $_passCodeLength = 6;
+
+ /**
+ * Pin modulo
+ *
+ * @var integer
+ */
+ private $_pinModulo;
+
+ /**
+ * The length of the secret in bytes.
+ * RFC 4226: "The length of the shared secret MUST be at least 128 bits. This document RECOMMENDs a shared secret length of 160 bits."
+ * The original value was 10 bytes (80 bits) this value has been increased to 20 (160 bits) with Joomla! 3.9.25
+ *
+ * @var integer
+ */
+ private $_secretLength = 20;
+
+ /**
+ * Timestep
+ *
+ * @var integer
+ */
+ private $_timeStep = 30;
+
+ /**
+ * Base32
+ *
+ * @var integer
+ */
+ private $_base32 = null;
+
+ /**
+ * Initialises an RFC6238-compatible TOTP generator. Please note that this
+ * class does not implement the constraint in the last paragraph of §5.2
+ * of RFC6238. It's up to you to ensure that the same user/device does not
+ * retry validation within the same Time Step.
+ *
+ * @param int $timeStep The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
+ * @param int $passCodeLength The generated passcode length. Default: 6 digits.
+ * @param int $secretLength The length of the secret key. Default: 10 bytes (80 bits).
+ * @param Object $base32 The base32 en/decrypter
+ */
+ public function __construct($timeStep = 30, $passCodeLength = 6, $secretLength = 10, $base32 = null)
+ {
+ $this->_timeStep = $timeStep;
+ $this->_passCodeLength = $passCodeLength;
+ $this->_secretLength = $secretLength;
+ $this->_pinModulo = pow(10, $this->_passCodeLength);
+
+ if (\is_null($base32)) {
+ $this->_base32 = new Base32();
+ } else {
+ $this->_base32 = $base32;
+ }
+ }
+
+ /**
+ * Get the time period based on the $time timestamp and the Time Step
+ * defined. If $time is skipped or set to null the current timestamp will
+ * be used.
+ *
+ * @param int|null $time Timestamp
+ *
+ * @return integer The time period since the UNIX Epoch
+ */
+ public function getPeriod($time = null)
+ {
+ if (\is_null($time)) {
+ $time = time();
+ }
+
+ $period = floor($time / $this->_timeStep);
+
+ return $period;
+ }
+
+ /**
+ * Check is the given passcode $code is a valid TOTP generated using secret
+ * key $secret
+ *
+ * @param string $secret The Base32-encoded secret key
+ * @param string $code The passcode to check
+ *
+ * @return boolean True if the code is valid
+ */
+ public function checkCode($secret, $code)
+ {
+ $time = $this->getPeriod();
+
+ for ($i = -1; $i <= 1; $i++) {
+ if ($this->getCode($secret, ($time + $i) * $this->_timeStep) == $code) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the TOTP passcode for a given secret key $secret and a given UNIX
+ * timestamp $time
+ *
+ * @param string $secret The Base32-encoded secret key
+ * @param int $time UNIX timestamp
+ *
+ * @return string
+ */
+ public function getCode($secret, $time = null)
+ {
+ $period = $this->getPeriod($time);
+ $secret = $this->_base32->decode($secret);
+
+ $time = pack("N", $period);
+ $time = str_pad($time, 8, \chr(0), STR_PAD_LEFT);
+
+ $hash = hash_hmac('sha1', $time, $secret, true);
+ $offset = \ord(substr($hash, -1));
+ $offset = $offset & 0xF;
+
+ $truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
+ $pinValue = str_pad($truncatedHash % $this->_pinModulo, $this->_passCodeLength, "0", STR_PAD_LEFT);
+
+ return $pinValue;
+ }
+
+ /**
+ * Extracts a part of a hash as an integer
+ *
+ * @param string $bytes The hash
+ * @param string $start The char to start from (0 = first char)
+ *
+ * @return string
+ */
+ protected function hashToInt($bytes, $start)
+ {
+ $input = substr($bytes, $start, \strlen($bytes) - $start);
+ $val2 = unpack("N", substr($input, 0, 4));
+
+ return $val2[1];
+ }
+
+ /**
+ * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
+ *
+ * @param string $user User
+ * @param string $hostname Hostname
+ * @param string $secret Secret string
+ *
+ * @return string
+ */
+ public function getUrl($user, $hostname, $secret)
+ {
+ $url = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
+ $encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
+ $encoderURL = $encoder . urlencode($url);
+
+ return $encoderURL;
+ }
+
+ /**
+ * Generates a (semi-)random Secret Key for TOTP generation
+ *
+ * @return string
+ *
+ * @note Since 3.9.25 we use the secure method "random_bytes" over the original insecure "rand" function.
+ * The random_bytes function has been backported to outdated PHP versions by the core shipped library paragonie/random_compat
+ */
+ public function generateSecret()
+ {
+ $secret = random_bytes($this->_secretLength);
+
+ return $this->_base32->encode($secret);
+ }
}
diff --git a/libraries/src/Environment/Browser.php b/libraries/src/Environment/Browser.php
index 0a3acdd115b61..8142743c7f692 100644
--- a/libraries/src/Environment/Browser.php
+++ b/libraries/src/Environment/Browser.php
@@ -1,4 +1,5 @@
match($userAgent, $accept);
- }
-
- /**
- * Returns the global Browser object, only creating it
- * if it doesn't already exist.
- *
- * @param string $userAgent The browser string to parse.
- * @param string $accept The HTTP_ACCEPT settings to use.
- *
- * @return Browser The Browser object.
- *
- * @since 1.7.0
- */
- public static function getInstance($userAgent = null, $accept = null)
- {
- $signature = serialize(array($userAgent, $accept));
-
- if (empty(self::$instances[$signature]))
- {
- self::$instances[$signature] = new static($userAgent, $accept);
- }
-
- return self::$instances[$signature];
- }
-
- /**
- * Parses the user agent string and inititializes the object with
- * all the known features and quirks for the given browser.
- *
- * @param string $userAgent The browser string to parse.
- * @param string $accept The HTTP_ACCEPT settings to use.
- *
- * @return void
- *
- * @since 1.7.0
- */
- public function match($userAgent = null, $accept = null)
- {
- // Set our agent string.
- if (\is_null($userAgent))
- {
- if (isset($_SERVER['HTTP_USER_AGENT']))
- {
- $this->agent = trim($_SERVER['HTTP_USER_AGENT']);
- }
- }
- else
- {
- $this->agent = $userAgent;
- }
-
- $this->lowerAgent = strtolower($this->agent);
-
- // Set our accept string.
- if (\is_null($accept))
- {
- if (isset($_SERVER['HTTP_ACCEPT']))
- {
- $this->accept = strtolower(trim($_SERVER['HTTP_ACCEPT']));
- }
- }
- else
- {
- $this->accept = strtolower($accept);
- }
-
- if (!empty($this->agent))
- {
- $this->_setPlatform();
-
- /*
- * Determine if mobile. Note: Some Handhelds have their screen resolution in the
- * user agent string, which we can use to look for mobile agents.
- */
- if (strpos($this->agent, 'MOT-') !== false
- || strpos($this->lowerAgent, 'j-') !== false
- || preg_match('/(mobileexplorer|openwave|opera mini|opera mobi|operamini|avantgo|wap|elaine)/i', $this->agent)
- || preg_match('/(iPhone|iPod|iPad|Android|Mobile|Phone|BlackBerry|Xiino|Palmscape|palmsource)/i', $this->agent)
- || preg_match('/(Nokia|Ericsson|docomo|digital paths|portalmmm|CriOS[\/ ]([0-9.]+))/i', $this->agent)
- || preg_match('/(UP|UP.B|UP.L)/', $this->agent)
- || preg_match('/; (120x160|240x280|240x320|320x320)\)/', $this->agent))
- {
- $this->mobile = true;
- }
-
- /*
- * We have to check for Edge as the first browser, because Edge has something like:
- * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
- */
- if (preg_match('|Edge\/([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('edge');
-
- if (strpos($version[1], '.') !== false)
- {
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- else
- {
- $this->majorVersion = $version[1];
- $this->minorVersion = 0;
- }
- }
- /*
- * We have to check for Edge as the first browser, because Edge has something like:
- * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3738.0 Safari/537.36 Edg/75.0.107.0
- */
- elseif (preg_match('|Edg\/([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('edg');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- elseif (preg_match('|Opera[\/ ]([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('opera');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
-
- /*
- * Due to changes in Opera UA, we need to check Version/xx.yy,
- * but only if version is > 9.80. See: http://dev.opera.com/articles/view/opera-ua-string-changes/
- */
- if ($this->majorVersion == 9 && $this->minorVersion >= 80)
- {
- $this->identifyBrowserVersion();
- }
- }
-
- // Opera 15+
- elseif (preg_match('/OPR[\/ ]([0-9.]+)/', $this->agent, $version))
- {
- $this->setBrowser('opera');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- elseif (preg_match('/Chrome[\/ ]([0-9.]+)/i', $this->agent, $version)
- || preg_match('/CrMo[\/ ]([0-9.]+)/i', $this->agent, $version)
- || preg_match('/CriOS[\/ ]([0-9.]+)/i', $this->agent, $version))
- {
- $this->setBrowser('chrome');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- elseif (strpos($this->lowerAgent, 'elaine/') !== false
- || strpos($this->lowerAgent, 'palmsource') !== false
- || strpos($this->lowerAgent, 'digital paths') !== false)
- {
- $this->setBrowser('palm');
- }
- elseif (preg_match('/MSIE ([0-9.]+)/i', $this->agent, $version)
- || preg_match('/IE ([0-9.]+)/i', $this->agent, $version)
- || preg_match('/Internet Explorer[\/ ]([0-9.]+)/i', $this->agent, $version)
- || preg_match('/Trident\/.*rv:([0-9.]+)/i', $this->agent, $version))
- {
- $this->setBrowser('msie');
-
- // Special case for IE 11+
- if (strpos($version[0], 'Trident') !== false && strpos($version[0], 'rv:') !== false)
- {
- preg_match('|rv:([0-9.]+)|', $this->agent, $version);
- }
-
- if (strpos($version[1], '.') !== false)
- {
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- else
- {
- $this->majorVersion = $version[1];
- $this->minorVersion = 0;
- }
- }
- elseif (preg_match('|amaya\/([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('amaya');
- $this->majorVersion = $version[1];
-
- if (isset($version[2]))
- {
- $this->minorVersion = $version[2];
- }
- }
- elseif (preg_match('|ANTFresco\/([0-9]+)|', $this->agent, $version))
- {
- $this->setBrowser('fresco');
- }
- elseif (strpos($this->lowerAgent, 'avantgo') !== false)
- {
- $this->setBrowser('avantgo');
- }
- elseif (preg_match('|[Kk]onqueror\/([0-9]+)|', $this->agent, $version) || preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->agent, $version))
- {
- // Konqueror and Apple's Safari both use the KHTML rendering engine.
- $this->setBrowser('konqueror');
- $this->majorVersion = $version[1];
-
- if (isset($version[2]))
- {
- $this->minorVersion = $version[2];
- }
-
- if (strpos($this->agent, 'Safari') !== false && $this->majorVersion >= 60)
- {
- // Safari.
- $this->setBrowser('safari');
- $this->identifyBrowserVersion();
- }
- }
- elseif (preg_match('|Firefox\/([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('firefox');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- elseif (preg_match('|Lynx\/([0-9]+)|', $this->agent, $version))
- {
- $this->setBrowser('lynx');
- }
- elseif (preg_match('|Links \(([0-9]+)|', $this->agent, $version))
- {
- $this->setBrowser('links');
- }
- elseif (preg_match('|HotJava\/([0-9]+)|', $this->agent, $version))
- {
- $this->setBrowser('hotjava');
- }
- elseif (strpos($this->agent, 'UP/') !== false || strpos($this->agent, 'UP.B') !== false || strpos($this->agent, 'UP.L') !== false)
- {
- $this->setBrowser('up');
- }
- elseif (strpos($this->agent, 'Xiino/') !== false)
- {
- $this->setBrowser('xiino');
- }
- elseif (strpos($this->agent, 'Palmscape/') !== false)
- {
- $this->setBrowser('palmscape');
- }
- elseif (strpos($this->agent, 'Nokia') !== false)
- {
- $this->setBrowser('nokia');
- }
- elseif (strpos($this->agent, 'Ericsson') !== false)
- {
- $this->setBrowser('ericsson');
- }
- elseif (strpos($this->lowerAgent, 'wap') !== false)
- {
- $this->setBrowser('wap');
- }
- elseif (strpos($this->lowerAgent, 'docomo') !== false || strpos($this->lowerAgent, 'portalmmm') !== false)
- {
- $this->setBrowser('imode');
- }
- elseif (strpos($this->agent, 'BlackBerry') !== false)
- {
- $this->setBrowser('blackberry');
- }
- elseif (strpos($this->agent, 'MOT-') !== false)
- {
- $this->setBrowser('motorola');
- }
- elseif (strpos($this->lowerAgent, 'j-') !== false)
- {
- $this->setBrowser('mml');
- }
- elseif (preg_match('|Mozilla\/([0-9.]+)|', $this->agent, $version))
- {
- $this->setBrowser('mozilla');
-
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
- }
- }
- }
-
- /**
- * Match the platform of the browser.
- *
- * This is a pretty simplistic implementation, but it's intended
- * to let us tell what line breaks to send, so it's good enough
- * for its purpose.
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected function _setPlatform()
- {
- if (strpos($this->lowerAgent, 'wind') !== false)
- {
- $this->platform = 'win';
- }
- elseif (strpos($this->lowerAgent, 'mac') !== false)
- {
- $this->platform = 'mac';
- }
- else
- {
- $this->platform = 'unix';
- }
- }
-
- /**
- * Return the currently matched platform.
- *
- * @return string The user's platform.
- *
- * @since 1.7.0
- */
- public function getPlatform()
- {
- return $this->platform;
- }
-
- /**
- * Set browser version, not by engine version
- * Fallback to use when no other method identify the engine version
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected function identifyBrowserVersion()
- {
- if (preg_match('|Version[/ ]([0-9.]+)|', $this->agent, $version))
- {
- list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
-
- return;
- }
-
- // Can't identify browser version
- $this->majorVersion = 0;
- $this->minorVersion = 0;
- }
-
- /**
- * Sets the current browser.
- *
- * @param string $browser The browser to set as current.
- *
- * @return void
- *
- * @since 1.7.0
- */
- public function setBrowser($browser)
- {
- $this->browser = $browser;
- }
-
- /**
- * Retrieve the current browser.
- *
- * @return string The current browser.
- *
- * @since 1.7.0
- */
- public function getBrowser()
- {
- return $this->browser;
- }
-
- /**
- * Retrieve the current browser's major version.
- *
- * @return integer The current browser's major version
- *
- * @since 1.7.0
- */
- public function getMajor()
- {
- return $this->majorVersion;
- }
-
- /**
- * Retrieve the current browser's minor version.
- *
- * @return integer The current browser's minor version.
- *
- * @since 1.7.0
- */
- public function getMinor()
- {
- return $this->minorVersion;
- }
-
- /**
- * Retrieve the current browser's version.
- *
- * @return string The current browser's version.
- *
- * @since 1.7.0
- */
- public function getVersion()
- {
- return $this->majorVersion . '.' . $this->minorVersion;
- }
-
- /**
- * Return the full browser agent string.
- *
- * @return string The browser agent string
- *
- * @since 1.7.0
- */
- public function getAgentString()
- {
- return $this->agent;
- }
-
- /**
- * Returns the server protocol in use on the current server.
- *
- * @return string The HTTP server protocol version.
- *
- * @since 1.7.0
- */
- public function getHTTPProtocol()
- {
- if (isset($_SERVER['SERVER_PROTOCOL']))
- {
- if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'], '/')))
- {
- return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1);
- }
- }
- }
-
- /**
- * Determines if a browser can display a given MIME type.
- *
- * Note that image/jpeg and image/pjpeg *appear* to be the same
- * entity, but Mozilla doesn't seem to want to accept the latter.
- * For our purposes, we will treat them the same.
- *
- * @param string $mimetype The MIME type to check.
- *
- * @return boolean True if the browser can display the MIME type.
- *
- * @since 1.7.0
- */
- public function isViewable($mimetype)
- {
- $mimetype = strtolower($mimetype);
- list($type, $subtype) = explode('/', $mimetype);
-
- if (!empty($this->accept))
- {
- $wildcard_match = false;
-
- if (strpos($this->accept, $mimetype) !== false)
- {
- return true;
- }
-
- if (strpos($this->accept, '*/*') !== false)
- {
- $wildcard_match = true;
-
- if ($type !== 'image')
- {
- return true;
- }
- }
-
- // Deal with Mozilla pjpeg/jpeg issue
- if ($this->isBrowser('mozilla') && ($mimetype === 'image/pjpeg') && (strpos($this->accept, 'image/jpeg') !== false))
- {
- return true;
- }
-
- if (!$wildcard_match)
- {
- return false;
- }
- }
-
- if ($type !== 'image')
- {
- return false;
- }
-
- return \in_array($subtype, $this->images);
- }
-
- /**
- * Determine if the given browser is the same as the current.
- *
- * @param string $browser The browser to check.
- *
- * @return boolean Is the given browser the same as the current?
- *
- * @since 1.7.0
- */
- public function isBrowser($browser)
- {
- return $this->browser === $browser;
- }
-
- /**
- * Determines if the browser is a robot or not.
- *
- * @return boolean True if browser is a known robot.
- *
- * @since 1.7.0
- */
- public function isRobot()
- {
- foreach ($this->robots as $robot)
- {
- if (preg_match('/' . $robot . '/', $this->agent))
- {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Determines if the browser is mobile version or not.
- *
- * @return boolean True if browser is a known mobile version.
- *
- * @since 1.7.0
- */
- public function isMobile()
- {
- return $this->mobile;
- }
+ /**
+ * @var integer Major version number
+ * @since 3.0.0
+ */
+ protected $majorVersion = 0;
+
+ /**
+ * @var integer Minor version number
+ * @since 3.0.0
+ */
+ protected $minorVersion = 0;
+
+ /**
+ * @var string Browser name.
+ * @since 3.0.0
+ */
+ protected $browser = '';
+
+ /**
+ * @var string Full user agent string.
+ * @since 3.0.0
+ */
+ protected $agent = '';
+
+ /**
+ * @var string Lower-case user agent string
+ * @since 3.0.0
+ */
+ protected $lowerAgent = '';
+
+ /**
+ * @var string HTTP_ACCEPT string.
+ * @since 3.0.0
+ */
+ protected $accept = '';
+
+ /**
+ * @var array Parsed HTTP_ACCEPT string
+ * @since 3.0.0
+ */
+ protected $acceptParsed = array();
+
+ /**
+ * @var string Platform the browser is running on
+ * @since 3.0.0
+ */
+ protected $platform = '';
+
+ /**
+ * @var array Known robots.
+ * @since 3.0.0
+ */
+ protected $robots = array(
+ 'Googlebot\/',
+ 'Googlebot-Mobile',
+ 'Googlebot-Image',
+ 'Googlebot-News',
+ 'Googlebot-Video',
+ 'AdsBot-Google([^-]|$)',
+ 'AdsBot-Google-Mobile',
+ 'Feedfetcher-Google',
+ 'Mediapartners-Google',
+ 'Mediapartners \(Googlebot\)',
+ 'APIs-Google',
+ 'bingbot',
+ 'Slurp',
+ '[wW]get',
+ 'curl',
+ 'LinkedInBot',
+ 'Python-urllib',
+ 'python-requests',
+ 'libwww',
+ 'httpunit',
+ 'nutch',
+ 'Go-http-client',
+ 'phpcrawl',
+ 'msnbot',
+ 'jyxobot',
+ 'FAST-WebCrawler',
+ 'FAST Enterprise Crawler',
+ 'BIGLOTRON',
+ 'Teoma',
+ 'convera',
+ 'seekbot',
+ 'Gigabot',
+ 'Gigablast',
+ 'exabot',
+ 'ia_archiver',
+ 'GingerCrawler',
+ 'webmon ',
+ 'HTTrack',
+ 'grub.org',
+ 'UsineNouvelleCrawler',
+ 'antibot',
+ 'netresearchserver',
+ 'speedy',
+ 'fluffy',
+ 'bibnum.bnf',
+ 'findlink',
+ 'msrbot',
+ 'panscient',
+ 'yacybot',
+ 'AISearchBot',
+ 'ips-agent',
+ 'tagoobot',
+ 'MJ12bot',
+ 'woriobot',
+ 'yanga',
+ 'buzzbot',
+ 'mlbot',
+ 'YandexBot',
+ 'yandex.com\/bots',
+ 'purebot',
+ 'Linguee Bot',
+ 'CyberPatrol',
+ 'voilabot',
+ 'Baiduspider',
+ 'citeseerxbot',
+ 'spbot',
+ 'twengabot',
+ 'postrank',
+ 'turnitinbot',
+ 'scribdbot',
+ 'page2rss',
+ 'sitebot',
+ 'linkdex',
+ 'Adidxbot',
+ 'blekkobot',
+ 'ezooms',
+ 'dotbot',
+ 'Mail.RU_Bot',
+ 'discobot',
+ 'heritrix',
+ 'findthatfile',
+ 'europarchive.org',
+ 'NerdByNature.Bot',
+ 'sistrix crawler',
+ 'Ahrefs(Bot|SiteAudit)',
+ 'fuelbot',
+ 'CrunchBot',
+ 'centurybot9',
+ 'IndeedBot',
+ 'mappydata',
+ 'woobot',
+ 'ZoominfoBot',
+ 'PrivacyAwareBot',
+ 'Multiviewbot',
+ 'SWIMGBot',
+ 'Grobbot',
+ 'eright',
+ 'Apercite',
+ 'semanticbot',
+ 'Aboundex',
+ 'domaincrawler',
+ 'wbsearchbot',
+ 'summify',
+ 'CCBot',
+ 'edisterbot',
+ 'seznambot',
+ 'ec2linkfinder',
+ 'gslfbot',
+ 'aiHitBot',
+ 'intelium_bot',
+ 'facebookexternalhit',
+ 'Yeti',
+ 'RetrevoPageAnalyzer',
+ 'lb-spider',
+ 'Sogou',
+ 'lssbot',
+ 'careerbot',
+ 'wotbox',
+ 'wocbot',
+ 'ichiro',
+ 'DuckDuckBot',
+ 'lssrocketcrawler',
+ 'drupact',
+ 'webcompanycrawler',
+ 'acoonbot',
+ 'openindexspider',
+ 'gnam gnam spider',
+ 'web-archive-net.com.bot',
+ 'backlinkcrawler',
+ 'coccoc',
+ 'integromedb',
+ 'content crawler spider',
+ 'toplistbot',
+ 'it2media-domain-crawler',
+ 'ip-web-crawler.com',
+ 'siteexplorer.info',
+ 'elisabot',
+ 'proximic',
+ 'changedetection',
+ 'arabot',
+ 'WeSEE:Search',
+ 'niki-bot',
+ 'CrystalSemanticsBot',
+ 'rogerbot',
+ '360Spider',
+ 'psbot',
+ 'InterfaxScanBot',
+ 'CC Metadata Scaper',
+ 'g00g1e.net',
+ 'GrapeshotCrawler',
+ 'urlappendbot',
+ 'brainobot',
+ 'fr-crawler',
+ 'binlar',
+ 'SimpleCrawler',
+ 'Twitterbot',
+ 'cXensebot',
+ 'smtbot',
+ 'bnf.fr_bot',
+ 'A6-Indexer',
+ 'ADmantX',
+ 'Facebot',
+ 'OrangeBot\/',
+ 'memorybot',
+ 'AdvBot',
+ 'MegaIndex',
+ 'SemanticScholarBot',
+ 'ltx71',
+ 'nerdybot',
+ 'xovibot',
+ 'BUbiNG',
+ 'Qwantify',
+ 'archive.org_bot',
+ 'Applebot',
+ 'TweetmemeBot',
+ 'crawler4j',
+ 'findxbot',
+ 'S[eE][mM]rushBot',
+ 'yoozBot',
+ 'lipperhey',
+ 'Y!J',
+ 'Domain Re-Animator Bot',
+ 'AddThis',
+ 'Screaming Frog SEO Spider',
+ 'MetaURI',
+ 'Scrapy',
+ 'Livelap[bB]ot',
+ 'OpenHoseBot',
+ 'CapsuleChecker',
+ 'collection@infegy.com',
+ 'IstellaBot',
+ 'DeuSu\/',
+ 'betaBot',
+ 'Cliqzbot\/',
+ 'MojeekBot\/',
+ 'netEstate NE Crawler',
+ 'SafeSearch microdata crawler',
+ 'Gluten Free Crawler\/',
+ 'Sonic',
+ 'Sysomos',
+ 'Trove',
+ 'deadlinkchecker',
+ 'Slack-ImgProxy',
+ 'Embedly',
+ 'RankActiveLinkBot',
+ 'iskanie',
+ 'SafeDNSBot',
+ 'SkypeUriPreview',
+ 'Veoozbot',
+ 'Slackbot',
+ 'redditbot',
+ 'datagnionbot',
+ 'Google-Adwords-Instant',
+ 'adbeat_bot',
+ 'WhatsApp',
+ 'contxbot',
+ 'pinterest',
+ 'electricmonk',
+ 'GarlikCrawler',
+ 'BingPreview\/',
+ 'vebidoobot',
+ 'FemtosearchBot',
+ 'Yahoo Link Preview',
+ 'MetaJobBot',
+ 'DomainStatsBot',
+ 'mindUpBot',
+ 'Daum\/',
+ 'Jugendschutzprogramm-Crawler',
+ 'Xenu Link Sleuth',
+ 'Pcore-HTTP',
+ 'moatbot',
+ 'KosmioBot',
+ 'pingdom',
+ 'PhantomJS',
+ 'Gowikibot',
+ 'PiplBot',
+ 'Discordbot',
+ 'TelegramBot',
+ 'Jetslide',
+ 'newsharecounts',
+ 'James BOT',
+ 'Barkrowler',
+ 'TinEye',
+ 'SocialRankIOBot',
+ 'trendictionbot',
+ 'Ocarinabot',
+ 'epicbot',
+ 'Primalbot',
+ 'DuckDuckGo-Favicons-Bot',
+ 'GnowitNewsbot',
+ 'Leikibot',
+ 'LinkArchiver',
+ 'YaK\/',
+ 'PaperLiBot',
+ 'Digg Deeper',
+ 'dcrawl',
+ 'Snacktory',
+ 'AndersPinkBot',
+ 'Fyrebot',
+ 'EveryoneSocialBot',
+ 'Mediatoolkitbot',
+ 'Luminator-robots',
+ 'ExtLinksBot',
+ 'SurveyBot',
+ 'NING\/',
+ 'okhttp',
+ 'Nuzzel',
+ 'omgili',
+ 'PocketParser',
+ 'YisouSpider',
+ 'um-LN',
+ 'ToutiaoSpider',
+ 'MuckRack',
+ 'Jamie\'s Spider',
+ 'AHC\/',
+ 'NetcraftSurveyAgent',
+ 'Laserlikebot',
+ 'Apache-HttpClient',
+ 'AppEngine-Google',
+ 'Jetty',
+ 'Upflow',
+ 'Thinklab',
+ 'Traackr.com',
+ 'Twurly',
+ 'Mastodon',
+ 'http_get',
+ 'DnyzBot',
+ 'botify',
+ '007ac9 Crawler',
+ 'BehloolBot',
+ 'BrandVerity',
+ 'check_http',
+ 'BDCbot',
+ 'ZumBot',
+ 'EZID',
+ 'ICC-Crawler',
+ 'ArchiveBot',
+ '^LCC ',
+ 'filterdb.iss.net\/crawler',
+ 'BLP_bbot',
+ 'BomboraBot',
+ 'Buck\/',
+ 'Companybook-Crawler',
+ 'Genieo',
+ 'magpie-crawler',
+ 'MeltwaterNews',
+ 'Moreover',
+ 'newspaper\/',
+ 'ScoutJet',
+ '(^| )sentry\/',
+ 'StorygizeBot',
+ 'UptimeRobot',
+ 'OutclicksBot',
+ 'seoscanners',
+ 'Hatena',
+ 'Google Web Preview',
+ 'MauiBot',
+ 'AlphaBot',
+ 'SBL-BOT',
+ 'IAS crawler',
+ 'adscanner',
+ 'Netvibes',
+ 'acapbot',
+ 'Baidu-YunGuanCe',
+ 'bitlybot',
+ 'blogmuraBot',
+ 'Bot.AraTurka.com',
+ 'bot-pge.chlooe.com',
+ 'BoxcarBot',
+ 'BTWebClient',
+ 'ContextAd Bot',
+ 'Digincore bot',
+ 'Disqus',
+ 'Feedly',
+ 'Fetch\/',
+ 'Fever',
+ 'Flamingo_SearchEngine',
+ 'FlipboardProxy',
+ 'g2reader-bot',
+ 'imrbot',
+ 'K7MLWCBot',
+ 'Kemvibot',
+ 'Landau-Media-Spider',
+ 'linkapediabot',
+ 'vkShare',
+ 'Siteimprove.com',
+ 'BLEXBot\/',
+ 'DareBoost',
+ 'ZuperlistBot\/',
+ 'Miniflux\/',
+ 'Feedspotbot\/',
+ 'Diffbot\/',
+ 'SEOkicks',
+ 'tracemyfile',
+ 'Nimbostratus-Bot',
+ 'zgrab',
+ 'PR-CY.RU',
+ 'AdsTxtCrawler',
+ 'Datafeedwatch',
+ 'Zabbix',
+ 'TangibleeBot',
+ 'google-xrawler',
+ 'axios',
+ 'Amazon CloudFront',
+ 'Pulsepoint',
+ );
+
+ /**
+ * @var boolean Is this a mobile browser?
+ * @since 3.0.0
+ */
+ protected $mobile = false;
+
+ /**
+ * List of viewable image MIME subtypes.
+ * This list of viewable images works for IE and Netscape/Mozilla.
+ *
+ * @var array
+ * @since 3.0.0
+ */
+ protected $images = array('jpeg', 'gif', 'png', 'pjpeg', 'x-png', 'bmp');
+
+ /**
+ * @var array Browser instances container.
+ * @since 1.7.3
+ */
+ protected static $instances = array();
+
+ /**
+ * Create a browser instance (constructor).
+ *
+ * @param string $userAgent The browser string to parse.
+ * @param string $accept The HTTP_ACCEPT settings to use.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($userAgent = null, $accept = null)
+ {
+ $this->match($userAgent, $accept);
+ }
+
+ /**
+ * Returns the global Browser object, only creating it
+ * if it doesn't already exist.
+ *
+ * @param string $userAgent The browser string to parse.
+ * @param string $accept The HTTP_ACCEPT settings to use.
+ *
+ * @return Browser The Browser object.
+ *
+ * @since 1.7.0
+ */
+ public static function getInstance($userAgent = null, $accept = null)
+ {
+ $signature = serialize(array($userAgent, $accept));
+
+ if (empty(self::$instances[$signature])) {
+ self::$instances[$signature] = new static($userAgent, $accept);
+ }
+
+ return self::$instances[$signature];
+ }
+
+ /**
+ * Parses the user agent string and inititializes the object with
+ * all the known features and quirks for the given browser.
+ *
+ * @param string $userAgent The browser string to parse.
+ * @param string $accept The HTTP_ACCEPT settings to use.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ public function match($userAgent = null, $accept = null)
+ {
+ // Set our agent string.
+ if (\is_null($userAgent)) {
+ if (isset($_SERVER['HTTP_USER_AGENT'])) {
+ $this->agent = trim($_SERVER['HTTP_USER_AGENT']);
+ }
+ } else {
+ $this->agent = $userAgent;
+ }
+
+ $this->lowerAgent = strtolower($this->agent);
+
+ // Set our accept string.
+ if (\is_null($accept)) {
+ if (isset($_SERVER['HTTP_ACCEPT'])) {
+ $this->accept = strtolower(trim($_SERVER['HTTP_ACCEPT']));
+ }
+ } else {
+ $this->accept = strtolower($accept);
+ }
+
+ if (!empty($this->agent)) {
+ $this->_setPlatform();
+
+ /*
+ * Determine if mobile. Note: Some Handhelds have their screen resolution in the
+ * user agent string, which we can use to look for mobile agents.
+ */
+ if (
+ strpos($this->agent, 'MOT-') !== false
+ || strpos($this->lowerAgent, 'j-') !== false
+ || preg_match('/(mobileexplorer|openwave|opera mini|opera mobi|operamini|avantgo|wap|elaine)/i', $this->agent)
+ || preg_match('/(iPhone|iPod|iPad|Android|Mobile|Phone|BlackBerry|Xiino|Palmscape|palmsource)/i', $this->agent)
+ || preg_match('/(Nokia|Ericsson|docomo|digital paths|portalmmm|CriOS[\/ ]([0-9.]+))/i', $this->agent)
+ || preg_match('/(UP|UP.B|UP.L)/', $this->agent)
+ || preg_match('/; (120x160|240x280|240x320|320x320)\)/', $this->agent)
+ ) {
+ $this->mobile = true;
+ }
+
+ /*
+ * We have to check for Edge as the first browser, because Edge has something like:
+ * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
+ */
+ if (preg_match('|Edge\/([0-9.]+)|', $this->agent, $version)) {
+ $this->setBrowser('edge');
+
+ if (strpos($version[1], '.') !== false) {
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } else {
+ $this->majorVersion = $version[1];
+ $this->minorVersion = 0;
+ }
+ } elseif (preg_match('|Edg\/([0-9.]+)|', $this->agent, $version)) {
+ /**
+ * We have to check for Edge as the first browser, because Edge has something like:
+ * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3738.0 Safari/537.36 Edg/75.0.107.0
+ */
+ $this->setBrowser('edg');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } elseif (preg_match('|Opera[\/ ]([0-9.]+)|', $this->agent, $version)) {
+ $this->setBrowser('opera');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+
+ /*
+ * Due to changes in Opera UA, we need to check Version/xx.yy,
+ * but only if version is > 9.80. See: http://dev.opera.com/articles/view/opera-ua-string-changes/
+ */
+ if ($this->majorVersion == 9 && $this->minorVersion >= 80) {
+ $this->identifyBrowserVersion();
+ }
+ } elseif (preg_match('/OPR[\/ ]([0-9.]+)/', $this->agent, $version)) {
+ // Opera 15+
+ $this->setBrowser('opera');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } elseif (
+ preg_match('/Chrome[\/ ]([0-9.]+)/i', $this->agent, $version)
+ || preg_match('/CrMo[\/ ]([0-9.]+)/i', $this->agent, $version)
+ || preg_match('/CriOS[\/ ]([0-9.]+)/i', $this->agent, $version)
+ ) {
+ $this->setBrowser('chrome');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } elseif (
+ strpos($this->lowerAgent, 'elaine/') !== false
+ || strpos($this->lowerAgent, 'palmsource') !== false
+ || strpos($this->lowerAgent, 'digital paths') !== false
+ ) {
+ $this->setBrowser('palm');
+ } elseif (
+ preg_match('/MSIE ([0-9.]+)/i', $this->agent, $version)
+ || preg_match('/IE ([0-9.]+)/i', $this->agent, $version)
+ || preg_match('/Internet Explorer[\/ ]([0-9.]+)/i', $this->agent, $version)
+ || preg_match('/Trident\/.*rv:([0-9.]+)/i', $this->agent, $version)
+ ) {
+ $this->setBrowser('msie');
+
+ // Special case for IE 11+
+ if (strpos($version[0], 'Trident') !== false && strpos($version[0], 'rv:') !== false) {
+ preg_match('|rv:([0-9.]+)|', $this->agent, $version);
+ }
+
+ if (strpos($version[1], '.') !== false) {
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } else {
+ $this->majorVersion = $version[1];
+ $this->minorVersion = 0;
+ }
+ } elseif (preg_match('|amaya\/([0-9.]+)|', $this->agent, $version)) {
+ $this->setBrowser('amaya');
+ $this->majorVersion = $version[1];
+
+ if (isset($version[2])) {
+ $this->minorVersion = $version[2];
+ }
+ } elseif (preg_match('|ANTFresco\/([0-9]+)|', $this->agent, $version)) {
+ $this->setBrowser('fresco');
+ } elseif (strpos($this->lowerAgent, 'avantgo') !== false) {
+ $this->setBrowser('avantgo');
+ } elseif (preg_match('|[Kk]onqueror\/([0-9]+)|', $this->agent, $version) || preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->agent, $version)) {
+ // Konqueror and Apple's Safari both use the KHTML rendering engine.
+ $this->setBrowser('konqueror');
+ $this->majorVersion = $version[1];
+
+ if (isset($version[2])) {
+ $this->minorVersion = $version[2];
+ }
+
+ if (strpos($this->agent, 'Safari') !== false && $this->majorVersion >= 60) {
+ // Safari.
+ $this->setBrowser('safari');
+ $this->identifyBrowserVersion();
+ }
+ } elseif (preg_match('|Firefox\/([0-9.]+)|', $this->agent, $version)) {
+ $this->setBrowser('firefox');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ } elseif (preg_match('|Lynx\/([0-9]+)|', $this->agent, $version)) {
+ $this->setBrowser('lynx');
+ } elseif (preg_match('|Links \(([0-9]+)|', $this->agent, $version)) {
+ $this->setBrowser('links');
+ } elseif (preg_match('|HotJava\/([0-9]+)|', $this->agent, $version)) {
+ $this->setBrowser('hotjava');
+ } elseif (strpos($this->agent, 'UP/') !== false || strpos($this->agent, 'UP.B') !== false || strpos($this->agent, 'UP.L') !== false) {
+ $this->setBrowser('up');
+ } elseif (strpos($this->agent, 'Xiino/') !== false) {
+ $this->setBrowser('xiino');
+ } elseif (strpos($this->agent, 'Palmscape/') !== false) {
+ $this->setBrowser('palmscape');
+ } elseif (strpos($this->agent, 'Nokia') !== false) {
+ $this->setBrowser('nokia');
+ } elseif (strpos($this->agent, 'Ericsson') !== false) {
+ $this->setBrowser('ericsson');
+ } elseif (strpos($this->lowerAgent, 'wap') !== false) {
+ $this->setBrowser('wap');
+ } elseif (strpos($this->lowerAgent, 'docomo') !== false || strpos($this->lowerAgent, 'portalmmm') !== false) {
+ $this->setBrowser('imode');
+ } elseif (strpos($this->agent, 'BlackBerry') !== false) {
+ $this->setBrowser('blackberry');
+ } elseif (strpos($this->agent, 'MOT-') !== false) {
+ $this->setBrowser('motorola');
+ } elseif (strpos($this->lowerAgent, 'j-') !== false) {
+ $this->setBrowser('mml');
+ } elseif (preg_match('|Mozilla\/([0-9.]+)|', $this->agent, $version)) {
+ $this->setBrowser('mozilla');
+
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+ }
+ }
+ }
+
+ /**
+ * Match the platform of the browser.
+ *
+ * This is a pretty simplistic implementation, but it's intended
+ * to let us tell what line breaks to send, so it's good enough
+ * for its purpose.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected function _setPlatform()
+ {
+ if (strpos($this->lowerAgent, 'wind') !== false) {
+ $this->platform = 'win';
+ } elseif (strpos($this->lowerAgent, 'mac') !== false) {
+ $this->platform = 'mac';
+ } else {
+ $this->platform = 'unix';
+ }
+ }
+
+ /**
+ * Return the currently matched platform.
+ *
+ * @return string The user's platform.
+ *
+ * @since 1.7.0
+ */
+ public function getPlatform()
+ {
+ return $this->platform;
+ }
+
+ /**
+ * Set browser version, not by engine version
+ * Fallback to use when no other method identify the engine version
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected function identifyBrowserVersion()
+ {
+ if (preg_match('|Version[/ ]([0-9.]+)|', $this->agent, $version)) {
+ list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
+
+ return;
+ }
+
+ // Can't identify browser version
+ $this->majorVersion = 0;
+ $this->minorVersion = 0;
+ }
+
+ /**
+ * Sets the current browser.
+ *
+ * @param string $browser The browser to set as current.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ public function setBrowser($browser)
+ {
+ $this->browser = $browser;
+ }
+
+ /**
+ * Retrieve the current browser.
+ *
+ * @return string The current browser.
+ *
+ * @since 1.7.0
+ */
+ public function getBrowser()
+ {
+ return $this->browser;
+ }
+
+ /**
+ * Retrieve the current browser's major version.
+ *
+ * @return integer The current browser's major version
+ *
+ * @since 1.7.0
+ */
+ public function getMajor()
+ {
+ return $this->majorVersion;
+ }
+
+ /**
+ * Retrieve the current browser's minor version.
+ *
+ * @return integer The current browser's minor version.
+ *
+ * @since 1.7.0
+ */
+ public function getMinor()
+ {
+ return $this->minorVersion;
+ }
+
+ /**
+ * Retrieve the current browser's version.
+ *
+ * @return string The current browser's version.
+ *
+ * @since 1.7.0
+ */
+ public function getVersion()
+ {
+ return $this->majorVersion . '.' . $this->minorVersion;
+ }
+
+ /**
+ * Return the full browser agent string.
+ *
+ * @return string The browser agent string
+ *
+ * @since 1.7.0
+ */
+ public function getAgentString()
+ {
+ return $this->agent;
+ }
+
+ /**
+ * Returns the server protocol in use on the current server.
+ *
+ * @return string The HTTP server protocol version.
+ *
+ * @since 1.7.0
+ */
+ public function getHTTPProtocol()
+ {
+ if (isset($_SERVER['SERVER_PROTOCOL'])) {
+ if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'], '/'))) {
+ return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1);
+ }
+ }
+ }
+
+ /**
+ * Determines if a browser can display a given MIME type.
+ *
+ * Note that image/jpeg and image/pjpeg *appear* to be the same
+ * entity, but Mozilla doesn't seem to want to accept the latter.
+ * For our purposes, we will treat them the same.
+ *
+ * @param string $mimetype The MIME type to check.
+ *
+ * @return boolean True if the browser can display the MIME type.
+ *
+ * @since 1.7.0
+ */
+ public function isViewable($mimetype)
+ {
+ $mimetype = strtolower($mimetype);
+ list($type, $subtype) = explode('/', $mimetype);
+
+ if (!empty($this->accept)) {
+ $wildcard_match = false;
+
+ if (strpos($this->accept, $mimetype) !== false) {
+ return true;
+ }
+
+ if (strpos($this->accept, '*/*') !== false) {
+ $wildcard_match = true;
+
+ if ($type !== 'image') {
+ return true;
+ }
+ }
+
+ // Deal with Mozilla pjpeg/jpeg issue
+ if ($this->isBrowser('mozilla') && ($mimetype === 'image/pjpeg') && (strpos($this->accept, 'image/jpeg') !== false)) {
+ return true;
+ }
+
+ if (!$wildcard_match) {
+ return false;
+ }
+ }
+
+ if ($type !== 'image') {
+ return false;
+ }
+
+ return \in_array($subtype, $this->images);
+ }
+
+ /**
+ * Determine if the given browser is the same as the current.
+ *
+ * @param string $browser The browser to check.
+ *
+ * @return boolean Is the given browser the same as the current?
+ *
+ * @since 1.7.0
+ */
+ public function isBrowser($browser)
+ {
+ return $this->browser === $browser;
+ }
+
+ /**
+ * Determines if the browser is a robot or not.
+ *
+ * @return boolean True if browser is a known robot.
+ *
+ * @since 1.7.0
+ */
+ public function isRobot()
+ {
+ foreach ($this->robots as $robot) {
+ if (preg_match('/' . $robot . '/', $this->agent)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if the browser is mobile version or not.
+ *
+ * @return boolean True if browser is a known mobile version.
+ *
+ * @since 1.7.0
+ */
+ public function isMobile()
+ {
+ return $this->mobile;
+ }
}
diff --git a/libraries/src/Error/AbstractRenderer.php b/libraries/src/Error/AbstractRenderer.php
index 4a376fb7ac857..a3d92666cc7ad 100644
--- a/libraries/src/Error/AbstractRenderer.php
+++ b/libraries/src/Error/AbstractRenderer.php
@@ -1,4 +1,5 @@
document)
- {
- $this->document = $this->loadDocument();
- }
+ /**
+ * Retrieve the Document instance attached to this renderer
+ *
+ * @return Document
+ *
+ * @since 4.0.0
+ */
+ public function getDocument(): Document
+ {
+ // Load the document if not already
+ if (!$this->document) {
+ $this->document = $this->loadDocument();
+ }
- return $this->document;
- }
+ return $this->document;
+ }
- /**
- * Get a renderer instance for the given type
- *
- * @param string $type The type of renderer to fetch
- *
- * @return static
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException
- */
- public static function getRenderer(string $type)
- {
- // Build the class name
- $class = __NAMESPACE__ . '\\Renderer\\' . ucfirst(strtolower($type)) . 'Renderer';
+ /**
+ * Get a renderer instance for the given type
+ *
+ * @param string $type The type of renderer to fetch
+ *
+ * @return static
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ */
+ public static function getRenderer(string $type)
+ {
+ // Build the class name
+ $class = __NAMESPACE__ . '\\Renderer\\' . ucfirst(strtolower($type)) . 'Renderer';
- // First check if an object may exist in the container and prefer that over everything else
- if (Factory::getContainer()->has($class))
- {
- return Factory::getContainer()->get($class);
- }
+ // First check if an object may exist in the container and prefer that over everything else
+ if (Factory::getContainer()->has($class)) {
+ return Factory::getContainer()->get($class);
+ }
- // Next check if a local class exists and use that
- if (class_exists($class))
- {
- return new $class;
- }
+ // Next check if a local class exists and use that
+ if (class_exists($class)) {
+ return new $class();
+ }
- // 404 Resource Not Found
- throw new \InvalidArgumentException(sprintf('There is not an error renderer for the "%s" format.', $type));
- }
+ // 404 Resource Not Found
+ throw new \InvalidArgumentException(sprintf('There is not an error renderer for the "%s" format.', $type));
+ }
- /**
- * Create the Document object for this renderer
- *
- * @return Document
- *
- * @since 4.0.0
- */
- protected function loadDocument(): Document
- {
- $attributes = [
- 'charset' => 'utf-8',
- 'lineend' => 'unix',
- 'tab' => "\t",
- 'language' => 'en-GB',
- 'direction' => 'ltr',
- ];
+ /**
+ * Create the Document object for this renderer
+ *
+ * @return Document
+ *
+ * @since 4.0.0
+ */
+ protected function loadDocument(): Document
+ {
+ $attributes = [
+ 'charset' => 'utf-8',
+ 'lineend' => 'unix',
+ 'tab' => "\t",
+ 'language' => 'en-GB',
+ 'direction' => 'ltr',
+ ];
- // If there is a Language instance in Factory then let's pull the language and direction from its metadata
- if (Factory::$language)
- {
- $attributes['language'] = Factory::getLanguage()->getTag();
- $attributes['direction'] = Factory::getLanguage()->isRtl() ? 'rtl' : 'ltr';
- }
+ // If there is a Language instance in Factory then let's pull the language and direction from its metadata
+ if (Factory::$language) {
+ $attributes['language'] = Factory::getLanguage()->getTag();
+ $attributes['direction'] = Factory::getLanguage()->isRtl() ? 'rtl' : 'ltr';
+ }
- return Factory::getContainer()->get(FactoryInterface::class)->createDocument($this->type, $attributes);
- }
+ return Factory::getContainer()->get(FactoryInterface::class)->createDocument($this->type, $attributes);
+ }
}
diff --git a/libraries/src/Error/JsonApi/AuthenticationFailedExceptionHandler.php b/libraries/src/Error/JsonApi/AuthenticationFailedExceptionHandler.php
index f4efc068b004b..1f14e4eb3c23c 100644
--- a/libraries/src/Error/JsonApi/AuthenticationFailedExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/AuthenticationFailedExceptionHandler.php
@@ -1,4 +1,5 @@
'Forbidden'];
-
- $code = $e->getCode();
-
- if ($code)
- {
- $error['code'] = $code;
- }
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof AuthenticationFailed;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 401;
+ $error = ['title' => 'Forbidden'];
+
+ $code = $e->getCode();
+
+ if ($code) {
+ $error['code'] = $code;
+ }
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/CheckinCheckoutExceptionHandler.php b/libraries/src/Error/JsonApi/CheckinCheckoutExceptionHandler.php
index dad13f87edabd..06328cf2ee664 100644
--- a/libraries/src/Error/JsonApi/CheckinCheckoutExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/CheckinCheckoutExceptionHandler.php
@@ -1,4 +1,5 @@
getCode())
- {
- $status = $e->getCode();
- }
-
- $error = ['title' => $e->getMessage()];
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof CheckinCheckout;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 400;
+
+ if ($e->getCode()) {
+ $status = $e->getCode();
+ }
+
+ $error = ['title' => $e->getMessage()];
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/InvalidParameterExceptionHandler.php b/libraries/src/Error/JsonApi/InvalidParameterExceptionHandler.php
index cde000b86ff61..08e1c5ece39d2 100644
--- a/libraries/src/Error/JsonApi/InvalidParameterExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/InvalidParameterExceptionHandler.php
@@ -1,4 +1,5 @@
$e->getMessage()];
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 400;
+ $error = ['title' => $e->getMessage()];
- $code = $e->getCode();
+ $code = $e->getCode();
- if ($code)
- {
- $error['code'] = $code;
- }
+ if ($code) {
+ $error['code'] = $code;
+ }
- return new ResponseBag($status, [$error]);
- }
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/InvalidRouteExceptionHandler.php b/libraries/src/Error/JsonApi/InvalidRouteExceptionHandler.php
index cb57e1bd12e3d..4a0f3d974f1ac 100644
--- a/libraries/src/Error/JsonApi/InvalidRouteExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/InvalidRouteExceptionHandler.php
@@ -1,4 +1,5 @@
'Resource not found'];
-
- $code = $e->getCode();
-
- if ($code)
- {
- $error['code'] = $code;
- }
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof RouteNotFoundException;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 404;
+ $error = ['title' => 'Resource not found'];
+
+ $code = $e->getCode();
+
+ if ($code) {
+ $error['code'] = $code;
+ }
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/NotAcceptableExceptionHandler.php b/libraries/src/Error/JsonApi/NotAcceptableExceptionHandler.php
index 12dd0e4f03e4b..e7b4ba5757742 100644
--- a/libraries/src/Error/JsonApi/NotAcceptableExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/NotAcceptableExceptionHandler.php
@@ -1,4 +1,5 @@
'Not Acceptable'];
-
- $code = $e->getCode();
-
- if ($code)
- {
- $error['code'] = $code;
- }
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof NotAcceptable;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 406;
+ $error = ['title' => 'Not Acceptable'];
+
+ $code = $e->getCode();
+
+ if ($code) {
+ $error['code'] = $code;
+ }
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/NotAllowedExceptionHandler.php b/libraries/src/Error/JsonApi/NotAllowedExceptionHandler.php
index 8f0e4a5819fb4..44b6d129c3df8 100644
--- a/libraries/src/Error/JsonApi/NotAllowedExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/NotAllowedExceptionHandler.php
@@ -1,4 +1,5 @@
'Access Denied'];
-
- $code = $e->getCode();
-
- if ($code)
- {
- $error['code'] = $code;
- }
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof NotAllowed;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 403;
+ $error = ['title' => 'Access Denied'];
+
+ $code = $e->getCode();
+
+ if ($code) {
+ $error['code'] = $code;
+ }
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/ResourceNotFoundExceptionHandler.php b/libraries/src/Error/JsonApi/ResourceNotFoundExceptionHandler.php
index d65a34767801d..438200a44581c 100644
--- a/libraries/src/Error/JsonApi/ResourceNotFoundExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/ResourceNotFoundExceptionHandler.php
@@ -1,4 +1,5 @@
'Resource not found'];
-
- $code = $e->getCode();
-
- if ($code)
- {
- $error['code'] = $code;
- }
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof ResourceNotFound;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 404;
+ $error = ['title' => 'Resource not found'];
+
+ $code = $e->getCode();
+
+ if ($code) {
+ $error['code'] = $code;
+ }
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/SaveExceptionHandler.php b/libraries/src/Error/JsonApi/SaveExceptionHandler.php
index a8ae0a15dffef..23684b77a5dc9 100644
--- a/libraries/src/Error/JsonApi/SaveExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/SaveExceptionHandler.php
@@ -1,4 +1,5 @@
getCode())
- {
- $status = $e->getCode();
- }
-
- $error = [
- 'title' => $e->getMessage(),
- 'code' => $status,
- ];
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof Save;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 400;
+
+ if ($e->getCode()) {
+ $status = $e->getCode();
+ }
+
+ $error = [
+ 'title' => $e->getMessage(),
+ 'code' => $status,
+ ];
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/JsonApi/SendEmailExceptionHandler.php b/libraries/src/Error/JsonApi/SendEmailExceptionHandler.php
index 5cf7caf94bac0..0a010e422de8a 100644
--- a/libraries/src/Error/JsonApi/SendEmailExceptionHandler.php
+++ b/libraries/src/Error/JsonApi/SendEmailExceptionHandler.php
@@ -1,4 +1,5 @@
getCode())
- {
- $status = $e->getCode();
- }
-
- $error = ['title' => $e->getMessage()];
-
- return new ResponseBag($status, [$error]);
- }
+ /**
+ * If the exception handler is able to format a response for the provided exception,
+ * then the implementation should return true.
+ *
+ * @param \Exception $e The exception to be handled
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function manages(Exception $e)
+ {
+ return $e instanceof SendEmail;
+ }
+
+ /**
+ * Handle the provided exception.
+ *
+ * @param Exception $e The exception being handled
+ *
+ * @return \Tobscure\JsonApi\Exception\Handler\ResponseBag
+ *
+ * @since 4.0.0
+ */
+ public function handle(Exception $e)
+ {
+ $status = 400;
+
+ if ($e->getCode()) {
+ $status = $e->getCode();
+ }
+
+ $error = ['title' => $e->getMessage()];
+
+ return new ResponseBag($status, [$error]);
+ }
}
diff --git a/libraries/src/Error/Renderer/CliRenderer.php b/libraries/src/Error/Renderer/CliRenderer.php
index 41a759b94fad1..7763d43757fa4 100644
--- a/libraries/src/Error/Renderer/CliRenderer.php
+++ b/libraries/src/Error/Renderer/CliRenderer.php
@@ -1,4 +1,5 @@
getMessage() . PHP_EOL . $this->getTrace($error);
+ /**
+ * Render the error for the given object.
+ *
+ * @param \Throwable $error The error object to be rendered
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function render(\Throwable $error): string
+ {
+ $buffer = PHP_EOL . 'Error occurred: ' . $error->getMessage() . PHP_EOL . $this->getTrace($error);
- if ($prev = $error->getPrevious())
- {
- $buffer .= PHP_EOL . PHP_EOL . 'Previous Exception: ' . $prev->getMessage() . PHP_EOL . $this->getTrace($prev);
- }
+ if ($prev = $error->getPrevious()) {
+ $buffer .= PHP_EOL . PHP_EOL . 'Previous Exception: ' . $prev->getMessage() . PHP_EOL . $this->getTrace($prev);
+ }
- return $buffer;
- }
+ return $buffer;
+ }
- /**
- * Returns a trace for the given error.
- *
- * @param \Throwable $error The error
- *
- * @return string
- *
- * @since 4.0.0
- */
- private function getTrace(\Throwable $error): string
- {
- // Include the stack trace only if in debug mode
- if (!JDEBUG)
- {
- return '';
- }
+ /**
+ * Returns a trace for the given error.
+ *
+ * @param \Throwable $error The error
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ private function getTrace(\Throwable $error): string
+ {
+ // Include the stack trace only if in debug mode
+ if (!JDEBUG) {
+ return '';
+ }
- return PHP_EOL . $error->getTraceAsString() . PHP_EOL;
- }
+ return PHP_EOL . $error->getTraceAsString() . PHP_EOL;
+ }
}
diff --git a/libraries/src/Error/Renderer/FeedRenderer.php b/libraries/src/Error/Renderer/FeedRenderer.php
index 07402afc6341a..a57d9f5aee13f 100644
--- a/libraries/src/Error/Renderer/FeedRenderer.php
+++ b/libraries/src/Error/Renderer/FeedRenderer.php
@@ -1,4 +1,5 @@
getTemplate(true);
+ // Get the current template from the application
+ $template = $app->getTemplate(true);
- // Push the error object into the document
- $this->getDocument()->setError($error);
+ // Push the error object into the document
+ $this->getDocument()->setError($error);
- // Add registry file for the template asset
- $wa = $this->getDocument()->getWebAssetManager()->getRegistry();
+ // Add registry file for the template asset
+ $wa = $this->getDocument()->getWebAssetManager()->getRegistry();
- $wa->addTemplateRegistryFile($template->template, $app->getClientId());
+ $wa->addTemplateRegistryFile($template->template, $app->getClientId());
- if (!empty($template->parent))
- {
- $wa->addTemplateRegistryFile($template->parent, $app->getClientId());
- }
+ if (!empty($template->parent)) {
+ $wa->addTemplateRegistryFile($template->parent, $app->getClientId());
+ }
- if (ob_get_contents())
- {
- ob_end_clean();
- }
+ if (ob_get_contents()) {
+ ob_end_clean();
+ }
- $this->getDocument()->setTitle(Text::_('Error') . ': ' . $error->getCode());
+ $this->getDocument()->setTitle(Text::_('Error') . ': ' . $error->getCode());
- return $this->getDocument()->render(
- false,
- [
- 'template' => $template->template,
- 'directory' => JPATH_THEMES,
- 'debug' => JDEBUG,
- 'csp_nonce' => $app->get('csp_nonce'),
- 'templateInherits' => $template->parent,
- 'params' => $template->params,
- ]
- );
- }
+ return $this->getDocument()->render(
+ false,
+ [
+ 'template' => $template->template,
+ 'directory' => JPATH_THEMES,
+ 'debug' => JDEBUG,
+ 'csp_nonce' => $app->get('csp_nonce'),
+ 'templateInherits' => $template->parent,
+ 'params' => $template->params,
+ ]
+ );
+ }
}
diff --git a/libraries/src/Error/Renderer/JsonRenderer.php b/libraries/src/Error/Renderer/JsonRenderer.php
index 0b30cfc22a4cc..c08fbce810d17 100644
--- a/libraries/src/Error/Renderer/JsonRenderer.php
+++ b/libraries/src/Error/Renderer/JsonRenderer.php
@@ -1,4 +1,5 @@
true,
- 'code' => $error->getCode(),
- 'message' => $error->getMessage(),
- ];
+ /**
+ * Render the error page for the given object
+ *
+ * @param \Throwable $error The error object to be rendered
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function render(\Throwable $error): string
+ {
+ // Create our data object to be rendered
+ $data = [
+ 'error' => true,
+ 'code' => $error->getCode(),
+ 'message' => $error->getMessage(),
+ ];
- // Include the stack trace if in debug mode
- if (JDEBUG)
- {
- $data['trace'] = $error->getTraceAsString();
- }
+ // Include the stack trace if in debug mode
+ if (JDEBUG) {
+ $data['trace'] = $error->getTraceAsString();
+ }
- $app = Factory::getApplication();
+ $app = Factory::getApplication();
- if ($app instanceof WebApplicationInterface)
- {
- $errorCode = 500;
+ if ($app instanceof WebApplicationInterface) {
+ $errorCode = 500;
- if ($error->getCode() > 0)
- {
- $errorCode = $error->getCode();
- }
+ if ($error->getCode() > 0) {
+ $errorCode = $error->getCode();
+ }
- $app->setHeader('status', $errorCode);
- }
+ $app->setHeader('status', $errorCode);
+ }
- // Push the data object into the document
- $this->getDocument()->setBuffer(json_encode($data));
+ // Push the data object into the document
+ $this->getDocument()->setBuffer(json_encode($data));
- if (ob_get_contents())
- {
- ob_end_clean();
- }
+ if (ob_get_contents()) {
+ ob_end_clean();
+ }
- return $this->getDocument()->render();
- }
+ return $this->getDocument()->render();
+ }
}
diff --git a/libraries/src/Error/Renderer/JsonapiRenderer.php b/libraries/src/Error/Renderer/JsonapiRenderer.php
index 3de3121656c3f..6bdb7e8135379 100644
--- a/libraries/src/Error/Renderer/JsonapiRenderer.php
+++ b/libraries/src/Error/Renderer/JsonapiRenderer.php
@@ -1,4 +1,5 @@
registerHandler(new InvalidRouteExceptionHandler);
- $errors->registerHandler(new AuthenticationFailedExceptionHandler);
- $errors->registerHandler(new NotAcceptableExceptionHandler);
- $errors->registerHandler(new NotAllowedExceptionHandler);
- $errors->registerHandler(new InvalidParameterExceptionHandler);
- $errors->registerHandler(new ResourceNotFoundExceptionHandler);
- $errors->registerHandler(new SaveExceptionHandler);
- $errors->registerHandler(new CheckinCheckoutExceptionHandler);
- $errors->registerHandler(new SendEmailExceptionHandler);
- $errors->registerHandler(new FallbackExceptionHandler(JDEBUG));
+ $errors->registerHandler(new InvalidRouteExceptionHandler());
+ $errors->registerHandler(new AuthenticationFailedExceptionHandler());
+ $errors->registerHandler(new NotAcceptableExceptionHandler());
+ $errors->registerHandler(new NotAllowedExceptionHandler());
+ $errors->registerHandler(new InvalidParameterExceptionHandler());
+ $errors->registerHandler(new ResourceNotFoundExceptionHandler());
+ $errors->registerHandler(new SaveExceptionHandler());
+ $errors->registerHandler(new CheckinCheckoutExceptionHandler());
+ $errors->registerHandler(new SendEmailExceptionHandler());
+ $errors->registerHandler(new FallbackExceptionHandler(JDEBUG));
- $response = $errors->handle($error);
- }
- else
- {
- $code = 500;
- $errorInfo = ['code' => $code, 'title' => 'Internal server error'];
+ $response = $errors->handle($error);
+ } else {
+ $code = 500;
+ $errorInfo = ['code' => $code, 'title' => 'Internal server error'];
- if (JDEBUG)
- {
- $errorInfo['detail'] = (string) $error;
- }
+ if (JDEBUG) {
+ $errorInfo['detail'] = (string) $error;
+ }
- $response = new ResponseBag($code, $errorInfo);
- }
+ $response = new ResponseBag($code, $errorInfo);
+ }
- $this->getDocument()->setErrors($response->getErrors());
- $app = Factory::getApplication();
+ $this->getDocument()->setErrors($response->getErrors());
+ $app = Factory::getApplication();
- if ($app instanceof WebApplicationInterface)
- {
- $app->setHeader('status', $response->getStatus());
- }
+ if ($app instanceof WebApplicationInterface) {
+ $app->setHeader('status', $response->getStatus());
+ }
- if (ob_get_contents())
- {
- ob_end_clean();
- }
+ if (ob_get_contents()) {
+ ob_end_clean();
+ }
- return $this->getDocument()->render();
- }
+ return $this->getDocument()->render();
+ }
}
diff --git a/libraries/src/Error/Renderer/XmlRenderer.php b/libraries/src/Error/Renderer/XmlRenderer.php
index ea8a4b82ae343..8c572635db56c 100644
--- a/libraries/src/Error/Renderer/XmlRenderer.php
+++ b/libraries/src/Error/Renderer/XmlRenderer.php
@@ -1,4 +1,5 @@
openMemory();
- $xw->setIndent(true);
- $xw->setIndentString("\t");
- $xw->startDocument('1.0', 'UTF-8');
+ /**
+ * Render the error page for the given object
+ *
+ * @param \Throwable $error The error object to be rendered
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function render(\Throwable $error): string
+ {
+ // Create our data object to be rendered
+ $xw = new \XMLWriter();
+ $xw->openMemory();
+ $xw->setIndent(true);
+ $xw->setIndentString("\t");
+ $xw->startDocument('1.0', 'UTF-8');
- $xw->startElement('error');
+ $xw->startElement('error');
- $xw->writeElement('code', $error->getCode());
- $xw->writeElement('message', $error->getMessage());
+ $xw->writeElement('code', $error->getCode());
+ $xw->writeElement('message', $error->getMessage());
- // Include the stack trace if in debug mode
- if (JDEBUG)
- {
- $xw->writeElement('trace', $error->getTraceAsString());
- }
+ // Include the stack trace if in debug mode
+ if (JDEBUG) {
+ $xw->writeElement('trace', $error->getTraceAsString());
+ }
- // End error element
- $xw->endElement();
+ // End error element
+ $xw->endElement();
- // Push the data object into the document
- $this->getDocument()->setBuffer($xw->outputMemory(true));
+ // Push the data object into the document
+ $this->getDocument()->setBuffer($xw->outputMemory(true));
- if (ob_get_contents())
- {
- ob_end_clean();
- }
+ if (ob_get_contents()) {
+ ob_end_clean();
+ }
- return $this->getDocument()->render();
- }
+ return $this->getDocument()->render();
+ }
}
diff --git a/libraries/src/Error/RendererInterface.php b/libraries/src/Error/RendererInterface.php
index 0f2e99f5f3062..bc7304654e47e 100644
--- a/libraries/src/Error/RendererInterface.php
+++ b/libraries/src/Error/RendererInterface.php
@@ -1,4 +1,5 @@
- * will be used.
- *
- * @param string $eventName The name of the event, e.g. onTableBeforeLoad
- * @param array $arguments Additional arguments to pass to the event
- *
- * @return static
- *
- * @since 4.0.0
- * @throws BadMethodCallException If you do not provide a subject argument
- */
- public static function create(string $eventName, array $arguments = [])
- {
- // Get the class name from the arguments, if specified
- $eventClassName = '';
-
- if (isset($arguments['eventClass']))
- {
- $eventClassName = $arguments['eventClass'];
-
- unset($arguments['eventClass']);
- }
-
- /**
- * If the class name isn't set/found determine it from the event name, e.g. TableBeforeLoadEvent from
- * the onTableBeforeLoad event name.
- */
- if (empty($eventClassName) || !class_exists($eventClassName, true))
- {
- $bareName = strpos($eventName, 'on') === 0 ? substr($eventName, 2) : $eventName;
- $parts = Normalise::fromCamelCase($bareName, true);
- $eventClassName = __NAMESPACE__ . '\\' . ucfirst(array_shift($parts)) . '\\';
- $eventClassName .= implode('', $parts);
- $eventClassName .= 'Event';
- }
-
- // Make sure a non-empty subject argument exists and that it is an object
- if (!isset($arguments['subject']) || empty($arguments['subject']) || !\is_object($arguments['subject']))
- {
- throw new BadMethodCallException("No subject given for the $eventName event");
- }
-
- // Create and return the event object
- if (class_exists($eventClassName, true))
- {
- return new $eventClassName($eventName, $arguments);
- }
-
- /**
- * The detection code above failed. This is to be expected, it was written back when we only
- * had the Table events. It does not address most other core events. So, let's use our
- * fancier detection instead.
- */
- $eventClassName = self::getEventClassByEventName($eventName);
-
- if (!empty($eventClassName) && ($eventClassName !== Event::class))
- {
- return new $eventClassName($eventName, $arguments);
- }
-
- return new GenericEvent($eventName, $arguments);
- }
-
- /**
- * Constructor. Overridden to go through the argument setters.
- *
- * @param string $name The event name.
- * @param array $arguments The event arguments.
- *
- * @since 4.0.0
- */
- public function __construct(string $name, array $arguments = [])
- {
- parent::__construct($name, $arguments);
-
- $this->arguments = [];
-
- foreach ($arguments as $argumentName => $value)
- {
- $this->setArgument($argumentName, $value);
- }
- }
-
- /**
- * Get an event argument value. It will use a getter method if one exists. The getters have the signature:
- *
- * get($value): mixed
- *
- * where:
- *
- * $value is the value currently stored in the $arguments array of the event
- * It returns the value to return to the caller.
- *
- * @param string $name The argument name.
- * @param mixed $default The default value if not found.
- *
- * @return mixed The argument value or the default value.
- *
- * @since 4.0.0
- */
- public function getArgument($name, $default = null)
- {
- $methodName = 'get' . ucfirst($name);
-
- $value = parent::getArgument($name, $default);
-
- if (method_exists($this, $methodName))
- {
- return $this->{$methodName}($value);
- }
-
- return $value;
- }
-
- /**
- * Add argument to event. It will use a setter method if one exists. The setters have the signature:
- *
- * set($value): mixed
- *
- * where:
- *
- * $value is the value being set by the user
- * It returns the value to return to set in the $arguments array of the event.
- *
- * @param string $name Argument name.
- * @param mixed $value Value.
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setArgument($name, $value)
- {
- $methodName = 'set' . ucfirst($name);
-
- if (method_exists($this, $methodName))
- {
- $value = $this->{$methodName}($value);
- }
-
- return parent::setArgument($name, $value);
- }
+ use CoreEventAware;
+
+ /**
+ * Creates a new CMS event object for a given event name and subject. The following arguments must be given:
+ * subject object The subject of the event. This is the core object you are going to manipulate.
+ * eventClass string The Event class name. If you do not provide it Joomla\CMS\Events\
+ * will be used.
+ *
+ * @param string $eventName The name of the event, e.g. onTableBeforeLoad
+ * @param array $arguments Additional arguments to pass to the event
+ *
+ * @return static
+ *
+ * @since 4.0.0
+ * @throws BadMethodCallException If you do not provide a subject argument
+ */
+ public static function create(string $eventName, array $arguments = [])
+ {
+ // Get the class name from the arguments, if specified
+ $eventClassName = '';
+
+ if (isset($arguments['eventClass'])) {
+ $eventClassName = $arguments['eventClass'];
+
+ unset($arguments['eventClass']);
+ }
+
+ /**
+ * If the class name isn't set/found determine it from the event name, e.g. TableBeforeLoadEvent from
+ * the onTableBeforeLoad event name.
+ */
+ if (empty($eventClassName) || !class_exists($eventClassName, true)) {
+ $bareName = strpos($eventName, 'on') === 0 ? substr($eventName, 2) : $eventName;
+ $parts = Normalise::fromCamelCase($bareName, true);
+ $eventClassName = __NAMESPACE__ . '\\' . ucfirst(array_shift($parts)) . '\\';
+ $eventClassName .= implode('', $parts);
+ $eventClassName .= 'Event';
+ }
+
+ // Make sure a non-empty subject argument exists and that it is an object
+ if (!isset($arguments['subject']) || empty($arguments['subject']) || !\is_object($arguments['subject'])) {
+ throw new BadMethodCallException("No subject given for the $eventName event");
+ }
+
+ // Create and return the event object
+ if (class_exists($eventClassName, true)) {
+ return new $eventClassName($eventName, $arguments);
+ }
+
+ /**
+ * The detection code above failed. This is to be expected, it was written back when we only
+ * had the Table events. It does not address most other core events. So, let's use our
+ * fancier detection instead.
+ */
+ $eventClassName = self::getEventClassByEventName($eventName);
+
+ if (!empty($eventClassName) && ($eventClassName !== Event::class)) {
+ return new $eventClassName($eventName, $arguments);
+ }
+
+ return new GenericEvent($eventName, $arguments);
+ }
+
+ /**
+ * Constructor. Overridden to go through the argument setters.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $name, array $arguments = [])
+ {
+ parent::__construct($name, $arguments);
+
+ $this->arguments = [];
+
+ foreach ($arguments as $argumentName => $value) {
+ $this->setArgument($argumentName, $value);
+ }
+ }
+
+ /**
+ * Get an event argument value. It will use a getter method if one exists. The getters have the signature:
+ *
+ * get($value): mixed
+ *
+ * where:
+ *
+ * $value is the value currently stored in the $arguments array of the event
+ * It returns the value to return to the caller.
+ *
+ * @param string $name The argument name.
+ * @param mixed $default The default value if not found.
+ *
+ * @return mixed The argument value or the default value.
+ *
+ * @since 4.0.0
+ */
+ public function getArgument($name, $default = null)
+ {
+ $methodName = 'get' . ucfirst($name);
+
+ $value = parent::getArgument($name, $default);
+
+ if (method_exists($this, $methodName)) {
+ return $this->{$methodName}($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Add argument to event. It will use a setter method if one exists. The setters have the signature:
+ *
+ * set($value): mixed
+ *
+ * where:
+ *
+ * $value is the value being set by the user
+ * It returns the value to return to set in the $arguments array of the event.
+ *
+ * @param string $name Argument name.
+ * @param mixed $value Value.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setArgument($name, $value)
+ {
+ $methodName = 'set' . ucfirst($name);
+
+ if (method_exists($this, $methodName)) {
+ $value = $this->{$methodName}($value);
+ }
+
+ return parent::setArgument($name, $value);
+ }
}
diff --git a/libraries/src/Event/AbstractImmutableEvent.php b/libraries/src/Event/AbstractImmutableEvent.php
index 3c14f64c94ac1..4926c95ab2f15 100644
--- a/libraries/src/Event/AbstractImmutableEvent.php
+++ b/libraries/src/Event/AbstractImmutableEvent.php
@@ -1,4 +1,5 @@
constructed)
- {
- throw new BadMethodCallException(
- sprintf('Cannot reconstruct the AbstractImmutableEvent %s.', $this->name)
- );
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @since 4.0.0
+ * @throws BadMethodCallException
+ */
+ public function __construct(string $name, array $arguments = [])
+ {
+ if ($this->constructed) {
+ throw new BadMethodCallException(
+ sprintf('Cannot reconstruct the AbstractImmutableEvent %s.', $this->name)
+ );
+ }
- $this->constructed = true;
+ $this->constructed = true;
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
- /**
- * Set the value of an event argument.
- *
- * @param string $name The argument name.
- * @param mixed $value The argument value.
- *
- * @return void
- *
- * @since 4.0.0
- * @throws BadMethodCallException
- */
- public function offsetSet($name, $value)
- {
- throw new BadMethodCallException(
- sprintf(
- 'Cannot set the argument %s of the immutable event %s.',
- $name,
- $this->name
- )
- );
- }
+ /**
+ * Set the value of an event argument.
+ *
+ * @param string $name The argument name.
+ * @param mixed $value The argument value.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws BadMethodCallException
+ */
+ public function offsetSet($name, $value)
+ {
+ throw new BadMethodCallException(
+ sprintf(
+ 'Cannot set the argument %s of the immutable event %s.',
+ $name,
+ $this->name
+ )
+ );
+ }
- /**
- * Remove an event argument.
- *
- * @param string $name The argument name.
- *
- * @return void
- *
- * @since 4.0.0
- * @throws BadMethodCallException
- */
- public function offsetUnset($name)
- {
- throw new BadMethodCallException(
- sprintf(
- 'Cannot remove the argument %s of the immutable event %s.',
- $name,
- $this->name
- )
- );
- }
+ /**
+ * Remove an event argument.
+ *
+ * @param string $name The argument name.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws BadMethodCallException
+ */
+ public function offsetUnset($name)
+ {
+ throw new BadMethodCallException(
+ sprintf(
+ 'Cannot remove the argument %s of the immutable event %s.',
+ $name,
+ $this->name
+ )
+ );
+ }
}
diff --git a/libraries/src/Event/AfterExtensionBootEvent.php b/libraries/src/Event/AfterExtensionBootEvent.php
index 82a4509218446..4ac0eba491a25 100644
--- a/libraries/src/Event/AfterExtensionBootEvent.php
+++ b/libraries/src/Event/AfterExtensionBootEvent.php
@@ -1,4 +1,5 @@
getArgument('type');
- }
+ /**
+ * Get the event's extension type. Can be:
+ * - component
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getExtensionType(): string
+ {
+ return $this->getArgument('type');
+ }
- /**
- * Get the event's extension name.
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getExtensionName(): string
- {
- return $this->arguments['extensionName'];
- }
+ /**
+ * Get the event's extension name.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getExtensionName(): string
+ {
+ return $this->arguments['extensionName'];
+ }
- /**
- * Get the event's container object
- *
- * @return Container
- *
- * @since 4.0.0
- */
- public function getContainer(): Container
- {
- return $this->arguments['container'];
- }
+ /**
+ * Get the event's container object
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ public function getContainer(): Container
+ {
+ return $this->arguments['container'];
+ }
}
diff --git a/libraries/src/Event/BeforeExtensionBootEvent.php b/libraries/src/Event/BeforeExtensionBootEvent.php
index 46055747dc3b9..50f01d02ab2a1 100644
--- a/libraries/src/Event/BeforeExtensionBootEvent.php
+++ b/libraries/src/Event/BeforeExtensionBootEvent.php
@@ -1,4 +1,5 @@
getArgument('type');
- }
+ /**
+ * Get the event's extension type. Can be:
+ * - component
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getExtensionType(): string
+ {
+ return $this->getArgument('type');
+ }
- /**
- * Get the event's extension name.
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getExtensionName(): string
- {
- return $this->arguments['extensionName'];
- }
+ /**
+ * Get the event's extension name.
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getExtensionName(): string
+ {
+ return $this->arguments['extensionName'];
+ }
- /**
- * Get the event's container object
- *
- * @return Container
- *
- * @since 4.0.0
- */
- public function getContainer(): Container
- {
- return $this->arguments['container'];
- }
+ /**
+ * Get the event's container object
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ public function getContainer(): Container
+ {
+ return $this->arguments['container'];
+ }
}
diff --git a/libraries/src/Event/CoreEventAware.php b/libraries/src/Event/CoreEventAware.php
index 7657d4684b674..e14ed5561e8ad 100644
--- a/libraries/src/Event/CoreEventAware.php
+++ b/libraries/src/Event/CoreEventAware.php
@@ -1,4 +1,5 @@
BeforeBatchEvent::class,
- // Quickicon
- 'onGetIcon' => GetIconEvent::class,
- // Table
- 'onTableAfterBind' => AfterBindEvent::class,
- 'onTableAfterCheckin' => AfterCheckinEvent::class,
- 'onTableAfterCheckout' => AfterCheckoutEvent::class,
- 'onTableAfterDelete' => AfterDeleteEvent::class,
- 'onTableAfterHit' => AfterHitEvent::class,
- 'onTableAfterLoad' => AfterLoadEvent::class,
- 'onTableAfterMove' => AfterMoveEvent::class,
- 'onTableAfterPublish' => AfterPublishEvent::class,
- 'onTableAfterReorder' => AfterReorderEvent::class,
- 'onTableAfterReset' => AfterResetEvent::class,
- 'onTableAfterStore' => AfterStoreEvent::class,
- 'onTableBeforeBind' => BeforeBindEvent::class,
- 'onTableBeforeCheckin' => BeforeCheckinEvent::class,
- 'onTableBeforeCheckout' => BeforeCheckoutEvent::class,
- 'onTableBeforeDelete' => BeforeDeleteEvent::class,
- 'onTableBeforeHit' => BeforeHitEvent::class,
- 'onTableBeforeLoad' => BeforeLoadEvent::class,
- 'onTableBeforeMove' => BeforeMoveEvent::class,
- 'onTableBeforePublish' => BeforePublishEvent::class,
- 'onTableBeforeReorder' => BeforeReorderEvent::class,
- 'onTableBeforeReset' => BeforeResetEvent::class,
- 'onTableBeforeStore' => BeforeStoreEvent::class,
- 'onTableCheck' => CheckEvent::class,
- 'onTableObjectCreate' => ObjectCreateEvent::class,
- 'onTableSetNewTags' => SetNewTagsEvent::class,
- // View
- 'onBeforeDisplay' => DisplayEvent::class,
- 'onAfterDisplay' => DisplayEvent::class,
- // Workflow
- 'onWorkflowFunctionalityUsed' => WorkflowFunctionalityUsedEvent::class,
- 'onWorkflowAfterTransition' => WorkflowTransitionEvent::class,
- 'onWorkflowBeforeTransition' => WorkflowTransitionEvent::class,
- // Plugin: System, WebAuthn
- 'onAjaxWebauthn' => PlgSystemWebauthnAjax::class,
- 'onAjaxWebauthnChallenge' => PlgSystemWebauthnAjaxChallenge::class,
- 'onAjaxWebauthnCreate' => PlgSystemWebauthnAjaxCreate::class,
- 'onAjaxWebauthnDelete' => PlgSystemWebauthnAjaxDelete::class,
- 'onAjaxWebauthnInitcreate' => PlgSystemWebauthnAjaxInitCreate::class,
- 'onAjaxWebauthnLogin' => PlgSystemWebauthnAjaxLogin::class,
- 'onAjaxWebauthnSavelabel' => PlgSystemWebauthnAjaxSaveLabel::class,
- ];
+ /**
+ * Maps event names to concrete Event classes.
+ *
+ * This is only for events with invariable names. Events with variable names are handled with
+ * PHP logic in the getEventClassByEventName class.
+ *
+ * @var array
+ * @since 4.2.0
+ */
+ private static $eventNameToConcreteClass = [
+ // Model
+ 'onBeforeBatch' => BeforeBatchEvent::class,
+ // Quickicon
+ 'onGetIcon' => GetIconEvent::class,
+ // Table
+ 'onTableAfterBind' => AfterBindEvent::class,
+ 'onTableAfterCheckin' => AfterCheckinEvent::class,
+ 'onTableAfterCheckout' => AfterCheckoutEvent::class,
+ 'onTableAfterDelete' => AfterDeleteEvent::class,
+ 'onTableAfterHit' => AfterHitEvent::class,
+ 'onTableAfterLoad' => AfterLoadEvent::class,
+ 'onTableAfterMove' => AfterMoveEvent::class,
+ 'onTableAfterPublish' => AfterPublishEvent::class,
+ 'onTableAfterReorder' => AfterReorderEvent::class,
+ 'onTableAfterReset' => AfterResetEvent::class,
+ 'onTableAfterStore' => AfterStoreEvent::class,
+ 'onTableBeforeBind' => BeforeBindEvent::class,
+ 'onTableBeforeCheckin' => BeforeCheckinEvent::class,
+ 'onTableBeforeCheckout' => BeforeCheckoutEvent::class,
+ 'onTableBeforeDelete' => BeforeDeleteEvent::class,
+ 'onTableBeforeHit' => BeforeHitEvent::class,
+ 'onTableBeforeLoad' => BeforeLoadEvent::class,
+ 'onTableBeforeMove' => BeforeMoveEvent::class,
+ 'onTableBeforePublish' => BeforePublishEvent::class,
+ 'onTableBeforeReorder' => BeforeReorderEvent::class,
+ 'onTableBeforeReset' => BeforeResetEvent::class,
+ 'onTableBeforeStore' => BeforeStoreEvent::class,
+ 'onTableCheck' => CheckEvent::class,
+ 'onTableObjectCreate' => ObjectCreateEvent::class,
+ 'onTableSetNewTags' => SetNewTagsEvent::class,
+ // View
+ 'onBeforeDisplay' => DisplayEvent::class,
+ 'onAfterDisplay' => DisplayEvent::class,
+ // Workflow
+ 'onWorkflowFunctionalityUsed' => WorkflowFunctionalityUsedEvent::class,
+ 'onWorkflowAfterTransition' => WorkflowTransitionEvent::class,
+ 'onWorkflowBeforeTransition' => WorkflowTransitionEvent::class,
+ // Plugin: System, WebAuthn
+ 'onAjaxWebauthn' => PlgSystemWebauthnAjax::class,
+ 'onAjaxWebauthnChallenge' => PlgSystemWebauthnAjaxChallenge::class,
+ 'onAjaxWebauthnCreate' => PlgSystemWebauthnAjaxCreate::class,
+ 'onAjaxWebauthnDelete' => PlgSystemWebauthnAjaxDelete::class,
+ 'onAjaxWebauthnInitcreate' => PlgSystemWebauthnAjaxInitCreate::class,
+ 'onAjaxWebauthnLogin' => PlgSystemWebauthnAjaxLogin::class,
+ 'onAjaxWebauthnSavelabel' => PlgSystemWebauthnAjaxSaveLabel::class,
+ ];
- /**
- * Get the concrete event class name for the given event name.
- *
- * This method falls back to the generic Joomla\Event\Event class if the event name is unknown
- * to this trait.
- *
- * @param string $eventName The event name
- *
- * @return string The event class name
- * @since 4.2.0
- */
- protected static function getEventClassByEventName(string $eventName): string
- {
- if (strpos($eventName, 'onWebAssetRegistryChangedAsset') === 0)
- {
- return WebAssetRegistryAssetChanged::class;
- }
+ /**
+ * Get the concrete event class name for the given event name.
+ *
+ * This method falls back to the generic Joomla\Event\Event class if the event name is unknown
+ * to this trait.
+ *
+ * @param string $eventName The event name
+ *
+ * @return string The event class name
+ * @since 4.2.0
+ */
+ protected static function getEventClassByEventName(string $eventName): string
+ {
+ if (strpos($eventName, 'onWebAssetRegistryChangedAsset') === 0) {
+ return WebAssetRegistryAssetChanged::class;
+ }
- return self::$eventNameToConcreteClass[$eventName] ?? Event::class;
- }
+ return self::$eventNameToConcreteClass[$eventName] ?? Event::class;
+ }
}
diff --git a/libraries/src/Event/ErrorEvent.php b/libraries/src/Event/ErrorEvent.php
index ee14f6bcb80e0..ed1be0769b49f 100644
--- a/libraries/src/Event/ErrorEvent.php
+++ b/libraries/src/Event/ErrorEvent.php
@@ -1,4 +1,5 @@
arguments['application'];
- }
+ /**
+ * Get the event's application object
+ *
+ * @return AbstractApplication
+ *
+ * @since 4.0.0
+ */
+ public function getApplication(): AbstractApplication
+ {
+ return $this->arguments['application'];
+ }
- /**
- * Get the event's error object
- *
- * @return \Throwable
- *
- * @since 4.0.0
- */
- public function getError(): \Throwable
- {
- return $this->getArgument('subject');
- }
+ /**
+ * Get the event's error object
+ *
+ * @return \Throwable
+ *
+ * @since 4.0.0
+ */
+ public function getError(): \Throwable
+ {
+ return $this->getArgument('subject');
+ }
- /**
- * Set the event's error object
- *
- * @param \Throwable $error The new error to process
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function setError(\Throwable $error)
- {
- $this->setArgument('subject', $error);
- }
+ /**
+ * Set the event's error object
+ *
+ * @param \Throwable $error The new error to process
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setError(\Throwable $error)
+ {
+ $this->setArgument('subject', $error);
+ }
}
diff --git a/libraries/src/Event/GenericEvent.php b/libraries/src/Event/GenericEvent.php
index cacfebde03d6d..7c95d4f5ea980 100644
--- a/libraries/src/Event/GenericEvent.php
+++ b/libraries/src/Event/GenericEvent.php
@@ -1,4 +1,5 @@
$user]);
- }
+ /**
+ * Public constructor
+ *
+ * @param User $user The user the MFA methods are displayed for
+ *
+ * @since 4.2.0
+ */
+ public function __construct(User $user)
+ {
+ parent::__construct('onUserMultifactorBeforeDisplayMethods', ['user' => $user]);
+ }
- /**
- * Validate the value of the 'user' named parameter
- *
- * @param User $value The value to validate
- *
- * @return User
- * @since 4.2.0
- */
- public function setUser(User $value): User
- {
- if (empty($value) || ($value->id <= 0) || ($value->guest == 1))
- {
- throw new DomainException(sprintf('Argument \'user\' of event %s must be a non-guest User object.', $this->name));
- }
+ /**
+ * Validate the value of the 'user' named parameter
+ *
+ * @param User $value The value to validate
+ *
+ * @return User
+ * @since 4.2.0
+ */
+ public function setUser(User $value): User
+ {
+ if (empty($value) || ($value->id <= 0) || ($value->guest == 1)) {
+ throw new DomainException(sprintf('Argument \'user\' of event %s must be a non-guest User object.', $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/MultiFactor/Callback.php b/libraries/src/Event/MultiFactor/Callback.php
index 272b9d3705a85..bbfe14ff2627d 100644
--- a/libraries/src/Event/MultiFactor/Callback.php
+++ b/libraries/src/Event/MultiFactor/Callback.php
@@ -1,4 +1,5 @@
$method]);
- }
+ /**
+ * Public constructor
+ *
+ * @param string $method The MFA method name
+ *
+ * @since 4.2.0
+ */
+ public function __construct(string $method)
+ {
+ parent::__construct('onUserMultifactorCallback', ['method' => $method]);
+ }
- /**
- * Validate the value of the 'method' named parameter
- *
- * @param string|null $value The value to validate
- *
- * @return string
- * @throws DomainException
- * @since 4.2.0
- */
- public function setMethod(string $value): string
- {
- if (empty($value))
- {
- throw new DomainException(sprintf("Argument 'method' of event %s must be a non-empty string.", $this->name));
- }
+ /**
+ * Validate the value of the 'method' named parameter
+ *
+ * @param string|null $value The value to validate
+ *
+ * @return string
+ * @throws DomainException
+ * @since 4.2.0
+ */
+ public function setMethod(string $value): string
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf("Argument 'method' of event %s must be a non-empty string.", $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/MultiFactor/Captive.php b/libraries/src/Event/MultiFactor/Captive.php
index 3e6a8bcae99a9..3f20aca39b6dc 100644
--- a/libraries/src/Event/MultiFactor/Captive.php
+++ b/libraries/src/Event/MultiFactor/Captive.php
@@ -1,4 +1,5 @@
$record]);
-
- $this->resultIsNullable = true;
- $this->resultAcceptableClasses = [
- CaptiveRenderOptions::class,
- ];
- }
-
- /**
- * Validate the value of the 'record' named parameter
- *
- * @param MfaTable $value The value to validate
- *
- * @return MfaTable
- * @since 4.2.0
- */
- public function setRecord(MfaTable $value): MfaTable
- {
- if (empty($value))
- {
- throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
- }
-
- return $value;
- }
+ use ResultAware;
+ use ResultTypeObjectAware;
+
+ /**
+ * Public constructor
+ *
+ * @param MfaTable $record The MFA record to use in the captive login page
+ *
+ * @since 4.2.0
+ */
+ public function __construct(MfaTable $record)
+ {
+ parent::__construct('onUserMultifactorCaptive', ['record' => $record]);
+
+ $this->resultIsNullable = true;
+ $this->resultAcceptableClasses = [
+ CaptiveRenderOptions::class,
+ ];
+ }
+
+ /**
+ * Validate the value of the 'record' named parameter
+ *
+ * @param MfaTable $value The value to validate
+ *
+ * @return MfaTable
+ * @since 4.2.0
+ */
+ public function setRecord(MfaTable $value): MfaTable
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
+ }
+
+ return $value;
+ }
}
diff --git a/libraries/src/Event/MultiFactor/GetMethod.php b/libraries/src/Event/MultiFactor/GetMethod.php
index 0a516db794e67..4501fe376ed53 100644
--- a/libraries/src/Event/MultiFactor/GetMethod.php
+++ b/libraries/src/Event/MultiFactor/GetMethod.php
@@ -1,4 +1,5 @@
resultIsNullable = true;
- $this->resultAcceptableClasses = [
- MethodDescriptor::class,
- ];
- }
+ $this->resultIsNullable = true;
+ $this->resultAcceptableClasses = [
+ MethodDescriptor::class,
+ ];
+ }
}
diff --git a/libraries/src/Event/MultiFactor/GetSetup.php b/libraries/src/Event/MultiFactor/GetSetup.php
index 8a05f857b436b..2acd5ff94e712 100644
--- a/libraries/src/Event/MultiFactor/GetSetup.php
+++ b/libraries/src/Event/MultiFactor/GetSetup.php
@@ -1,4 +1,5 @@
$record]);
-
- $this->resultIsNullable = true;
- $this->resultAcceptableClasses = [
- SetupRenderOptions::class,
- ];
- }
-
- /**
- * Validate the value of the 'record' named parameter
- *
- * @param MfaTable $value The value to validate
- *
- * @return MfaTable
- * @since 4.2.0
- */
- public function setRecord(MfaTable $value): MfaTable
- {
- if (empty($value))
- {
- throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
- }
-
- return $value;
- }
+ use ResultAware;
+ use ResultTypeObjectAware;
+
+ /**
+ * Public constructor
+ *
+ * @param MfaTable $record The record to display the setup page for
+ *
+ * @since 4.2.0
+ */
+ public function __construct(MfaTable $record)
+ {
+ parent::__construct('onUserMultifactorGetSetup', ['record' => $record]);
+
+ $this->resultIsNullable = true;
+ $this->resultAcceptableClasses = [
+ SetupRenderOptions::class,
+ ];
+ }
+
+ /**
+ * Validate the value of the 'record' named parameter
+ *
+ * @param MfaTable $value The value to validate
+ *
+ * @return MfaTable
+ * @since 4.2.0
+ */
+ public function setRecord(MfaTable $value): MfaTable
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
+ }
+
+ return $value;
+ }
}
diff --git a/libraries/src/Event/MultiFactor/NotifyActionLog.php b/libraries/src/Event/MultiFactor/NotifyActionLog.php
index 28abb8670eb53..19ebabcdf22ff 100644
--- a/libraries/src/Event/MultiFactor/NotifyActionLog.php
+++ b/libraries/src/Event/MultiFactor/NotifyActionLog.php
@@ -1,4 +1,5 @@
$record,
- 'input' => $input,
- ]
- );
+ /**
+ * Public constructor
+ *
+ * @param MfaTable $record The record to save into
+ * @param Input $input The application input object
+ *
+ * @since 4.2.0
+ */
+ public function __construct(MfaTable $record, Input $input)
+ {
+ parent::__construct(
+ 'onUserMultifactorSaveSetup',
+ [
+ 'record' => $record,
+ 'input' => $input,
+ ]
+ );
- $this->resultIsNullable = true;
- }
+ $this->resultIsNullable = true;
+ }
- /**
- * Validate the value of the 'record' named parameter
- *
- * @param MfaTable $value The value to validate
- *
- * @return MfaTable
- * @since 4.2.0
- */
- public function setRecord(MfaTable $value): MfaTable
- {
- if (empty($value))
- {
- throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
- }
+ /**
+ * Validate the value of the 'record' named parameter
+ *
+ * @param MfaTable $value The value to validate
+ *
+ * @return MfaTable
+ * @since 4.2.0
+ */
+ public function setRecord(MfaTable $value): MfaTable
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Validate the value of the 'record' named parameter
- *
- * @param Input $value The value to validate
- *
- * @return Input
- * @since 4.2.0
- */
- public function setInput(Input $value): Input
- {
- if (empty($value))
- {
- throw new DomainException(sprintf('Argument \'input\' of event %s must be an Input object.', $this->name));
- }
+ /**
+ * Validate the value of the 'record' named parameter
+ *
+ * @param Input $value The value to validate
+ *
+ * @return Input
+ * @since 4.2.0
+ */
+ public function setInput(Input $value): Input
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf('Argument \'input\' of event %s must be an Input object.', $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/MultiFactor/Validate.php b/libraries/src/Event/MultiFactor/Validate.php
index 4ab2bd7f3391f..7eae07e146302 100644
--- a/libraries/src/Event/MultiFactor/Validate.php
+++ b/libraries/src/Event/MultiFactor/Validate.php
@@ -1,4 +1,5 @@
$record,
- 'user' => $user,
- 'code' => $code,
- ]
- );
- }
+ /**
+ * Public constructor
+ *
+ * @param MfaTable $record The MFA record to validate against
+ * @param User $user The user currently logged into the site
+ * @param string $code The MFA code we are validating
+ *
+ * @since 4.2.0
+ */
+ public function __construct(MfaTable $record, User $user, string $code)
+ {
+ parent::__construct(
+ 'onUserMultifactorValidate',
+ [
+ 'record' => $record,
+ 'user' => $user,
+ 'code' => $code,
+ ]
+ );
+ }
- /**
- * Validate the value of the 'record' named parameter
- *
- * @param MfaTable $value The value to validate
- *
- * @return MfaTable
- * @since 4.2.0
- */
- public function setRecord(MfaTable $value): MfaTable
- {
- if (empty($value))
- {
- throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
- }
+ /**
+ * Validate the value of the 'record' named parameter
+ *
+ * @param MfaTable $value The value to validate
+ *
+ * @return MfaTable
+ * @since 4.2.0
+ */
+ public function setRecord(MfaTable $value): MfaTable
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf('Argument \'record\' of event %s must be a MfaTable object.', $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Validate the value of the 'user' named parameter
- *
- * @param User $value The value to validate
- *
- * @return User
- * @since 4.2.0
- */
- public function setUser(User $value): User
- {
- if (empty($value) || ($value->id <= 0) || ($value->guest == 1))
- {
- throw new DomainException(sprintf('Argument \'user\' of event %s must be a non-guest User object.', $this->name));
- }
+ /**
+ * Validate the value of the 'user' named parameter
+ *
+ * @param User $value The value to validate
+ *
+ * @return User
+ * @since 4.2.0
+ */
+ public function setUser(User $value): User
+ {
+ if (empty($value) || ($value->id <= 0) || ($value->guest == 1)) {
+ throw new DomainException(sprintf('Argument \'user\' of event %s must be a non-guest User object.', $this->name));
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Validate the value of the 'code' named parameter
- *
- * @param string|null $value The value to validate
- *
- * @return string|null
- * @since 4.2.0
- */
- public function setCode(?string $value): ?string
- {
- // No validation necessary, the type check in the method options is enough
- return $value;
- }
+ /**
+ * Validate the value of the 'code' named parameter
+ *
+ * @param string|null $value The value to validate
+ *
+ * @return string|null
+ * @since 4.2.0
+ */
+ public function setCode(?string $value): ?string
+ {
+ // No validation necessary, the type check in the method options is enough
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Plugin/System/Webauthn/Ajax.php b/libraries/src/Event/Plugin/System/Webauthn/Ajax.php
index c3b46fd813f2a..8c440af771d38 100644
--- a/libraries/src/Event/Plugin/System/Webauthn/Ajax.php
+++ b/libraries/src/Event/Plugin/System/Webauthn/Ajax.php
@@ -1,4 +1,5 @@
getName()));
- }
- }
+ if (!is_string($data) || @json_decode($data) === null) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts JSON results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Plugin/System/Webauthn/AjaxCreate.php b/libraries/src/Event/Plugin/System/Webauthn/AjaxCreate.php
index 6a7f3bc6aac4f..739b4832d5105 100644
--- a/libraries/src/Event/Plugin/System/Webauthn/AjaxCreate.php
+++ b/libraries/src/Event/Plugin/System/Webauthn/AjaxCreate.php
@@ -1,4 +1,5 @@
resultAcceptableClasses = [
- \stdClass::class,
- PublicKeyCredentialCreationOptions::class
- ];
- }
+ use ResultAware;
+ use ResultTypeObjectAware;
+ /**
+ * Constructor
+ *
+ * @param string $name Event name
+ * @param array $arguments Event arguments
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function __construct(string $name, array $arguments = [])
+ {
+ parent::__construct($name, $arguments);
+ $this->resultAcceptableClasses = [
+ \stdClass::class,
+ PublicKeyCredentialCreationOptions::class
+ ];
+ }
}
diff --git a/libraries/src/Event/Plugin/System/Webauthn/AjaxLogin.php b/libraries/src/Event/Plugin/System/Webauthn/AjaxLogin.php
index 5e84806472a19..e1affe0b96245 100644
--- a/libraries/src/Event/Plugin/System/Webauthn/AjaxLogin.php
+++ b/libraries/src/Event/Plugin/System/Webauthn/AjaxLogin.php
@@ -1,4 +1,5 @@
reshapeArguments($arguments, ['context']);
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @since 4.2.0
+ * @throws BadMethodCallException
+ */
+ public function __construct(string $name, array $arguments = [])
+ {
+ $this->reshapeArguments($arguments, ['context']);
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
- /**
- * A method to validate the 'context' named parameter.
- *
- * @param string $value The calling context for retrieving icons.
- *
- * @return void
- *
- * @since 4.2.0
- */
- public function setContext(string $value)
- {
- if (empty($value))
- {
- throw new DomainException(sprintf("Argument 'context' of event %s must be a non-empty string.", $this->name));
- }
- }
+ /**
+ * A method to validate the 'context' named parameter.
+ *
+ * @param string $value The calling context for retrieving icons.
+ *
+ * @return void
+ *
+ * @since 4.2.0
+ */
+ public function setContext(string $value)
+ {
+ if (empty($value)) {
+ throw new DomainException(sprintf("Argument 'context' of event %s must be a non-empty string.", $this->name));
+ }
+ }
}
diff --git a/libraries/src/Event/ReshapeArgumentsAware.php b/libraries/src/Event/ReshapeArgumentsAware.php
index 6a3cdf347edeb..44ee65b721ac6 100644
--- a/libraries/src/Event/ReshapeArgumentsAware.php
+++ b/libraries/src/Event/ReshapeArgumentsAware.php
@@ -1,4 +1,5 @@
getName(), implode(', ', $missingKeys)));
- }
+ // Am I missing any mandatory arguments?
+ if ($missingKeys) {
+ throw new DomainException(sprintf('Missing arguments for ‘%s’ event: %s', $this->getName(), implode(', ', $missingKeys)));
+ }
- // Do I have unknown arguments?
- if ($extraKeys)
- {
- throw new DomainException(sprintf('Unknown arguments for ‘%s’ event: %s', $this->getName(), implode(', ', $missingKeys)));
- }
+ // Do I have unknown arguments?
+ if ($extraKeys) {
+ throw new DomainException(sprintf('Unknown arguments for ‘%s’ event: %s', $this->getName(), implode(', ', $missingKeys)));
+ }
- // Reconstruct the arguments in the order specified in $argumentTypes
- $reconstructed = [];
+ // Reconstruct the arguments in the order specified in $argumentTypes
+ $reconstructed = [];
- foreach ($argumentNames as $key)
- {
- $reconstructed[$key] = $arguments[$key] ?? $defaults[$key];
- }
+ foreach ($argumentNames as $key) {
+ $reconstructed[$key] = $arguments[$key] ?? $defaults[$key];
+ }
- // Return the reconstructed arguments array
- return $reconstructed;
- }
+ // Return the reconstructed arguments array
+ return $reconstructed;
+ }
}
diff --git a/libraries/src/Event/Result/ResultAware.php b/libraries/src/Event/Result/ResultAware.php
index c7a0d542cdbdc..8038ee9d1fb7a 100644
--- a/libraries/src/Event/Result/ResultAware.php
+++ b/libraries/src/Event/Result/ResultAware.php
@@ -1,4 +1,5 @@
typeCheckResult($data);
+ // Make sure the data type is correct
+ $this->typeCheckResult($data);
- // Append the result. We use the arguments property directly to allow this to work on immutable events.
- $this->arguments['result'] = $this->arguments['result'] ?? [];
- $this->arguments['result'][] = $data;
- }
+ // Append the result. We use the arguments property directly to allow this to work on immutable events.
+ $this->arguments['result'] = $this->arguments['result'] ?? [];
+ $this->arguments['result'][] = $data;
+ }
- /**
- * Handle setting the result argument directly.
- *
- * This method serves a dual purpose: backwards compatibility and enforcing the use of addResult.
- *
- * When $this->preventSetArgumentResult is false it acts as a backwards compatibility shim for
- * event handlers expecting generic event classes instead of the concrete Events implemented in
- * this package. This allows the migration to concrete event classes throughout the lifetime of
- * Joomla 4.x.
- *
- * When $this->preventSetArgumentResult is false (which will always be the case on Joomla 5.0)
- * it will throw a BadMethodCallException if the developer tries to call setArgument('result', ...)
- * instead of going through the addResult() method.
- *
- * @param array $value The new result array.
- *
- * @return array
- * @since 4.2.0
- */
- protected function setResult(array $value)
- {
- if ($this->preventSetArgumentResult)
- {
- throw new BadMethodCallException('You are not allowed to set the result argument directly. Use addResult() instead.');
- }
+ /**
+ * Handle setting the result argument directly.
+ *
+ * This method serves a dual purpose: backwards compatibility and enforcing the use of addResult.
+ *
+ * When $this->preventSetArgumentResult is false it acts as a backwards compatibility shim for
+ * event handlers expecting generic event classes instead of the concrete Events implemented in
+ * this package. This allows the migration to concrete event classes throughout the lifetime of
+ * Joomla 4.x.
+ *
+ * When $this->preventSetArgumentResult is false (which will always be the case on Joomla 5.0)
+ * it will throw a BadMethodCallException if the developer tries to call setArgument('result', ...)
+ * instead of going through the addResult() method.
+ *
+ * @param array $value The new result array.
+ *
+ * @return array
+ * @since 4.2.0
+ */
+ protected function setResult(array $value)
+ {
+ if ($this->preventSetArgumentResult) {
+ throw new BadMethodCallException('You are not allowed to set the result argument directly. Use addResult() instead.');
+ }
- // Always assume that the last element of the array is the result the handler is trying to append.
- $latestValue = array_pop($value);
+ // Always assume that the last element of the array is the result the handler is trying to append.
+ $latestValue = array_pop($value);
- $this->addResult($latestValue);
+ $this->addResult($latestValue);
- return $this->arguments['result'];
- }
+ return $this->arguments['result'];
+ }
}
diff --git a/libraries/src/Event/Result/ResultAwareInterface.php b/libraries/src/Event/Result/ResultAwareInterface.php
index e8770e3f74f2d..76d96341b3fd2 100644
--- a/libraries/src/Event/Result/ResultAwareInterface.php
+++ b/libraries/src/Event/Result/ResultAwareInterface.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_array($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts Array results.', $this->getName()));
- }
- }
+ if (!is_array($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts Array results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeBooleanAware.php b/libraries/src/Event/Result/ResultTypeBooleanAware.php
index 6d148bfa0e425..1bd4a8a5e3ccc 100644
--- a/libraries/src/Event/Result/ResultTypeBooleanAware.php
+++ b/libraries/src/Event/Result/ResultTypeBooleanAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if (!is_bool($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts Boolean results.', $this->getName()));
- }
- }
+ if (!is_bool($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts Boolean results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeFloatAware.php b/libraries/src/Event/Result/ResultTypeFloatAware.php
index 986e5851af8ef..14369f5576783 100644
--- a/libraries/src/Event/Result/ResultTypeFloatAware.php
+++ b/libraries/src/Event/Result/ResultTypeFloatAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_float($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts Float results.', $this->getName()));
- }
- }
+ if (!is_float($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts Float results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeIntegerAware.php b/libraries/src/Event/Result/ResultTypeIntegerAware.php
index e56ba3f0de5be..3342df3e9ba49 100644
--- a/libraries/src/Event/Result/ResultTypeIntegerAware.php
+++ b/libraries/src/Event/Result/ResultTypeIntegerAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_int($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts Integer results.', $this->getName()));
- }
- }
+ if (!is_int($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts Integer results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeMixedAware.php b/libraries/src/Event/Result/ResultTypeMixedAware.php
index cc435395ae3ed..afa051b57161a 100644
--- a/libraries/src/Event/Result/ResultTypeMixedAware.php
+++ b/libraries/src/Event/Result/ResultTypeMixedAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_numeric($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts Numeric results.', $this->getName()));
- }
- }
+ if (!is_numeric($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts Numeric results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeObjectAware.php b/libraries/src/Event/Result/ResultTypeObjectAware.php
index b41528b51aca0..d02049f66dc9d 100644
--- a/libraries/src/Event/Result/ResultTypeObjectAware.php
+++ b/libraries/src/Event/Result/ResultTypeObjectAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_object($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts object results.', $this->getName()));
- }
+ if (!is_object($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts object results.', $this->getName()));
+ }
- if (empty($this->resultAcceptableClasses))
- {
- return;
- }
+ if (empty($this->resultAcceptableClasses)) {
+ return;
+ }
- foreach ($this->resultAcceptableClasses as $className)
- {
- if (is_a($data, $className))
- {
- return;
- }
- }
+ foreach ($this->resultAcceptableClasses as $className) {
+ if (is_a($data, $className)) {
+ return;
+ }
+ }
- $acceptableTypes = implode(', ', $this->resultAcceptableClasses);
- $messageTemplate = 'Event %s only accepts object results which are instances of one of %s.';
- throw new InvalidArgumentException(sprintf($messageTemplate, $this->getName(), $acceptableTypes));
- }
+ $acceptableTypes = implode(', ', $this->resultAcceptableClasses);
+ $messageTemplate = 'Event %s only accepts object results which are instances of one of %s.';
+ throw new InvalidArgumentException(sprintf($messageTemplate, $this->getName(), $acceptableTypes));
+ }
}
diff --git a/libraries/src/Event/Result/ResultTypeStringAware.php b/libraries/src/Event/Result/ResultTypeStringAware.php
index cd3a18dee6c1b..0f30ea2ee9a37 100644
--- a/libraries/src/Event/Result/ResultTypeStringAware.php
+++ b/libraries/src/Event/Result/ResultTypeStringAware.php
@@ -1,4 +1,5 @@
resultIsNullable && $data === null)
- {
- return;
- }
+ /**
+ * Checks the type of the data being appended to the result argument.
+ *
+ * @param mixed $data The data to type check
+ *
+ * @return void
+ * @throws InvalidArgumentException
+ *
+ * @internal
+ * @since 4.2.0
+ */
+ public function typeCheckResult($data): void
+ {
+ if ($this->resultIsNullable && $data === null) {
+ return;
+ }
- if ($this->resultIsFalseable && $data === false)
- {
- return;
- }
+ if ($this->resultIsFalseable && $data === false) {
+ return;
+ }
- if (!is_string($data))
- {
- throw new InvalidArgumentException(sprintf('Event %s only accepts String results.', $this->getName()));
- }
- }
+ if (!is_string($data)) {
+ throw new InvalidArgumentException(sprintf('Event %s only accepts String results.', $this->getName()));
+ }
+ }
}
diff --git a/libraries/src/Event/Table/AbstractEvent.php b/libraries/src/Event/Table/AbstractEvent.php
index 30823534a3398..dabd5081782e4 100644
--- a/libraries/src/Event/Table/AbstractEvent.php
+++ b/libraries/src/Event/Table/AbstractEvent.php
@@ -1,4 +1,5 @@
name} is required but has not been provided");
- }
+ /**
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 1.0
+ */
+ public function __construct($name, array $arguments = [])
+ {
+ if (!\array_key_exists('subject', $arguments)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is required but has not been provided");
+ }
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
- /**
- * Setter for the subject argument
- *
- * @param TableInterface $value The value to set
- *
- * @return TableInterface
- *
- * @throws BadMethodCallException If the argument is not of the expected type.
- */
- protected function setSubject($value)
- {
- if (!\is_object($value) || !($value instanceof TableInterface))
- {
- throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of the expected type");
- }
+ /**
+ * Setter for the subject argument
+ *
+ * @param TableInterface $value The value to set
+ *
+ * @return TableInterface
+ *
+ * @throws BadMethodCallException If the argument is not of the expected type.
+ */
+ protected function setSubject($value)
+ {
+ if (!\is_object($value) || !($value instanceof TableInterface)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of the expected type");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/AfterBindEvent.php b/libraries/src/Event/Table/AfterBindEvent.php
index 2328f8e4a9dfb..8f28135fc37bd 100644
--- a/libraries/src/Event/Table/AfterBindEvent.php
+++ b/libraries/src/Event/Table/AfterBindEvent.php
@@ -1,4 +1,5 @@
name} is not of the expected type");
- }
+ /**
+ * Setter for the result argument
+ *
+ * @param boolean $value The value to set
+ *
+ * @return boolean
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setResult($value)
+ {
+ return $value ? true : false;
+ }
- return $value;
- }
+ /**
+ * Setter for the row argument
+ *
+ * @param array|null $value The value to set
+ *
+ * @return array|null
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setRow($value)
+ {
+ if (!\is_null($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'row' of event {$this->name} is not of the expected type");
+ }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/AfterMoveEvent.php b/libraries/src/Event/Table/AfterMoveEvent.php
index 1042f65d9dd2b..e7df8928f1347 100644
--- a/libraries/src/Event/Table/AfterMoveEvent.php
+++ b/libraries/src/Event/Table/AfterMoveEvent.php
@@ -1,4 +1,5 @@
name} must be an stdClass object or null");
- }
+ /**
+ * Setter for the rows argument
+ *
+ * @param stdClass|null $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setRow($value)
+ {
+ if (!($value instanceof stdClass) && !empty($value)) {
+ throw new BadMethodCallException("Argument 'row' of event {$this->name} must be an stdClass object or null");
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Setter for the delta argument
- *
- * @param int $value The value to set
- *
- * @return integer
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setDelta($value)
- {
- if (!is_numeric($value))
- {
- throw new BadMethodCallException("Argument 'delta' of event {$this->name} must be an integer");
- }
+ /**
+ * Setter for the delta argument
+ *
+ * @param int $value The value to set
+ *
+ * @return integer
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setDelta($value)
+ {
+ if (!is_numeric($value)) {
+ throw new BadMethodCallException("Argument 'delta' of event {$this->name} must be an integer");
+ }
- return (int) $value;
- }
+ return (int) $value;
+ }
- /**
- * Setter for the where argument
- *
- * @param string|null $value The value to set
- *
- * @return mixed
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setWhere($value)
- {
- if (!empty($value) && !\is_string($value))
- {
- throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string");
- }
+ /**
+ * Setter for the where argument
+ *
+ * @param string|null $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setWhere($value)
+ {
+ if (!empty($value) && !\is_string($value)) {
+ throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/AfterPublishEvent.php b/libraries/src/Event/Table/AfterPublishEvent.php
index 8e490adc62ff9..e25108fc2938d 100644
--- a/libraries/src/Event/Table/AfterPublishEvent.php
+++ b/libraries/src/Event/Table/AfterPublishEvent.php
@@ -1,4 +1,5 @@
name} must be empty or string or array of strings");
- }
+ /**
+ * Setter for the where argument
+ *
+ * @param array|string|null $value A string or array of where conditions.
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setWhere($value)
+ {
+ if (!empty($value) && !\is_string($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string or array of strings");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/AfterResetEvent.php b/libraries/src/Event/Table/AfterResetEvent.php
index 3b920228bf2d9..8043d3524e639 100644
--- a/libraries/src/Event/Table/AfterResetEvent.php
+++ b/libraries/src/Event/Table/AfterResetEvent.php
@@ -1,4 +1,5 @@
name} must be empty, object or array");
- }
+ /**
+ * Setter for the src argument
+ *
+ * @param mixed $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setSrc($value)
+ {
+ if (!empty($value) && !\is_object($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'src' of event {$this->name} must be empty, object or array");
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Setter for the ignore argument
- *
- * @param mixed $value The value to set
- *
- * @return mixed
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setIgnore($value)
- {
- if (!empty($value) && !\is_array($value))
- {
- throw new BadMethodCallException("Argument 'ignore' of event {$this->name} must be empty or array");
- }
+ /**
+ * Setter for the ignore argument
+ *
+ * @param mixed $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setIgnore($value)
+ {
+ if (!empty($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'ignore' of event {$this->name} must be empty or array");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/BeforeCheckinEvent.php b/libraries/src/Event/Table/BeforeCheckinEvent.php
index 543365b26e4c5..62e715aedf29d 100644
--- a/libraries/src/Event/Table/BeforeCheckinEvent.php
+++ b/libraries/src/Event/Table/BeforeCheckinEvent.php
@@ -1,4 +1,5 @@
name} must be an integer");
- }
+ /**
+ * Setter for the userId argument
+ *
+ * @param mixed $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setUserId($value)
+ {
+ if (!is_numeric($value) || empty($value)) {
+ throw new BadMethodCallException("Argument 'userId' of event {$this->name} must be an integer");
+ }
- return (int) $value;
- }
+ return (int) $value;
+ }
}
diff --git a/libraries/src/Event/Table/BeforeDeleteEvent.php b/libraries/src/Event/Table/BeforeDeleteEvent.php
index 5181c8c2da065..8a758b023f7fb 100644
--- a/libraries/src/Event/Table/BeforeDeleteEvent.php
+++ b/libraries/src/Event/Table/BeforeDeleteEvent.php
@@ -1,4 +1,5 @@
name} must be of DatabaseQuery type");
- }
+ /**
+ * Setter for the query argument
+ *
+ * @param DatabaseQuery $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setQuery($value)
+ {
+ if (!($value instanceof DatabaseQuery)) {
+ throw new BadMethodCallException("Argument 'query' of event {$this->name} must be of DatabaseQuery type");
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Setter for the delta argument
- *
- * @param int $value The value to set
- *
- * @return integer
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setDelta($value)
- {
- if (!is_numeric($value))
- {
- throw new BadMethodCallException("Argument 'delta' of event {$this->name} must be an integer");
- }
+ /**
+ * Setter for the delta argument
+ *
+ * @param int $value The value to set
+ *
+ * @return integer
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setDelta($value)
+ {
+ if (!is_numeric($value)) {
+ throw new BadMethodCallException("Argument 'delta' of event {$this->name} must be an integer");
+ }
- return (int) $value;
- }
+ return (int) $value;
+ }
- /**
- * Setter for the where argument
- *
- * @param string|null $value The value to set
- *
- * @return mixed
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setWhere($value)
- {
- if (!empty($value) && !\is_string($value))
- {
- throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string");
- }
+ /**
+ * Setter for the where argument
+ *
+ * @param string|null $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setWhere($value)
+ {
+ if (!empty($value) && !\is_string($value)) {
+ throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/BeforePublishEvent.php b/libraries/src/Event/Table/BeforePublishEvent.php
index 32e96519e7322..3b9062af9c366 100644
--- a/libraries/src/Event/Table/BeforePublishEvent.php
+++ b/libraries/src/Event/Table/BeforePublishEvent.php
@@ -1,4 +1,5 @@
name} must be empty or an array");
- }
+ parent::__construct($name, $arguments);
+ }
- return $value;
- }
+ /**
+ * Setter for the pks argument
+ *
+ * @param array|null $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setQuery($value)
+ {
+ if (!empty($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'pks' of event {$this->name} must be empty or an array");
+ }
- /**
- * Setter for the state argument
- *
- * @param int $value The value to set
- *
- * @return integer
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setState($value)
- {
- if (!is_numeric($value))
- {
- throw new BadMethodCallException("Argument 'state' of event {$this->name} must be an integer");
- }
+ return $value;
+ }
- return (int) $value;
- }
+ /**
+ * Setter for the state argument
+ *
+ * @param int $value The value to set
+ *
+ * @return integer
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setState($value)
+ {
+ if (!is_numeric($value)) {
+ throw new BadMethodCallException("Argument 'state' of event {$this->name} must be an integer");
+ }
- /**
- * Setter for the userId argument
- *
- * @param int $value The value to set
- *
- * @return integer
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setUserId($value)
- {
- if (!is_numeric($value))
- {
- throw new BadMethodCallException("Argument 'userId' of event {$this->name} must be an integer");
- }
+ return (int) $value;
+ }
- return (int) $value;
- }
+ /**
+ * Setter for the userId argument
+ *
+ * @param int $value The value to set
+ *
+ * @return integer
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setUserId($value)
+ {
+ if (!is_numeric($value)) {
+ throw new BadMethodCallException("Argument 'userId' of event {$this->name} must be an integer");
+ }
+ return (int) $value;
+ }
}
diff --git a/libraries/src/Event/Table/BeforeReorderEvent.php b/libraries/src/Event/Table/BeforeReorderEvent.php
index 63d84074e7d3b..b9b69699d7aea 100644
--- a/libraries/src/Event/Table/BeforeReorderEvent.php
+++ b/libraries/src/Event/Table/BeforeReorderEvent.php
@@ -1,4 +1,5 @@
name} must be of DatabaseQuery type");
- }
+ /**
+ * Setter for the query argument
+ *
+ * @param DatabaseQuery $value The value to set
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setQuery($value)
+ {
+ if (!($value instanceof DatabaseQuery)) {
+ throw new BadMethodCallException("Argument 'query' of event {$this->name} must be of DatabaseQuery type");
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Setter for the where argument
- *
- * @param array|string|null $value A string or array of where conditions.
- *
- * @return mixed
- *
- * @throws BadMethodCallException if the argument is not of the expected type
- */
- protected function setWhere($value)
- {
- if (!empty($value) && !\is_string($value) && !\is_array($value))
- {
- throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string or array of strings");
- }
+ /**
+ * Setter for the where argument
+ *
+ * @param array|string|null $value A string or array of where conditions.
+ *
+ * @return mixed
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ */
+ protected function setWhere($value)
+ {
+ if (!empty($value) && !\is_string($value) && !\is_array($value)) {
+ throw new BadMethodCallException("Argument 'where' of event {$this->name} must be empty or string or array of strings");
+ }
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Event/Table/BeforeResetEvent.php b/libraries/src/Event/Table/BeforeResetEvent.php
index 4a93d0ef19d0a..7f19409ad4dec 100644
--- a/libraries/src/Event/Table/BeforeResetEvent.php
+++ b/libraries/src/Event/Table/BeforeResetEvent.php
@@ -1,4 +1,5 @@
name} is required but has not been provided");
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 4.0.0
+ */
+ public function __construct($name, array $arguments = array())
+ {
+ if (!isset($arguments['subject'])) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is required but has not been provided");
+ }
- if (!($arguments['subject'] instanceof ViewInterface))
- {
- throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of type 'ViewInterface'");
- }
+ if (!($arguments['subject'] instanceof ViewInterface)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of type 'ViewInterface'");
+ }
- if (!isset($arguments['extension']))
- {
- throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided");
- }
+ if (!isset($arguments['extension'])) {
+ throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided");
+ }
- if (!isset($arguments['extension']) || !is_string($arguments['extension']))
- {
- throw new BadMethodCallException("Argument 'extension' of event {$this->name} is not of type 'string'");
- }
+ if (!isset($arguments['extension']) || !is_string($arguments['extension'])) {
+ throw new BadMethodCallException("Argument 'extension' of event {$this->name} is not of type 'string'");
+ }
- if (strpos($arguments['extension'], '.') === false)
- {
- throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'");
- }
+ if (strpos($arguments['extension'], '.') === false) {
+ throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'");
+ }
- if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments))
- {
- $parts = explode('.', $arguments['extension']);
+ if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments)) {
+ $parts = explode('.', $arguments['extension']);
- $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
- $arguments['section'] = $arguments['section'] ?? $parts[1];
- }
+ $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
+ $arguments['section'] = $arguments['section'] ?? $parts[1];
+ }
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
}
diff --git a/libraries/src/Event/WebAsset/AbstractEvent.php b/libraries/src/Event/WebAsset/AbstractEvent.php
index 6c26aa5525e2b..6a515f09f8f01 100644
--- a/libraries/src/Event/WebAsset/AbstractEvent.php
+++ b/libraries/src/Event/WebAsset/AbstractEvent.php
@@ -1,4 +1,5 @@
name} is required but has not been provided");
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 4.0.0
+ */
+ public function __construct($name, array $arguments = array())
+ {
+ if (!\array_key_exists('subject', $arguments)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is required but has not been provided");
+ }
- parent::__construct($name, $arguments);
- }
+ parent::__construct($name, $arguments);
+ }
}
diff --git a/libraries/src/Event/WebAsset/WebAssetRegistryAssetChanged.php b/libraries/src/Event/WebAsset/WebAssetRegistryAssetChanged.php
index 51b3a133d1a2a..82ec008261cb9 100644
--- a/libraries/src/Event/WebAsset/WebAssetRegistryAssetChanged.php
+++ b/libraries/src/Event/WebAsset/WebAssetRegistryAssetChanged.php
@@ -1,4 +1,5 @@
name} is not of the expected type");
- }
+ /**
+ * Setter for the subject argument
+ *
+ * @param WebAssetRegistryInterface $value The value to set
+ *
+ * @return WebAssetRegistryInterface
+ *
+ * @throws BadMethodCallException if the argument is not of the expected type
+ *
+ * @since 4.0.0
+ */
+ protected function setSubject($value)
+ {
+ if (!$value || !($value instanceof WebAssetRegistryInterface)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is not of the expected type");
+ }
- return $value;
- }
+ return $value;
+ }
- /**
- * Return modified asset
- *
- * @return WebAssetItemInterface
- *
- * @since 4.0.0
- */
- public function getAsset(): WebAssetItemInterface
- {
- return $this->arguments['asset'];
- }
+ /**
+ * Return modified asset
+ *
+ * @return WebAssetItemInterface
+ *
+ * @since 4.0.0
+ */
+ public function getAsset(): WebAssetItemInterface
+ {
+ return $this->arguments['asset'];
+ }
- /**
- * Return a type of modified asset
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getAssetType(): string
- {
- return $this->arguments['assetType'];
- }
+ /**
+ * Return a type of modified asset
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getAssetType(): string
+ {
+ return $this->arguments['assetType'];
+ }
- /**
- * Return a type of changes: new, remove, override
- *
- * @return string
- *
- * @since 4.0.0
- */
- public function getChange(): string
- {
- return $this->arguments['change'];
- }
+ /**
+ * Return a type of changes: new, remove, override
+ *
+ * @return string
+ *
+ * @since 4.0.0
+ */
+ public function getChange(): string
+ {
+ return $this->arguments['change'];
+ }
}
diff --git a/libraries/src/Event/Workflow/AbstractEvent.php b/libraries/src/Event/Workflow/AbstractEvent.php
index 58146ddddaf4b..755d76f5ce751 100644
--- a/libraries/src/Event/Workflow/AbstractEvent.php
+++ b/libraries/src/Event/Workflow/AbstractEvent.php
@@ -1,4 +1,5 @@
name} is required but has not been provided");
- }
-
- if (!\array_key_exists('extension', $arguments))
- {
- throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided");
- }
-
- if (strpos($arguments['extension'], '.') === false)
- {
- throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'");
- }
-
- if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments))
- {
- $parts = explode('.', $arguments['extension']);
-
- $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
- $arguments['section'] = $arguments['section'] ?? $parts[1];
- }
-
- parent::__construct($name, $arguments);
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 4.0.0
+ */
+ public function __construct($name, array $arguments = array())
+ {
+ if (!\array_key_exists('subject', $arguments)) {
+ throw new BadMethodCallException("Argument 'subject' of event {$this->name} is required but has not been provided");
+ }
+
+ if (!\array_key_exists('extension', $arguments)) {
+ throw new BadMethodCallException("Argument 'extension' of event {$this->name} is required but has not been provided");
+ }
+
+ if (strpos($arguments['extension'], '.') === false) {
+ throw new BadMethodCallException("Argument 'extension' of event {$this->name} has wrong format. Valid format: 'component.section'");
+ }
+
+ if (!\array_key_exists('extensionName', $arguments) || !\array_key_exists('section', $arguments)) {
+ $parts = explode('.', $arguments['extension']);
+
+ $arguments['extensionName'] = $arguments['extensionName'] ?? $parts[0];
+ $arguments['section'] = $arguments['section'] ?? $parts[1];
+ }
+
+ parent::__construct($name, $arguments);
+ }
}
diff --git a/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php b/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php
index b89ac099612ae..edfe8974b4e9f 100644
--- a/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php
+++ b/libraries/src/Event/Workflow/WorkflowFunctionalityUsedEvent.php
@@ -1,4 +1,5 @@
arguments['used'] = $value;
+ /**
+ * Set used parameter to true
+ *
+ * @param bool $value The value to set
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setUsed($value = true)
+ {
+ $this->arguments['used'] = $value;
- if ($value === true)
- {
- $this->stopPropagation();
- }
- }
+ if ($value === true) {
+ $this->stopPropagation();
+ }
+ }
}
diff --git a/libraries/src/Event/Workflow/WorkflowTransitionEvent.php b/libraries/src/Event/Workflow/WorkflowTransitionEvent.php
index 5fbeb31ae48fa..f02ee500cbd5d 100644
--- a/libraries/src/Event/Workflow/WorkflowTransitionEvent.php
+++ b/libraries/src/Event/Workflow/WorkflowTransitionEvent.php
@@ -1,4 +1,5 @@
arguments['stopTransition'] = $value;
-
- if ($value === true)
- {
- $this->stopPropagation();
- }
- }
-
-
+ /**
+ * Constructor.
+ *
+ * @param string $name The event name.
+ * @param array $arguments The event arguments.
+ *
+ * @throws BadMethodCallException
+ *
+ * @since 4.0.0
+ */
+ public function __construct($name, array $arguments = array())
+ {
+ $arguments['stopTransition'] = false;
+
+ parent::__construct($name, $arguments);
+ }
+
+ /**
+ * Set used parameter to true
+ *
+ * @param bool $value The value to set
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function setStopTransition($value = true)
+ {
+ $this->arguments['stopTransition'] = $value;
+
+ if ($value === true) {
+ $this->stopPropagation();
+ }
+ }
}
diff --git a/libraries/src/Exception/ExceptionHandler.php b/libraries/src/Exception/ExceptionHandler.php
index 561adfe8e3f43..7004ec26264c4 100644
--- a/libraries/src/Exception/ExceptionHandler.php
+++ b/libraries/src/Exception/ExceptionHandler.php
@@ -1,4 +1,5 @@
isClient('cli');
-
- // If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404)
- if (!$isCli && $error->getCode() == '404' && $app->get('offline') == 1)
- {
- $app->redirect('index.php');
- }
-
- /*
- * Try and determine the format to render the error page in
- *
- * First we check if a Document instance was registered to Factory and use the type from that if available
- * If a type doesn't exist for that format, we try to use the format from the application's Input object
- * Lastly, if all else fails, we default onto the HTML format to at least render something
- */
- if (Factory::$document)
- {
- $format = Factory::$document->getType();
- }
- else
- {
- $format = $app->input->getString('format', 'html');
- }
-
- try
- {
- $renderer = AbstractRenderer::getRenderer($format);
- }
- catch (\InvalidArgumentException $e)
- {
- // Default to the HTML renderer
- $renderer = AbstractRenderer::getRenderer('html');
- }
-
- // Reset the document object in the factory, this gives us a clean slate and lets everything render properly
- Factory::$document = $renderer->getDocument();
- Factory::getApplication()->loadDocument(Factory::$document);
-
- $data = $renderer->render($error);
-
- // If nothing was rendered, just use the message from the Exception
- if (empty($data))
- {
- $data = $error->getMessage();
- }
-
- if ($isCli)
- {
- echo $data;
- }
- else
- {
- /** @var CMSApplication $app */
-
- // Do not allow cache
- $app->allowCache(false);
-
- $app->setBody($data);
- }
-
- // This return is needed to ensure the test suite does not trigger the non-Exception handling below
- return;
- }
- catch (\Throwable $errorRendererError)
- {
- // Pass the error down
- }
-
- /*
- * To reach this point in the code means there was an error creating the error page.
- *
- * Let global handler to handle the error, @see bootstrap.php
- */
- if (isset($errorRendererError))
- {
- /*
- * Here the thing, at this point we have 2 exceptions:
- * $errorRendererError - the error caused by error renderer
- * $error - the main error
- *
- * We need to show both exceptions, without loss of trace information, so use a bit of magic to merge them.
- *
- * Use exception nesting feature: rethrow the exceptions, an exception thrown in a finally block
- * will take unhandled exception as previous.
- * So PHP will add $error Exception as previous to $errorRendererError Exception to keep full error stack.
- */
- try
- {
- try
- {
- throw $error;
- }
- finally
- {
- throw $errorRendererError;
- }
- }
- catch (\Throwable $finalError)
- {
- throw $finalError;
- }
- }
- else
- {
- throw $error;
- }
-
- }
-
- /**
- * Checks if given error belong to PHP exception class (\Throwable for PHP 7+, \Exception for PHP 5-).
- *
- * @param mixed $error Any error value.
- *
- * @return boolean
- *
- * @since 3.10.0
- */
- protected static function isException($error)
- {
- return $error instanceof \Throwable;
- }
-
- /**
- * Logs exception, catching all possible errors during logging.
- *
- * @param \Throwable $error An Exception or Throwable (PHP 7+) object to get error message from.
- *
- * @return void
- *
- * @since 3.10.0
- */
- protected static function logException(\Throwable $error)
- {
- // Try to log the error, but don't let the logging cause a fatal error
- try
- {
- Log::add(
- sprintf(
- 'Uncaught Throwable of type %1$s thrown with message "%2$s". Stack trace: %3$s',
- \get_class($error),
- $error->getMessage(),
- $error->getTraceAsString()
- ),
- Log::CRITICAL,
- 'error'
- );
- }
- catch (\Throwable $e)
- {
- // Logging failed, don't make a stink about it though
- }
- }
+ /**
+ * Handles an error triggered with the E_USER_DEPRECATED level.
+ *
+ * @param integer $errorNumber The level of the raised error, represented by the E_* constants.
+ * @param string $errorMessage The error message.
+ * @param string $errorFile The file the error was triggered from.
+ * @param integer $errorLine The line number the error was triggered from.
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public static function handleUserDeprecatedErrors(int $errorNumber, string $errorMessage, string $errorFile, int $errorLine): bool
+ {
+ // We only want to handle user deprecation messages, these will be triggered in code
+ if ($errorNumber === E_USER_DEPRECATED) {
+ try {
+ Log::add($errorMessage, Log::WARNING, 'deprecated');
+ } catch (\Exception $e) {
+ // Silence
+ }
+
+ // If debug mode is enabled, we want to let PHP continue to handle the error; otherwise, we can bail early
+ if (\defined('JDEBUG') && JDEBUG) {
+ return true;
+ }
+ }
+
+ // Always return false, this will tell PHP to handle the error internally
+ return false;
+ }
+
+ /**
+ * Handles exceptions: logs errors and renders error page.
+ *
+ * @param \Exception|\Throwable $error An Exception or Throwable (PHP 7+) object for which to render the error page.
+ *
+ * @return void
+ *
+ * @since 3.10.0
+ */
+ public static function handleException(\Throwable $error)
+ {
+ static::logException($error);
+ static::render($error);
+ }
+
+ /**
+ * Render the error page based on an exception.
+ *
+ * @param \Throwable $error An Exception or Throwable (PHP 7+) object for which to render the error page.
+ *
+ * @return void
+ *
+ * @since 3.0
+ */
+ public static function render(\Throwable $error)
+ {
+ try {
+ $app = Factory::getApplication();
+
+ // Flag if we are on cli
+ $isCli = $app->isClient('cli');
+
+ // If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404)
+ if (!$isCli && $error->getCode() == '404' && $app->get('offline') == 1) {
+ $app->redirect('index.php');
+ }
+
+ /*
+ * Try and determine the format to render the error page in
+ *
+ * First we check if a Document instance was registered to Factory and use the type from that if available
+ * If a type doesn't exist for that format, we try to use the format from the application's Input object
+ * Lastly, if all else fails, we default onto the HTML format to at least render something
+ */
+ if (Factory::$document) {
+ $format = Factory::$document->getType();
+ } else {
+ $format = $app->input->getString('format', 'html');
+ }
+
+ try {
+ $renderer = AbstractRenderer::getRenderer($format);
+ } catch (\InvalidArgumentException $e) {
+ // Default to the HTML renderer
+ $renderer = AbstractRenderer::getRenderer('html');
+ }
+
+ // Reset the document object in the factory, this gives us a clean slate and lets everything render properly
+ Factory::$document = $renderer->getDocument();
+ Factory::getApplication()->loadDocument(Factory::$document);
+
+ $data = $renderer->render($error);
+
+ // If nothing was rendered, just use the message from the Exception
+ if (empty($data)) {
+ $data = $error->getMessage();
+ }
+
+ if ($isCli) {
+ echo $data;
+ } else {
+ /** @var CMSApplication $app */
+
+ // Do not allow cache
+ $app->allowCache(false);
+
+ $app->setBody($data);
+ }
+
+ // This return is needed to ensure the test suite does not trigger the non-Exception handling below
+ return;
+ } catch (\Throwable $errorRendererError) {
+ // Pass the error down
+ }
+
+ /*
+ * To reach this point in the code means there was an error creating the error page.
+ *
+ * Let global handler to handle the error, @see bootstrap.php
+ */
+ if (isset($errorRendererError)) {
+ /*
+ * Here the thing, at this point we have 2 exceptions:
+ * $errorRendererError - the error caused by error renderer
+ * $error - the main error
+ *
+ * We need to show both exceptions, without loss of trace information, so use a bit of magic to merge them.
+ *
+ * Use exception nesting feature: rethrow the exceptions, an exception thrown in a finally block
+ * will take unhandled exception as previous.
+ * So PHP will add $error Exception as previous to $errorRendererError Exception to keep full error stack.
+ */
+ try {
+ try {
+ throw $error;
+ } finally {
+ throw $errorRendererError;
+ }
+ } catch (\Throwable $finalError) {
+ throw $finalError;
+ }
+ } else {
+ throw $error;
+ }
+ }
+
+ /**
+ * Checks if given error belong to PHP exception class (\Throwable for PHP 7+, \Exception for PHP 5-).
+ *
+ * @param mixed $error Any error value.
+ *
+ * @return boolean
+ *
+ * @since 3.10.0
+ */
+ protected static function isException($error)
+ {
+ return $error instanceof \Throwable;
+ }
+
+ /**
+ * Logs exception, catching all possible errors during logging.
+ *
+ * @param \Throwable $error An Exception or Throwable (PHP 7+) object to get error message from.
+ *
+ * @return void
+ *
+ * @since 3.10.0
+ */
+ protected static function logException(\Throwable $error)
+ {
+ // Try to log the error, but don't let the logging cause a fatal error
+ try {
+ Log::add(
+ sprintf(
+ 'Uncaught Throwable of type %1$s thrown with message "%2$s". Stack trace: %3$s',
+ \get_class($error),
+ $error->getMessage(),
+ $error->getTraceAsString()
+ ),
+ Log::CRITICAL,
+ 'error'
+ );
+ } catch (\Throwable $e) {
+ // Logging failed, don't make a stink about it though
+ }
+ }
}
diff --git a/libraries/src/Extension/BootableExtensionInterface.php b/libraries/src/Extension/BootableExtensionInterface.php
index bd07d119090ce..9d8ebdcf8bb9a 100644
--- a/libraries/src/Extension/BootableExtensionInterface.php
+++ b/libraries/src/Extension/BootableExtensionInterface.php
@@ -1,4 +1,5 @@
dispatcherFactory = $dispatcherFactory;
- }
+ /**
+ * Component constructor.
+ *
+ * @param ComponentDispatcherFactoryInterface $dispatcherFactory The dispatcher factory
+ *
+ * @since 4.0.0
+ */
+ public function __construct(ComponentDispatcherFactoryInterface $dispatcherFactory)
+ {
+ $this->dispatcherFactory = $dispatcherFactory;
+ }
- /**
- * Returns the dispatcher for the given application.
- *
- * @param CMSApplicationInterface $application The application
- *
- * @return DispatcherInterface
- *
- * @since 4.0.0
- */
- public function getDispatcher(CMSApplicationInterface $application): DispatcherInterface
- {
- return $this->dispatcherFactory->createDispatcher($application);
- }
+ /**
+ * Returns the dispatcher for the given application.
+ *
+ * @param CMSApplicationInterface $application The application
+ *
+ * @return DispatcherInterface
+ *
+ * @since 4.0.0
+ */
+ public function getDispatcher(CMSApplicationInterface $application): DispatcherInterface
+ {
+ return $this->dispatcherFactory->createDispatcher($application);
+ }
}
diff --git a/libraries/src/Extension/ComponentInterface.php b/libraries/src/Extension/ComponentInterface.php
index ff9981c972bc3..259bc5442dd0b 100644
--- a/libraries/src/Extension/ComponentInterface.php
+++ b/libraries/src/Extension/ComponentInterface.php
@@ -1,4 +1,5 @@
[], ComponentInterface::class => [], PluginInterface::class => []];
-
- /**
- * The loaded extensions.
- *
- * @var array
- * @since 4.0.0
- */
- private static $loadedExtensions = [];
-
- /**
- * Array of core extensions
- * Each element is an array with elements "type", "element", "folder" and
- * "client_id".
- *
- * @var array
- * @since 3.7.4
- */
- protected static $coreExtensions = array(
- // Format: `type`, `element`, `folder`, `client_id`
-
- // Core component extensions
- array('component', 'com_actionlogs', '', 1),
- array('component', 'com_admin', '', 1),
- array('component', 'com_ajax', '', 1),
- array('component', 'com_associations', '', 1),
- array('component', 'com_banners', '', 1),
- array('component', 'com_cache', '', 1),
- array('component', 'com_categories', '', 1),
- array('component', 'com_checkin', '', 1),
- array('component', 'com_config', '', 1),
- array('component', 'com_contact', '', 1),
- array('component', 'com_content', '', 1),
- array('component', 'com_contenthistory', '', 1),
- array('component', 'com_cpanel', '', 1),
- array('component', 'com_fields', '', 1),
- array('component', 'com_finder', '', 1),
- array('component', 'com_installer', '', 1),
- array('component', 'com_joomlaupdate', '', 1),
- array('component', 'com_languages', '', 1),
- array('component', 'com_login', '', 1),
- array('component', 'com_mails', '', 1),
- array('component', 'com_media', '', 1),
- array('component', 'com_menus', '', 1),
- array('component', 'com_messages', '', 1),
- array('component', 'com_modules', '', 1),
- array('component', 'com_newsfeeds', '', 1),
- array('component', 'com_plugins', '', 1),
- array('component', 'com_postinstall', '', 1),
- array('component', 'com_privacy', '', 1),
- array('component', 'com_redirect', '', 1),
- array('component', 'com_scheduler', '', 1),
- array('component', 'com_tags', '', 1),
- array('component', 'com_templates', '', 1),
- array('component', 'com_users', '', 1),
- array('component', 'com_workflow', '', 1),
- array('component', 'com_wrapper', '', 1),
-
- // Core file extensions
- array('file', 'joomla', '', 0),
-
- // Core language extensions - administrator
- array('language', 'en-GB', '', 1),
-
- // Core language extensions - site
- array('language', 'en-GB', '', 0),
-
- // Core language extensions - API
- array('language', 'en-GB', '', 3),
-
- // Core library extensions
- array('library', 'joomla', '', 0),
- array('library', 'phpass', '', 0),
-
- // Core module extensions - administrator
- array('module', 'mod_custom', '', 1),
- array('module', 'mod_feed', '', 1),
- array('module', 'mod_frontend', '', 1),
- array('module', 'mod_latest', '', 1),
- array('module', 'mod_latestactions', '', 1),
- array('module', 'mod_logged', '', 1),
- array('module', 'mod_login', '', 1),
- array('module', 'mod_loginsupport', '', 1),
- array('module', 'mod_menu', '', 1),
- array('module', 'mod_messages', '', 1),
- array('module', 'mod_multilangstatus', '', 1),
- array('module', 'mod_popular', '', 1),
- array('module', 'mod_post_installation_messages', '', 1),
- array('module', 'mod_privacy_dashboard', '', 1),
- array('module', 'mod_privacy_status', '', 1),
- array('module', 'mod_quickicon', '', 1),
- array('module', 'mod_sampledata', '', 1),
- array('module', 'mod_stats_admin', '', 1),
- array('module', 'mod_submenu', '', 1),
- array('module', 'mod_title', '', 1),
- array('module', 'mod_toolbar', '', 1),
- array('module', 'mod_user', '', 1),
- array('module', 'mod_version', '', 1),
-
- // Core module extensions - site
- array('module', 'mod_articles_archive', '', 0),
- array('module', 'mod_articles_categories', '', 0),
- array('module', 'mod_articles_category', '', 0),
- array('module', 'mod_articles_latest', '', 0),
- array('module', 'mod_articles_news', '', 0),
- array('module', 'mod_articles_popular', '', 0),
- array('module', 'mod_banners', '', 0),
- array('module', 'mod_breadcrumbs', '', 0),
- array('module', 'mod_custom', '', 0),
- array('module', 'mod_feed', '', 0),
- array('module', 'mod_finder', '', 0),
- array('module', 'mod_footer', '', 0),
- array('module', 'mod_languages', '', 0),
- array('module', 'mod_login', '', 0),
- array('module', 'mod_menu', '', 0),
- array('module', 'mod_random_image', '', 0),
- array('module', 'mod_related_items', '', 0),
- array('module', 'mod_stats', '', 0),
- array('module', 'mod_syndicate', '', 0),
- array('module', 'mod_tags_popular', '', 0),
- array('module', 'mod_tags_similar', '', 0),
- array('module', 'mod_users_latest', '', 0),
- array('module', 'mod_whosonline', '', 0),
- array('module', 'mod_wrapper', '', 0),
-
- // Core package extensions
- array('package', 'pkg_en-GB', '', 0),
-
- // Core plugin extensions - actionlog
- array('plugin', 'joomla', 'actionlog', 0),
-
- // Core plugin extensions - API Authentication
- array('plugin', 'basic', 'api-authentication', 0),
- array('plugin', 'token', 'api-authentication', 0),
-
- // Core plugin extensions - authentication
- array('plugin', 'cookie', 'authentication', 0),
- array('plugin', 'joomla', 'authentication', 0),
- array('plugin', 'ldap', 'authentication', 0),
-
- // Core plugin extensions - behaviour
- array('plugin', 'taggable', 'behaviour', 0),
- array('plugin', 'versionable', 'behaviour', 0),
-
- // Core plugin extensions - captcha
- array('plugin', 'recaptcha', 'captcha', 0),
- array('plugin', 'recaptcha_invisible', 'captcha', 0),
-
- // Core plugin extensions - content
- array('plugin', 'confirmconsent', 'content', 0),
- array('plugin', 'contact', 'content', 0),
- array('plugin', 'emailcloak', 'content', 0),
- array('plugin', 'fields', 'content', 0),
- array('plugin', 'finder', 'content', 0),
- array('plugin', 'joomla', 'content', 0),
- array('plugin', 'loadmodule', 'content', 0),
- array('plugin', 'pagebreak', 'content', 0),
- array('plugin', 'pagenavigation', 'content', 0),
- array('plugin', 'vote', 'content', 0),
-
- // Core plugin extensions - editors
- array('plugin', 'codemirror', 'editors', 0),
- array('plugin', 'none', 'editors', 0),
- array('plugin', 'tinymce', 'editors', 0),
-
- // Core plugin extensions - editors xtd
- array('plugin', 'article', 'editors-xtd', 0),
- array('plugin', 'contact', 'editors-xtd', 0),
- array('plugin', 'fields', 'editors-xtd', 0),
- array('plugin', 'image', 'editors-xtd', 0),
- array('plugin', 'menu', 'editors-xtd', 0),
- array('plugin', 'module', 'editors-xtd', 0),
- array('plugin', 'pagebreak', 'editors-xtd', 0),
- array('plugin', 'readmore', 'editors-xtd', 0),
-
- // Core plugin extensions - extension
- array('plugin', 'joomla', 'extension', 0),
- array('plugin', 'namespacemap', 'extension', 0),
- array('plugin', 'finder', 'extension', 0),
-
- // Core plugin extensions - fields
- array('plugin', 'calendar', 'fields', 0),
- array('plugin', 'checkboxes', 'fields', 0),
- array('plugin', 'color', 'fields', 0),
- array('plugin', 'editor', 'fields', 0),
- array('plugin', 'imagelist', 'fields', 0),
- array('plugin', 'integer', 'fields', 0),
- array('plugin', 'list', 'fields', 0),
- array('plugin', 'media', 'fields', 0),
- array('plugin', 'radio', 'fields', 0),
- array('plugin', 'sql', 'fields', 0),
- array('plugin', 'subform', 'fields', 0),
- array('plugin', 'text', 'fields', 0),
- array('plugin', 'textarea', 'fields', 0),
- array('plugin', 'url', 'fields', 0),
- array('plugin', 'user', 'fields', 0),
- array('plugin', 'usergrouplist', 'fields', 0),
-
- // Core plugin extensions - filesystem
- array('plugin', 'local', 'filesystem', 0),
-
- // Core plugin extensions - finder
- array('plugin', 'categories', 'finder', 0),
- array('plugin', 'contacts', 'finder', 0),
- array('plugin', 'content', 'finder', 0),
- array('plugin', 'newsfeeds', 'finder', 0),
- array('plugin', 'tags', 'finder', 0),
-
- // Core plugin extensions - installer
- array('plugin', 'folderinstaller', 'installer', 0),
- array('plugin', 'override', 'installer', 0),
- array('plugin', 'packageinstaller', 'installer', 0),
- array('plugin', 'urlinstaller', 'installer', 0),
- array('plugin', 'webinstaller', 'installer', 0),
-
- // Core plugin extensions - media-action
- array('plugin', 'crop', 'media-action', 0),
- array('plugin', 'resize', 'media-action', 0),
- array('plugin', 'rotate', 'media-action', 0),
-
- // Core plugin extensions - Multi-factor Authentication
- array('plugin', 'email', 'multifactorauth', 0),
- array('plugin', 'fixed', 'multifactorauth', 0),
- array('plugin', 'totp', 'multifactorauth', 0),
- array('plugin', 'webauthn', 'multifactorauth', 0),
- array('plugin', 'yubikey', 'multifactorauth', 0),
-
- // Core plugin extensions - privacy
- array('plugin', 'actionlogs', 'privacy', 0),
- array('plugin', 'consents', 'privacy', 0),
- array('plugin', 'contact', 'privacy', 0),
- array('plugin', 'content', 'privacy', 0),
- array('plugin', 'message', 'privacy', 0),
- array('plugin', 'user', 'privacy', 0),
-
- // Core plugin extensions - quick icon
- array('plugin', 'downloadkey', 'quickicon', 0),
- array('plugin', 'extensionupdate', 'quickicon', 0),
- array('plugin', 'joomlaupdate', 'quickicon', 0),
- array('plugin', 'overridecheck', 'quickicon', 0),
- array('plugin', 'phpversioncheck', 'quickicon', 0),
- array('plugin', 'privacycheck', 'quickicon', 0),
-
- // Core plugin extensions - sample data
- array('plugin', 'blog', 'sampledata', 0),
- array('plugin', 'multilang', 'sampledata', 0),
-
- // Core plugin extensions - system
- array('plugin', 'accessibility', 'system', 0),
- array('plugin', 'actionlogs', 'system', 0),
- array('plugin', 'cache', 'system', 0),
- array('plugin', 'debug', 'system', 0),
- array('plugin', 'fields', 'system', 0),
- array('plugin', 'highlight', 'system', 0),
- array('plugin', 'httpheaders', 'system', 0),
- array('plugin', 'jooa11y', 'system', 0),
- array('plugin', 'languagecode', 'system', 0),
- array('plugin', 'languagefilter', 'system', 0),
- array('plugin', 'log', 'system', 0),
- array('plugin', 'logout', 'system', 0),
- array('plugin', 'logrotation', 'system', 0),
- array('plugin', 'privacyconsent', 'system', 0),
- array('plugin', 'redirect', 'system', 0),
- array('plugin', 'remember', 'system', 0),
- array('plugin', 'schedulerunner', 'system', 0),
- array('plugin', 'sef', 'system', 0),
- array('plugin', 'sessiongc', 'system', 0),
- array('plugin', 'shortcut', 'system', 0),
- array('plugin', 'skipto', 'system', 0),
- array('plugin', 'stats', 'system', 0),
- array('plugin', 'tasknotification', 'system', 0),
- array('plugin', 'updatenotification', 'system', 0),
- array('plugin', 'webauthn', 'system', 0),
-
- // Core plugin extensions - task scheduler
- array('plugin', 'checkfiles', 'task', 0),
- array('plugin', 'demotasks', 'task', 0),
- array('plugin', 'requests', 'task', 0),
- array('plugin', 'sitestatus', 'task', 0),
-
- // Core plugin extensions - user
- array('plugin', 'contactcreator', 'user', 0),
- array('plugin', 'joomla', 'user', 0),
- array('plugin', 'profile', 'user', 0),
- array('plugin', 'terms', 'user', 0),
- array('plugin', 'token', 'user', 0),
-
- // Core plugin extensions - webservices
- array('plugin', 'banners', 'webservices', 0),
- array('plugin', 'config', 'webservices', 0),
- array('plugin', 'contact', 'webservices', 0),
- array('plugin', 'content', 'webservices', 0),
- array('plugin', 'installer', 'webservices', 0),
- array('plugin', 'languages', 'webservices', 0),
- array('plugin', 'media', 'webservices', 0),
- array('plugin', 'menus', 'webservices', 0),
- array('plugin', 'messages', 'webservices', 0),
- array('plugin', 'modules', 'webservices', 0),
- array('plugin', 'newsfeeds', 'webservices', 0),
- array('plugin', 'plugins', 'webservices', 0),
- array('plugin', 'privacy', 'webservices', 0),
- array('plugin', 'redirect', 'webservices', 0),
- array('plugin', 'tags', 'webservices', 0),
- array('plugin', 'templates', 'webservices', 0),
- array('plugin', 'users', 'webservices', 0),
-
- // Core plugin extensions - workflow
- array('plugin', 'featuring', 'workflow', 0),
- array('plugin', 'notification', 'workflow', 0),
- array('plugin', 'publishing', 'workflow', 0),
-
- // Core template extensions - administrator
- array('template', 'atum', '', 1),
-
- // Core template extensions - site
- array('template', 'cassiopeia', '', 0),
- );
-
- /**
- * Array of core extension IDs.
- *
- * @var array
- * @since 4.0.0
- */
- protected static $coreExtensionIds;
-
- /**
- * Gets the core extensions.
- *
- * @return array Array with core extensions.
- * Each extension is an array with following format:
- * `type`, `element`, `folder`, `client_id`.
- *
- * @since 3.7.4
- */
- public static function getCoreExtensions()
- {
- return self::$coreExtensions;
- }
-
- /**
- * Returns an array of core extension IDs.
- *
- * @return array
- *
- * @since 4.0.0
- * @throws \RuntimeException
- */
- public static function getCoreExtensionIds()
- {
- if (self::$coreExtensionIds !== null)
- {
- return self::$coreExtensionIds;
- }
-
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select($db->quoteName('extension_id'))
- ->from($db->quoteName('#__extensions'));
-
- foreach (self::$coreExtensions as $extension)
- {
- $values = $query->bindArray($extension, [ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER]);
- $query->where(
- '(' . $db->quoteName('type') . ' = ' . $values[0] . ' AND ' . $db->quoteName('element') . ' = ' . $values[1]
- . ' AND ' . $db->quoteName('folder') . ' = ' . $values[2] . ' AND ' . $db->quoteName('client_id') . ' = ' . $values[3] . ')',
- 'OR'
- );
- }
-
- $db->setQuery($query);
- self::$coreExtensionIds = $db->loadColumn();
-
- return self::$coreExtensionIds;
- }
-
- /**
- * Check if an extension is core or not
- *
- * @param string $type The extension's type.
- * @param string $element The extension's element name.
- * @param integer $clientId The extension's client ID. Default 0.
- * @param string $folder The extension's folder. Default ''.
- *
- * @return boolean True if core, false if not.
- *
- * @since 3.7.4
- */
- public static function checkIfCoreExtension($type, $element, $clientId = 0, $folder = '')
- {
- return \in_array(array($type, $element, $folder, $clientId), self::$coreExtensions);
- }
-
- /**
- * Returns an extension record for the given name.
- *
- * @param string $element The extension element
- * @param string $type The extension type
- * @param integer|null $clientId The client ID
- * @param string|null $folder Plugin folder
- *
- * @return \stdClass|null The object or null if not found.
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException
- */
- public static function getExtensionRecord(string $element, string $type, ?int $clientId = null, ?string $folder = null): ?\stdClass
- {
- if ($type === 'plugin' && $folder === null)
- {
- throw new \InvalidArgumentException(sprintf('`$folder` is required when `$type` is `plugin` in %s()', __METHOD__));
- }
-
- if (\in_array($type, ['module', 'language', 'template'], true) && $clientId === null)
- {
- throw new \InvalidArgumentException(
- sprintf('`$clientId` is required when `$type` is `module`, `language` or `template` in %s()', __METHOD__)
- );
- }
-
- $key = $element . '.' . $type . '.' . $clientId . '.' . $folder;
-
- if (!\array_key_exists($key, self::$loadedExtensions))
- {
- $db = Factory::getDbo();
- $query = $db->getQuery(true)
- ->select('*')
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('element') . ' = :element',
- $db->quoteName('type') . ' = :type',
- ]
- )
- ->bind(':element', $element)
- ->bind(':type', $type);
-
- if ($clientId !== null)
- {
- $query->where($db->quoteName('client_id') . ' = :clientId')
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
- }
-
- if ($folder !== null)
- {
- $query->where($db->quoteName('folder') . ' = :folder')
- ->bind(':folder', $folder);
- }
-
- $query->setLimit(1);
- $db->setQuery($query);
-
- self::$loadedExtensions[$key] = $db->loadObject();
- }
-
- return self::$loadedExtensions[$key];
- }
+ /**
+ * The loaded extensions.
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ public static $extensions = [ModuleInterface::class => [], ComponentInterface::class => [], PluginInterface::class => []];
+
+ /**
+ * The loaded extensions.
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ private static $loadedExtensions = [];
+
+ /**
+ * Array of core extensions
+ * Each element is an array with elements "type", "element", "folder" and
+ * "client_id".
+ *
+ * @var array
+ * @since 3.7.4
+ */
+ protected static $coreExtensions = array(
+ // Format: `type`, `element`, `folder`, `client_id`
+
+ // Core component extensions
+ array('component', 'com_actionlogs', '', 1),
+ array('component', 'com_admin', '', 1),
+ array('component', 'com_ajax', '', 1),
+ array('component', 'com_associations', '', 1),
+ array('component', 'com_banners', '', 1),
+ array('component', 'com_cache', '', 1),
+ array('component', 'com_categories', '', 1),
+ array('component', 'com_checkin', '', 1),
+ array('component', 'com_config', '', 1),
+ array('component', 'com_contact', '', 1),
+ array('component', 'com_content', '', 1),
+ array('component', 'com_contenthistory', '', 1),
+ array('component', 'com_cpanel', '', 1),
+ array('component', 'com_fields', '', 1),
+ array('component', 'com_finder', '', 1),
+ array('component', 'com_installer', '', 1),
+ array('component', 'com_joomlaupdate', '', 1),
+ array('component', 'com_languages', '', 1),
+ array('component', 'com_login', '', 1),
+ array('component', 'com_mails', '', 1),
+ array('component', 'com_media', '', 1),
+ array('component', 'com_menus', '', 1),
+ array('component', 'com_messages', '', 1),
+ array('component', 'com_modules', '', 1),
+ array('component', 'com_newsfeeds', '', 1),
+ array('component', 'com_plugins', '', 1),
+ array('component', 'com_postinstall', '', 1),
+ array('component', 'com_privacy', '', 1),
+ array('component', 'com_redirect', '', 1),
+ array('component', 'com_scheduler', '', 1),
+ array('component', 'com_tags', '', 1),
+ array('component', 'com_templates', '', 1),
+ array('component', 'com_users', '', 1),
+ array('component', 'com_workflow', '', 1),
+ array('component', 'com_wrapper', '', 1),
+
+ // Core file extensions
+ array('file', 'joomla', '', 0),
+
+ // Core language extensions - administrator
+ array('language', 'en-GB', '', 1),
+
+ // Core language extensions - site
+ array('language', 'en-GB', '', 0),
+
+ // Core language extensions - API
+ array('language', 'en-GB', '', 3),
+
+ // Core library extensions
+ array('library', 'joomla', '', 0),
+ array('library', 'phpass', '', 0),
+
+ // Core module extensions - administrator
+ array('module', 'mod_custom', '', 1),
+ array('module', 'mod_feed', '', 1),
+ array('module', 'mod_frontend', '', 1),
+ array('module', 'mod_latest', '', 1),
+ array('module', 'mod_latestactions', '', 1),
+ array('module', 'mod_logged', '', 1),
+ array('module', 'mod_login', '', 1),
+ array('module', 'mod_loginsupport', '', 1),
+ array('module', 'mod_menu', '', 1),
+ array('module', 'mod_messages', '', 1),
+ array('module', 'mod_multilangstatus', '', 1),
+ array('module', 'mod_popular', '', 1),
+ array('module', 'mod_post_installation_messages', '', 1),
+ array('module', 'mod_privacy_dashboard', '', 1),
+ array('module', 'mod_privacy_status', '', 1),
+ array('module', 'mod_quickicon', '', 1),
+ array('module', 'mod_sampledata', '', 1),
+ array('module', 'mod_stats_admin', '', 1),
+ array('module', 'mod_submenu', '', 1),
+ array('module', 'mod_title', '', 1),
+ array('module', 'mod_toolbar', '', 1),
+ array('module', 'mod_user', '', 1),
+ array('module', 'mod_version', '', 1),
+
+ // Core module extensions - site
+ array('module', 'mod_articles_archive', '', 0),
+ array('module', 'mod_articles_categories', '', 0),
+ array('module', 'mod_articles_category', '', 0),
+ array('module', 'mod_articles_latest', '', 0),
+ array('module', 'mod_articles_news', '', 0),
+ array('module', 'mod_articles_popular', '', 0),
+ array('module', 'mod_banners', '', 0),
+ array('module', 'mod_breadcrumbs', '', 0),
+ array('module', 'mod_custom', '', 0),
+ array('module', 'mod_feed', '', 0),
+ array('module', 'mod_finder', '', 0),
+ array('module', 'mod_footer', '', 0),
+ array('module', 'mod_languages', '', 0),
+ array('module', 'mod_login', '', 0),
+ array('module', 'mod_menu', '', 0),
+ array('module', 'mod_random_image', '', 0),
+ array('module', 'mod_related_items', '', 0),
+ array('module', 'mod_stats', '', 0),
+ array('module', 'mod_syndicate', '', 0),
+ array('module', 'mod_tags_popular', '', 0),
+ array('module', 'mod_tags_similar', '', 0),
+ array('module', 'mod_users_latest', '', 0),
+ array('module', 'mod_whosonline', '', 0),
+ array('module', 'mod_wrapper', '', 0),
+
+ // Core package extensions
+ array('package', 'pkg_en-GB', '', 0),
+
+ // Core plugin extensions - actionlog
+ array('plugin', 'joomla', 'actionlog', 0),
+
+ // Core plugin extensions - API Authentication
+ array('plugin', 'basic', 'api-authentication', 0),
+ array('plugin', 'token', 'api-authentication', 0),
+
+ // Core plugin extensions - authentication
+ array('plugin', 'cookie', 'authentication', 0),
+ array('plugin', 'joomla', 'authentication', 0),
+ array('plugin', 'ldap', 'authentication', 0),
+
+ // Core plugin extensions - behaviour
+ array('plugin', 'taggable', 'behaviour', 0),
+ array('plugin', 'versionable', 'behaviour', 0),
+
+ // Core plugin extensions - captcha
+ array('plugin', 'recaptcha', 'captcha', 0),
+ array('plugin', 'recaptcha_invisible', 'captcha', 0),
+
+ // Core plugin extensions - content
+ array('plugin', 'confirmconsent', 'content', 0),
+ array('plugin', 'contact', 'content', 0),
+ array('plugin', 'emailcloak', 'content', 0),
+ array('plugin', 'fields', 'content', 0),
+ array('plugin', 'finder', 'content', 0),
+ array('plugin', 'joomla', 'content', 0),
+ array('plugin', 'loadmodule', 'content', 0),
+ array('plugin', 'pagebreak', 'content', 0),
+ array('plugin', 'pagenavigation', 'content', 0),
+ array('plugin', 'vote', 'content', 0),
+
+ // Core plugin extensions - editors
+ array('plugin', 'codemirror', 'editors', 0),
+ array('plugin', 'none', 'editors', 0),
+ array('plugin', 'tinymce', 'editors', 0),
+
+ // Core plugin extensions - editors xtd
+ array('plugin', 'article', 'editors-xtd', 0),
+ array('plugin', 'contact', 'editors-xtd', 0),
+ array('plugin', 'fields', 'editors-xtd', 0),
+ array('plugin', 'image', 'editors-xtd', 0),
+ array('plugin', 'menu', 'editors-xtd', 0),
+ array('plugin', 'module', 'editors-xtd', 0),
+ array('plugin', 'pagebreak', 'editors-xtd', 0),
+ array('plugin', 'readmore', 'editors-xtd', 0),
+
+ // Core plugin extensions - extension
+ array('plugin', 'joomla', 'extension', 0),
+ array('plugin', 'namespacemap', 'extension', 0),
+ array('plugin', 'finder', 'extension', 0),
+
+ // Core plugin extensions - fields
+ array('plugin', 'calendar', 'fields', 0),
+ array('plugin', 'checkboxes', 'fields', 0),
+ array('plugin', 'color', 'fields', 0),
+ array('plugin', 'editor', 'fields', 0),
+ array('plugin', 'imagelist', 'fields', 0),
+ array('plugin', 'integer', 'fields', 0),
+ array('plugin', 'list', 'fields', 0),
+ array('plugin', 'media', 'fields', 0),
+ array('plugin', 'radio', 'fields', 0),
+ array('plugin', 'sql', 'fields', 0),
+ array('plugin', 'subform', 'fields', 0),
+ array('plugin', 'text', 'fields', 0),
+ array('plugin', 'textarea', 'fields', 0),
+ array('plugin', 'url', 'fields', 0),
+ array('plugin', 'user', 'fields', 0),
+ array('plugin', 'usergrouplist', 'fields', 0),
+
+ // Core plugin extensions - filesystem
+ array('plugin', 'local', 'filesystem', 0),
+
+ // Core plugin extensions - finder
+ array('plugin', 'categories', 'finder', 0),
+ array('plugin', 'contacts', 'finder', 0),
+ array('plugin', 'content', 'finder', 0),
+ array('plugin', 'newsfeeds', 'finder', 0),
+ array('plugin', 'tags', 'finder', 0),
+
+ // Core plugin extensions - installer
+ array('plugin', 'folderinstaller', 'installer', 0),
+ array('plugin', 'override', 'installer', 0),
+ array('plugin', 'packageinstaller', 'installer', 0),
+ array('plugin', 'urlinstaller', 'installer', 0),
+ array('plugin', 'webinstaller', 'installer', 0),
+
+ // Core plugin extensions - media-action
+ array('plugin', 'crop', 'media-action', 0),
+ array('plugin', 'resize', 'media-action', 0),
+ array('plugin', 'rotate', 'media-action', 0),
+
+ // Core plugin extensions - Multi-factor Authentication
+ array('plugin', 'email', 'multifactorauth', 0),
+ array('plugin', 'fixed', 'multifactorauth', 0),
+ array('plugin', 'totp', 'multifactorauth', 0),
+ array('plugin', 'webauthn', 'multifactorauth', 0),
+ array('plugin', 'yubikey', 'multifactorauth', 0),
+
+ // Core plugin extensions - privacy
+ array('plugin', 'actionlogs', 'privacy', 0),
+ array('plugin', 'consents', 'privacy', 0),
+ array('plugin', 'contact', 'privacy', 0),
+ array('plugin', 'content', 'privacy', 0),
+ array('plugin', 'message', 'privacy', 0),
+ array('plugin', 'user', 'privacy', 0),
+
+ // Core plugin extensions - quick icon
+ array('plugin', 'downloadkey', 'quickicon', 0),
+ array('plugin', 'extensionupdate', 'quickicon', 0),
+ array('plugin', 'joomlaupdate', 'quickicon', 0),
+ array('plugin', 'overridecheck', 'quickicon', 0),
+ array('plugin', 'phpversioncheck', 'quickicon', 0),
+ array('plugin', 'privacycheck', 'quickicon', 0),
+
+ // Core plugin extensions - sample data
+ array('plugin', 'blog', 'sampledata', 0),
+ array('plugin', 'multilang', 'sampledata', 0),
+
+ // Core plugin extensions - system
+ array('plugin', 'accessibility', 'system', 0),
+ array('plugin', 'actionlogs', 'system', 0),
+ array('plugin', 'cache', 'system', 0),
+ array('plugin', 'debug', 'system', 0),
+ array('plugin', 'fields', 'system', 0),
+ array('plugin', 'highlight', 'system', 0),
+ array('plugin', 'httpheaders', 'system', 0),
+ array('plugin', 'jooa11y', 'system', 0),
+ array('plugin', 'languagecode', 'system', 0),
+ array('plugin', 'languagefilter', 'system', 0),
+ array('plugin', 'log', 'system', 0),
+ array('plugin', 'logout', 'system', 0),
+ array('plugin', 'logrotation', 'system', 0),
+ array('plugin', 'privacyconsent', 'system', 0),
+ array('plugin', 'redirect', 'system', 0),
+ array('plugin', 'remember', 'system', 0),
+ array('plugin', 'schedulerunner', 'system', 0),
+ array('plugin', 'sef', 'system', 0),
+ array('plugin', 'sessiongc', 'system', 0),
+ array('plugin', 'shortcut', 'system', 0),
+ array('plugin', 'skipto', 'system', 0),
+ array('plugin', 'stats', 'system', 0),
+ array('plugin', 'tasknotification', 'system', 0),
+ array('plugin', 'updatenotification', 'system', 0),
+ array('plugin', 'webauthn', 'system', 0),
+
+ // Core plugin extensions - task scheduler
+ array('plugin', 'checkfiles', 'task', 0),
+ array('plugin', 'demotasks', 'task', 0),
+ array('plugin', 'requests', 'task', 0),
+ array('plugin', 'sitestatus', 'task', 0),
+
+ // Core plugin extensions - user
+ array('plugin', 'contactcreator', 'user', 0),
+ array('plugin', 'joomla', 'user', 0),
+ array('plugin', 'profile', 'user', 0),
+ array('plugin', 'terms', 'user', 0),
+ array('plugin', 'token', 'user', 0),
+
+ // Core plugin extensions - webservices
+ array('plugin', 'banners', 'webservices', 0),
+ array('plugin', 'config', 'webservices', 0),
+ array('plugin', 'contact', 'webservices', 0),
+ array('plugin', 'content', 'webservices', 0),
+ array('plugin', 'installer', 'webservices', 0),
+ array('plugin', 'languages', 'webservices', 0),
+ array('plugin', 'media', 'webservices', 0),
+ array('plugin', 'menus', 'webservices', 0),
+ array('plugin', 'messages', 'webservices', 0),
+ array('plugin', 'modules', 'webservices', 0),
+ array('plugin', 'newsfeeds', 'webservices', 0),
+ array('plugin', 'plugins', 'webservices', 0),
+ array('plugin', 'privacy', 'webservices', 0),
+ array('plugin', 'redirect', 'webservices', 0),
+ array('plugin', 'tags', 'webservices', 0),
+ array('plugin', 'templates', 'webservices', 0),
+ array('plugin', 'users', 'webservices', 0),
+
+ // Core plugin extensions - workflow
+ array('plugin', 'featuring', 'workflow', 0),
+ array('plugin', 'notification', 'workflow', 0),
+ array('plugin', 'publishing', 'workflow', 0),
+
+ // Core template extensions - administrator
+ array('template', 'atum', '', 1),
+
+ // Core template extensions - site
+ array('template', 'cassiopeia', '', 0),
+ );
+
+ /**
+ * Array of core extension IDs.
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected static $coreExtensionIds;
+
+ /**
+ * Gets the core extensions.
+ *
+ * @return array Array with core extensions.
+ * Each extension is an array with following format:
+ * `type`, `element`, `folder`, `client_id`.
+ *
+ * @since 3.7.4
+ */
+ public static function getCoreExtensions()
+ {
+ return self::$coreExtensions;
+ }
+
+ /**
+ * Returns an array of core extension IDs.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ * @throws \RuntimeException
+ */
+ public static function getCoreExtensionIds()
+ {
+ if (self::$coreExtensionIds !== null) {
+ return self::$coreExtensionIds;
+ }
+
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('extension_id'))
+ ->from($db->quoteName('#__extensions'));
+
+ foreach (self::$coreExtensions as $extension) {
+ $values = $query->bindArray($extension, [ParameterType::STRING, ParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER]);
+ $query->where(
+ '(' . $db->quoteName('type') . ' = ' . $values[0] . ' AND ' . $db->quoteName('element') . ' = ' . $values[1]
+ . ' AND ' . $db->quoteName('folder') . ' = ' . $values[2] . ' AND ' . $db->quoteName('client_id') . ' = ' . $values[3] . ')',
+ 'OR'
+ );
+ }
+
+ $db->setQuery($query);
+ self::$coreExtensionIds = $db->loadColumn();
+
+ return self::$coreExtensionIds;
+ }
+
+ /**
+ * Check if an extension is core or not
+ *
+ * @param string $type The extension's type.
+ * @param string $element The extension's element name.
+ * @param integer $clientId The extension's client ID. Default 0.
+ * @param string $folder The extension's folder. Default ''.
+ *
+ * @return boolean True if core, false if not.
+ *
+ * @since 3.7.4
+ */
+ public static function checkIfCoreExtension($type, $element, $clientId = 0, $folder = '')
+ {
+ return \in_array(array($type, $element, $folder, $clientId), self::$coreExtensions);
+ }
+
+ /**
+ * Returns an extension record for the given name.
+ *
+ * @param string $element The extension element
+ * @param string $type The extension type
+ * @param integer|null $clientId The client ID
+ * @param string|null $folder Plugin folder
+ *
+ * @return \stdClass|null The object or null if not found.
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ */
+ public static function getExtensionRecord(string $element, string $type, ?int $clientId = null, ?string $folder = null): ?\stdClass
+ {
+ if ($type === 'plugin' && $folder === null) {
+ throw new \InvalidArgumentException(sprintf('`$folder` is required when `$type` is `plugin` in %s()', __METHOD__));
+ }
+
+ if (\in_array($type, ['module', 'language', 'template'], true) && $clientId === null) {
+ throw new \InvalidArgumentException(
+ sprintf('`$clientId` is required when `$type` is `module`, `language` or `template` in %s()', __METHOD__)
+ );
+ }
+
+ $key = $element . '.' . $type . '.' . $clientId . '.' . $folder;
+
+ if (!\array_key_exists($key, self::$loadedExtensions)) {
+ $db = Factory::getDbo();
+ $query = $db->getQuery(true)
+ ->select('*')
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('element') . ' = :element',
+ $db->quoteName('type') . ' = :type',
+ ]
+ )
+ ->bind(':element', $element)
+ ->bind(':type', $type);
+
+ if ($clientId !== null) {
+ $query->where($db->quoteName('client_id') . ' = :clientId')
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+ }
+
+ if ($folder !== null) {
+ $query->where($db->quoteName('folder') . ' = :folder')
+ ->bind(':folder', $folder);
+ }
+
+ $query->setLimit(1);
+ $db->setQuery($query);
+
+ self::$loadedExtensions[$key] = $db->loadObject();
+ }
+
+ return self::$loadedExtensions[$key];
+ }
}
diff --git a/libraries/src/Extension/ExtensionManagerInterface.php b/libraries/src/Extension/ExtensionManagerInterface.php
index 8e7ea904b056f..5600c67fa559e 100644
--- a/libraries/src/Extension/ExtensionManagerInterface.php
+++ b/libraries/src/Extension/ExtensionManagerInterface.php
@@ -1,4 +1,5 @@
loadExtension(ComponentInterface::class, $component, $path);
- }
-
- /**
- * Boots the module with the given name.
- *
- * @param string $module The module to boot
- * @param string $applicationName The application name
- *
- * @return ModuleInterface
- *
- * @since 4.0.0
- */
- public function bootModule($module, $applicationName): ModuleInterface
- {
- // Normalize the module name
- $module = strtolower(str_replace('mod_', '', $module));
-
- // Path to to look for services
- $path = JPATH_SITE . '/modules/mod_' . $module;
-
- if ($applicationName === 'administrator')
- {
- $path = JPATH_ADMINISTRATOR . '/modules/mod_' . $module;
- }
-
- return $this->loadExtension(ModuleInterface::class, $module, $path);
- }
-
- /**
- * Boots the plugin with the given name and type.
- *
- * @param string $plugin The plugin name
- * @param string $type The type of the plugin
- *
- * @return PluginInterface
- *
- * @since 4.0.0
- */
- public function bootPlugin($plugin, $type): PluginInterface
- {
- // Normalize the module name
- $plugin = strtolower(str_replace('plg_', '', $plugin));
-
- // Path to to look for services
- $path = JPATH_SITE . '/plugins/' . $type . '/' . $plugin;
-
- return $this->loadExtension(PluginInterface::class, $plugin . ':' . $type, $path);
- }
-
- /**
- * Loads the extension.
- *
- * @param string $type The extension type
- * @param string $extensionName The extension name
- * @param string $extensionPath The path of the extension
- *
- * @return ComponentInterface|ModuleInterface|PluginInterface
- *
- * @since 4.0.0
- */
- private function loadExtension($type, $extensionName, $extensionPath)
- {
- // Check if the extension is already loaded
- if (!empty(ExtensionHelper::$extensions[$type][$extensionName]))
- {
- return ExtensionHelper::$extensions[$type][$extensionName];
- }
-
- // The container to get the services from
- $container = $this->getContainer()->createChild();
-
- $container->get(DispatcherInterface::class)->dispatch(
- 'onBeforeExtensionBoot',
- AbstractEvent::create(
- 'onBeforeExtensionBoot',
- [
- 'subject' => $this,
- 'type' => $type,
- 'extensionName' => $extensionName,
- 'container' => $container
- ]
- )
- );
-
- // The path of the loader file
- $path = $extensionPath . '/services/provider.php';
-
- if (is_file($path))
- {
- // Load the file
- $provider = require_once $path;
-
- // Check if the extension supports the service provider interface
- if ($provider instanceof ServiceProviderInterface)
- {
- $provider->register($container);
- }
- }
-
- // Fallback to legacy
- if (!$container->has($type))
- {
- switch ($type)
- {
- case ComponentInterface::class:
- $container->set($type, new LegacyComponent('com_' . $extensionName));
- break;
- case ModuleInterface::class:
- $container->set($type, new Module(new ModuleDispatcherFactory(''), new HelperFactory('')));
- break;
- case PluginInterface::class:
- list($pluginName, $pluginType) = explode(':', $extensionName);
- $container->set($type, $this->loadPluginFromFilesystem($pluginName, $pluginType));
- }
- }
-
- $container->get(DispatcherInterface::class)->dispatch(
- 'onAfterExtensionBoot',
- AbstractEvent::create(
- 'onAfterExtensionBoot',
- [
- 'subject' => $this,
- 'type' => $type,
- 'extensionName' => $extensionName,
- 'container' => $container
- ]
- )
- );
-
- $extension = $container->get($type);
-
- if ($extension instanceof BootableExtensionInterface)
- {
- $extension->boot($container);
- }
-
- // Cache the extension
- ExtensionHelper::$extensions[$type][$extensionName] = $extension;
-
- return $extension;
- }
-
- /**
- * Creates a CMS plugin from the filesystem.
- *
- * @param string $plugin The plugin
- * @param string $type The type
- *
- * @return CMSPlugin
- *
- * @since 4.0.0
- */
- private function loadPluginFromFilesystem(string $plugin, string $type)
- {
- // The dispatcher
- $dispatcher = $this->getContainer()->get(DispatcherInterface::class);
-
- // Clear the names
- $plugin = preg_replace('/[^A-Z0-9_\.-]/i', '', $plugin);
- $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
-
- // The path of the plugin
- $path = JPATH_PLUGINS . '/' . $type . '/' . $plugin . '/' . $plugin . '.php';
-
- // Return an empty class when the file doesn't exist
- if (!is_file($path))
- {
- return new DummyPlugin($dispatcher);
- }
-
- // Include the file of the plugin
- require_once $path;
-
- // Compile the classname
- $className = 'Plg' . str_replace('-', '', $type) . $plugin;
-
- if ($type === 'editors-xtd')
- {
- // This type doesn't follow the convention
- $className = 'PlgEditorsXtd' . $plugin;
-
- if (!class_exists($className))
- {
- $className = 'PlgButton' . $plugin;
- }
- }
-
- // Return an empty class when the class doesn't exist
- if (!class_exists($className))
- {
- return new DummyPlugin($dispatcher);
- }
-
- // Instantiate the plugin
- return new $className($dispatcher, (array) PluginHelper::getPlugin($type, $plugin));
- }
-
- /**
- * Get the DI container.
- *
- * @return Container
- *
- * @since 4.0.0
- * @throws ContainerNotFoundException May be thrown if the container has not been set.
- */
- abstract protected function getContainer();
+ /**
+ * Boots the component with the given name.
+ *
+ * @param string $component The component to boot.
+ *
+ * @return ComponentInterface
+ *
+ * @since 4.0.0
+ */
+ public function bootComponent($component): ComponentInterface
+ {
+ // Normalize the component name
+ $component = strtolower(str_replace('com_', '', $component));
+
+ // Path to to look for services
+ $path = JPATH_ADMINISTRATOR . '/components/com_' . $component;
+
+ return $this->loadExtension(ComponentInterface::class, $component, $path);
+ }
+
+ /**
+ * Boots the module with the given name.
+ *
+ * @param string $module The module to boot
+ * @param string $applicationName The application name
+ *
+ * @return ModuleInterface
+ *
+ * @since 4.0.0
+ */
+ public function bootModule($module, $applicationName): ModuleInterface
+ {
+ // Normalize the module name
+ $module = strtolower(str_replace('mod_', '', $module));
+
+ // Path to to look for services
+ $path = JPATH_SITE . '/modules/mod_' . $module;
+
+ if ($applicationName === 'administrator') {
+ $path = JPATH_ADMINISTRATOR . '/modules/mod_' . $module;
+ }
+
+ return $this->loadExtension(ModuleInterface::class, $module, $path);
+ }
+
+ /**
+ * Boots the plugin with the given name and type.
+ *
+ * @param string $plugin The plugin name
+ * @param string $type The type of the plugin
+ *
+ * @return PluginInterface
+ *
+ * @since 4.0.0
+ */
+ public function bootPlugin($plugin, $type): PluginInterface
+ {
+ // Normalize the module name
+ $plugin = strtolower(str_replace('plg_', '', $plugin));
+
+ // Path to to look for services
+ $path = JPATH_SITE . '/plugins/' . $type . '/' . $plugin;
+
+ return $this->loadExtension(PluginInterface::class, $plugin . ':' . $type, $path);
+ }
+
+ /**
+ * Loads the extension.
+ *
+ * @param string $type The extension type
+ * @param string $extensionName The extension name
+ * @param string $extensionPath The path of the extension
+ *
+ * @return ComponentInterface|ModuleInterface|PluginInterface
+ *
+ * @since 4.0.0
+ */
+ private function loadExtension($type, $extensionName, $extensionPath)
+ {
+ // Check if the extension is already loaded
+ if (!empty(ExtensionHelper::$extensions[$type][$extensionName])) {
+ return ExtensionHelper::$extensions[$type][$extensionName];
+ }
+
+ // The container to get the services from
+ $container = $this->getContainer()->createChild();
+
+ $container->get(DispatcherInterface::class)->dispatch(
+ 'onBeforeExtensionBoot',
+ AbstractEvent::create(
+ 'onBeforeExtensionBoot',
+ [
+ 'subject' => $this,
+ 'type' => $type,
+ 'extensionName' => $extensionName,
+ 'container' => $container
+ ]
+ )
+ );
+
+ // The path of the loader file
+ $path = $extensionPath . '/services/provider.php';
+
+ if (is_file($path)) {
+ // Load the file
+ $provider = require_once $path;
+
+ // Check if the extension supports the service provider interface
+ if ($provider instanceof ServiceProviderInterface) {
+ $provider->register($container);
+ }
+ }
+
+ // Fallback to legacy
+ if (!$container->has($type)) {
+ switch ($type) {
+ case ComponentInterface::class:
+ $container->set($type, new LegacyComponent('com_' . $extensionName));
+ break;
+ case ModuleInterface::class:
+ $container->set($type, new Module(new ModuleDispatcherFactory(''), new HelperFactory('')));
+ break;
+ case PluginInterface::class:
+ list($pluginName, $pluginType) = explode(':', $extensionName);
+ $container->set($type, $this->loadPluginFromFilesystem($pluginName, $pluginType));
+ }
+ }
+
+ $container->get(DispatcherInterface::class)->dispatch(
+ 'onAfterExtensionBoot',
+ AbstractEvent::create(
+ 'onAfterExtensionBoot',
+ [
+ 'subject' => $this,
+ 'type' => $type,
+ 'extensionName' => $extensionName,
+ 'container' => $container
+ ]
+ )
+ );
+
+ $extension = $container->get($type);
+
+ if ($extension instanceof BootableExtensionInterface) {
+ $extension->boot($container);
+ }
+
+ // Cache the extension
+ ExtensionHelper::$extensions[$type][$extensionName] = $extension;
+
+ return $extension;
+ }
+
+ /**
+ * Creates a CMS plugin from the filesystem.
+ *
+ * @param string $plugin The plugin
+ * @param string $type The type
+ *
+ * @return CMSPlugin
+ *
+ * @since 4.0.0
+ */
+ private function loadPluginFromFilesystem(string $plugin, string $type)
+ {
+ // The dispatcher
+ $dispatcher = $this->getContainer()->get(DispatcherInterface::class);
+
+ // Clear the names
+ $plugin = preg_replace('/[^A-Z0-9_\.-]/i', '', $plugin);
+ $type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
+
+ // The path of the plugin
+ $path = JPATH_PLUGINS . '/' . $type . '/' . $plugin . '/' . $plugin . '.php';
+
+ // Return an empty class when the file doesn't exist
+ if (!is_file($path)) {
+ return new DummyPlugin($dispatcher);
+ }
+
+ // Include the file of the plugin
+ require_once $path;
+
+ // Compile the classname
+ $className = 'Plg' . str_replace('-', '', $type) . $plugin;
+
+ if ($type === 'editors-xtd') {
+ // This type doesn't follow the convention
+ $className = 'PlgEditorsXtd' . $plugin;
+
+ if (!class_exists($className)) {
+ $className = 'PlgButton' . $plugin;
+ }
+ }
+
+ // Return an empty class when the class doesn't exist
+ if (!class_exists($className)) {
+ return new DummyPlugin($dispatcher);
+ }
+
+ // Instantiate the plugin
+ return new $className($dispatcher, (array) PluginHelper::getPlugin($type, $plugin));
+ }
+
+ /**
+ * Get the DI container.
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ * @throws ContainerNotFoundException May be thrown if the container has not been set.
+ */
+ abstract protected function getContainer();
}
diff --git a/libraries/src/Extension/LegacyComponent.php b/libraries/src/Extension/LegacyComponent.php
index 3b6c30d5fb081..a4fd026858c7e 100644
--- a/libraries/src/Extension/LegacyComponent.php
+++ b/libraries/src/Extension/LegacyComponent.php
@@ -1,4 +1,5 @@
component = str_replace('com_', '', $component);
- }
-
- /**
- * Returns the dispatcher for the given application.
- *
- * @param CMSApplicationInterface $application The application
- *
- * @return DispatcherInterface
- *
- * @since 4.0.0
- */
- public function getDispatcher(CMSApplicationInterface $application): DispatcherInterface
- {
- return new LegacyComponentDispatcher($application);
- }
-
- /**
- * Get the factory.
- *
- * @return MVCFactoryInterface
- *
- * @since 4.0.0
- * @throws \UnexpectedValueException May be thrown if the factory has not been set.
- */
- public function getMVCFactory(): MVCFactoryInterface
- {
- return new LegacyFactory;
- }
-
- /**
- * Returns the category service.
- *
- * @param array $options The options
- * @param string $section The section
- *
- * @return CategoryInterface
- *
- * @since 4.0.0
- * @throws SectionNotFoundException
- */
- public function getCategory(array $options = [], $section = ''): CategoryInterface
- {
- $classname = ucfirst($this->component) . ucfirst($section) . 'Categories';
-
- if (!class_exists($classname))
- {
- $path = JPATH_SITE . '/components/com_' . $this->component . '/helpers/category.php';
-
- if (!is_file($path))
- {
- throw new SectionNotFoundException;
- }
-
- include_once $path;
- }
-
- if (!class_exists($classname))
- {
- throw new SectionNotFoundException;
- }
-
- return new $classname($options);
- }
-
- /**
- * Adds Count Items for Category Manager.
- *
- * @param \stdClass[] $items The category objects
- * @param string $section The section
- *
- * @return void
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function countItems(array $items, string $section)
- {
- $helper = $this->loadHelper();
-
- if (!$helper || !\is_callable(array($helper, 'countItems')))
- {
- return;
- }
-
- $helper::countItems($items, $section);
- }
-
- /**
- * Adds Count Items for Tag Manager.
- *
- * @param \stdClass[] $items The content objects
- * @param string $extension The name of the active view.
- *
- * @return void
- *
- * @since 4.0.0
- * @throws \Exception
- */
- public function countTagItems(array $items, string $extension)
- {
- $helper = $this->loadHelper();
-
- if (!$helper || !\is_callable(array($helper, 'countTagItems')))
- {
- return;
- }
-
- $helper::countTagItems($items, $extension);
- }
-
- /**
- * Returns a valid section for articles. If it is not valid then null
- * is returned.
- *
- * @param string $section The section to get the mapping for
- * @param object $item The item
- *
- * @return string|null The new section
- *
- * @since 4.0.0
- */
- public function validateSection($section, $item = null)
- {
- $helper = $this->loadHelper();
-
- if (!$helper || !\is_callable(array($helper, 'validateSection')))
- {
- return $section;
- }
-
- return $helper::validateSection($section, $item);
- }
-
- /**
- * Returns valid contexts.
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getContexts(): array
- {
- $helper = $this->loadHelper();
-
- if (!$helper || !\is_callable(array($helper, 'getContexts')))
- {
- return [];
- }
-
- return $helper::getContexts();
- }
-
- /**
- * Returns the router.
- *
- * @param CMSApplicationInterface $application The application object
- * @param AbstractMenu $menu The menu object to work with
- *
- * @return RouterInterface
- *
- * @since 4.0.0
- */
- public function createRouter(CMSApplicationInterface $application, AbstractMenu $menu): RouterInterface
- {
- $compname = ucfirst($this->component);
- $class = $compname . 'Router';
-
- if (!class_exists($class))
- {
- // Use the component routing handler if it exists
- $path = JPATH_SITE . '/components/com_' . $this->component . '/router.php';
-
- // Use the custom routing handler if it exists
- if (is_file($path))
- {
- require_once $path;
- }
- }
-
- if (class_exists($class))
- {
- $reflection = new \ReflectionClass($class);
-
- if (\in_array('Joomla\\CMS\\Component\\Router\\RouterInterface', $reflection->getInterfaceNames()))
- {
- return new $class($application, $menu);
- }
- }
-
- return new RouterLegacy($compname);
- }
-
- /**
- * Returns the classname of the legacy helper class. If none is found it returns false.
- *
- * @return boolean|string
- *
- * @since 4.0.0
- */
- private function loadHelper()
- {
- $className = ucfirst($this->component) . 'Helper';
-
- if (class_exists($className))
- {
- return $className;
- }
-
- $file = Path::clean(JPATH_ADMINISTRATOR . '/components/com_' . $this->component . '/helpers/' . $this->component . '.php');
-
- if (!is_file($file))
- {
- return false;
- }
-
- \JLoader::register($className, $file);
-
- if (!class_exists($className))
- {
- return false;
- }
-
- return $className;
- }
+ use CategoryServiceTrait, TagServiceTrait {
+ CategoryServiceTrait::getTableNameForSection insteadof TagServiceTrait;
+ CategoryServiceTrait::getStateColumnForSection insteadof TagServiceTrait;
+ }
+
+ /**
+ * @var string
+ *
+ * @since 4.0.0
+ */
+ private $component;
+
+ /**
+ * LegacyComponentContainer constructor.
+ *
+ * @param string $component The component
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $component)
+ {
+ $this->component = str_replace('com_', '', $component);
+ }
+
+ /**
+ * Returns the dispatcher for the given application.
+ *
+ * @param CMSApplicationInterface $application The application
+ *
+ * @return DispatcherInterface
+ *
+ * @since 4.0.0
+ */
+ public function getDispatcher(CMSApplicationInterface $application): DispatcherInterface
+ {
+ return new LegacyComponentDispatcher($application);
+ }
+
+ /**
+ * Get the factory.
+ *
+ * @return MVCFactoryInterface
+ *
+ * @since 4.0.0
+ * @throws \UnexpectedValueException May be thrown if the factory has not been set.
+ */
+ public function getMVCFactory(): MVCFactoryInterface
+ {
+ return new LegacyFactory();
+ }
+
+ /**
+ * Returns the category service.
+ *
+ * @param array $options The options
+ * @param string $section The section
+ *
+ * @return CategoryInterface
+ *
+ * @since 4.0.0
+ * @throws SectionNotFoundException
+ */
+ public function getCategory(array $options = [], $section = ''): CategoryInterface
+ {
+ $classname = ucfirst($this->component) . ucfirst($section) . 'Categories';
+
+ if (!class_exists($classname)) {
+ $path = JPATH_SITE . '/components/com_' . $this->component . '/helpers/category.php';
+
+ if (!is_file($path)) {
+ throw new SectionNotFoundException();
+ }
+
+ include_once $path;
+ }
+
+ if (!class_exists($classname)) {
+ throw new SectionNotFoundException();
+ }
+
+ return new $classname($options);
+ }
+
+ /**
+ * Adds Count Items for Category Manager.
+ *
+ * @param \stdClass[] $items The category objects
+ * @param string $section The section
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function countItems(array $items, string $section)
+ {
+ $helper = $this->loadHelper();
+
+ if (!$helper || !\is_callable(array($helper, 'countItems'))) {
+ return;
+ }
+
+ $helper::countItems($items, $section);
+ }
+
+ /**
+ * Adds Count Items for Tag Manager.
+ *
+ * @param \stdClass[] $items The content objects
+ * @param string $extension The name of the active view.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ * @throws \Exception
+ */
+ public function countTagItems(array $items, string $extension)
+ {
+ $helper = $this->loadHelper();
+
+ if (!$helper || !\is_callable(array($helper, 'countTagItems'))) {
+ return;
+ }
+
+ $helper::countTagItems($items, $extension);
+ }
+
+ /**
+ * Returns a valid section for articles. If it is not valid then null
+ * is returned.
+ *
+ * @param string $section The section to get the mapping for
+ * @param object $item The item
+ *
+ * @return string|null The new section
+ *
+ * @since 4.0.0
+ */
+ public function validateSection($section, $item = null)
+ {
+ $helper = $this->loadHelper();
+
+ if (!$helper || !\is_callable(array($helper, 'validateSection'))) {
+ return $section;
+ }
+
+ return $helper::validateSection($section, $item);
+ }
+
+ /**
+ * Returns valid contexts.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getContexts(): array
+ {
+ $helper = $this->loadHelper();
+
+ if (!$helper || !\is_callable(array($helper, 'getContexts'))) {
+ return [];
+ }
+
+ return $helper::getContexts();
+ }
+
+ /**
+ * Returns the router.
+ *
+ * @param CMSApplicationInterface $application The application object
+ * @param AbstractMenu $menu The menu object to work with
+ *
+ * @return RouterInterface
+ *
+ * @since 4.0.0
+ */
+ public function createRouter(CMSApplicationInterface $application, AbstractMenu $menu): RouterInterface
+ {
+ $compname = ucfirst($this->component);
+ $class = $compname . 'Router';
+
+ if (!class_exists($class)) {
+ // Use the component routing handler if it exists
+ $path = JPATH_SITE . '/components/com_' . $this->component . '/router.php';
+
+ // Use the custom routing handler if it exists
+ if (is_file($path)) {
+ require_once $path;
+ }
+ }
+
+ if (class_exists($class)) {
+ $reflection = new \ReflectionClass($class);
+
+ if (\in_array('Joomla\\CMS\\Component\\Router\\RouterInterface', $reflection->getInterfaceNames())) {
+ return new $class($application, $menu);
+ }
+ }
+
+ return new RouterLegacy($compname);
+ }
+
+ /**
+ * Returns the classname of the legacy helper class. If none is found it returns false.
+ *
+ * @return boolean|string
+ *
+ * @since 4.0.0
+ */
+ private function loadHelper()
+ {
+ $className = ucfirst($this->component) . 'Helper';
+
+ if (class_exists($className)) {
+ return $className;
+ }
+
+ $file = Path::clean(JPATH_ADMINISTRATOR . '/components/com_' . $this->component . '/helpers/' . $this->component . '.php');
+
+ if (!is_file($file)) {
+ return false;
+ }
+
+ \JLoader::register($className, $file);
+
+ if (!class_exists($className)) {
+ return false;
+ }
+
+ return $className;
+ }
}
diff --git a/libraries/src/Extension/MVCComponent.php b/libraries/src/Extension/MVCComponent.php
index 019f982b8f200..e3f2325a38576 100644
--- a/libraries/src/Extension/MVCComponent.php
+++ b/libraries/src/Extension/MVCComponent.php
@@ -1,4 +1,5 @@
dispatcherFactory = $dispatcherFactory;
- $this->helperFactory = $helperFactory;
- }
+ /**
+ * Module constructor.
+ *
+ * @param ModuleDispatcherFactoryInterface $dispatcherFactory The dispatcher factory
+ * @param HelperFactoryInterface $helperFactory The helper factory
+ *
+ * @since 4.0.0
+ */
+ public function __construct(ModuleDispatcherFactoryInterface $dispatcherFactory, HelperFactoryInterface $helperFactory)
+ {
+ $this->dispatcherFactory = $dispatcherFactory;
+ $this->helperFactory = $helperFactory;
+ }
- /**
- * Returns the dispatcher for the given application, module and input.
- *
- * @param \stdClass $module The module
- * @param CMSApplicationInterface $application The application
- * @param Input $input The input object, defaults to the one in the application
- *
- * @return DispatcherInterface
- *
- * @since 4.0.0
- */
- public function getDispatcher(\stdClass $module, CMSApplicationInterface $application, Input $input = null): DispatcherInterface
- {
- $dispatcher = $this->dispatcherFactory->createDispatcher($module, $application, $input);
+ /**
+ * Returns the dispatcher for the given application, module and input.
+ *
+ * @param \stdClass $module The module
+ * @param CMSApplicationInterface $application The application
+ * @param Input $input The input object, defaults to the one in the application
+ *
+ * @return DispatcherInterface
+ *
+ * @since 4.0.0
+ */
+ public function getDispatcher(\stdClass $module, CMSApplicationInterface $application, Input $input = null): DispatcherInterface
+ {
+ $dispatcher = $this->dispatcherFactory->createDispatcher($module, $application, $input);
- if ($dispatcher instanceof HelperFactoryAwareInterface)
- {
- $dispatcher->setHelperFactory($this->helperFactory);
- }
+ if ($dispatcher instanceof HelperFactoryAwareInterface) {
+ $dispatcher->setHelperFactory($this->helperFactory);
+ }
- return $dispatcher;
- }
+ return $dispatcher;
+ }
- /**
- * Returns a helper instance for the given name.
- *
- * @param string $name The name
- * @param array $config The config
- *
- * @return \stdClass
- *
- * @since 4.0.0
- */
- public function getHelper(string $name, array $config = [])
- {
- return $this->helperFactory->getHelper($name, $config);
- }
+ /**
+ * Returns a helper instance for the given name.
+ *
+ * @param string $name The name
+ * @param array $config The config
+ *
+ * @return \stdClass
+ *
+ * @since 4.0.0
+ */
+ public function getHelper(string $name, array $config = [])
+ {
+ return $this->helperFactory->getHelper($name, $config);
+ }
}
diff --git a/libraries/src/Extension/ModuleInterface.php b/libraries/src/Extension/ModuleInterface.php
index c3937621f8349..be43d8921509a 100644
--- a/libraries/src/Extension/ModuleInterface.php
+++ b/libraries/src/Extension/ModuleInterface.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * The namespace must be like:
+ * Joomla\Component\Content
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct($namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- CategoryFactoryInterface::class,
- function (Container $container)
- {
- $factory = new \Joomla\CMS\Categories\CategoryFactory($this->namespace);
- $factory->setDatabase($container->get(DatabaseInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ CategoryFactoryInterface::class,
+ function (Container $container) {
+ $factory = new \Joomla\CMS\Categories\CategoryFactory($this->namespace);
+ $factory->setDatabase($container->get(DatabaseInterface::class));
- return $factory;
- }
- );
- }
+ return $factory;
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/ComponentDispatcherFactory.php b/libraries/src/Extension/Service/Provider/ComponentDispatcherFactory.php
index 41d44a8933de5..b6d4ebe0e4d52 100644
--- a/libraries/src/Extension/Service/Provider/ComponentDispatcherFactory.php
+++ b/libraries/src/Extension/Service/Provider/ComponentDispatcherFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * ComponentDispatcherFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- ComponentDispatcherFactoryInterface::class,
- function (Container $container)
- {
- return new \Joomla\CMS\Dispatcher\ComponentDispatcherFactory($this->namespace, $container->get(MVCFactoryInterface::class));
- }
- );
- }
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ ComponentDispatcherFactoryInterface::class,
+ function (Container $container) {
+ return new \Joomla\CMS\Dispatcher\ComponentDispatcherFactory($this->namespace, $container->get(MVCFactoryInterface::class));
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/HelperFactory.php b/libraries/src/Extension/Service/Provider/HelperFactory.php
index bb2c4973e98de..9a531b04defce 100644
--- a/libraries/src/Extension/Service/Provider/HelperFactory.php
+++ b/libraries/src/Extension/Service/Provider/HelperFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * HelperFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- HelperFactoryInterface::class,
- function (Container $container)
- {
- $factory = new \Joomla\CMS\Helper\HelperFactory($this->namespace);
- $factory->setDatabase($container->get(DatabaseInterface::class));
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ HelperFactoryInterface::class,
+ function (Container $container) {
+ $factory = new \Joomla\CMS\Helper\HelperFactory($this->namespace);
+ $factory->setDatabase($container->get(DatabaseInterface::class));
- return $factory;
- }
- );
- }
+ return $factory;
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/MVCFactory.php b/libraries/src/Extension/Service/Provider/MVCFactory.php
index 9d1baa83bab16..fb70c222d24b7 100644
--- a/libraries/src/Extension/Service/Provider/MVCFactory.php
+++ b/libraries/src/Extension/Service/Provider/MVCFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * MVCFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- MVCFactoryInterface::class,
- function (Container $container)
- {
- if (\Joomla\CMS\Factory::getApplication()->isClient('api'))
- {
- $factory = new ApiMVCFactory($this->namespace);
- }
- else
- {
- $factory = new \Joomla\CMS\MVC\Factory\MVCFactory($this->namespace);
- }
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ MVCFactoryInterface::class,
+ function (Container $container) {
+ if (\Joomla\CMS\Factory::getApplication()->isClient('api')) {
+ $factory = new ApiMVCFactory($this->namespace);
+ } else {
+ $factory = new \Joomla\CMS\MVC\Factory\MVCFactory($this->namespace);
+ }
- $factory->setFormFactory($container->get(FormFactoryInterface::class));
- $factory->setDispatcher($container->get(DispatcherInterface::class));
- $factory->setDatabase($container->get(DatabaseInterface::class));
- $factory->setSiteRouter($container->get(SiteRouter::class));
- $factory->setCacheControllerFactory($container->get(CacheControllerFactoryInterface::class));
+ $factory->setFormFactory($container->get(FormFactoryInterface::class));
+ $factory->setDispatcher($container->get(DispatcherInterface::class));
+ $factory->setDatabase($container->get(DatabaseInterface::class));
+ $factory->setSiteRouter($container->get(SiteRouter::class));
+ $factory->setCacheControllerFactory($container->get(CacheControllerFactoryInterface::class));
- return $factory;
- }
- );
- }
+ return $factory;
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/Module.php b/libraries/src/Extension/Service/Provider/Module.php
index e883eeb2ce4d9..be691bea80b3d 100644
--- a/libraries/src/Extension/Service/Provider/Module.php
+++ b/libraries/src/Extension/Service/Provider/Module.php
@@ -1,4 +1,5 @@
set(
- ModuleInterface::class,
- function (Container $container)
- {
- return new \Joomla\CMS\Extension\Module(
- $container->get(ModuleDispatcherFactoryInterface::class),
- $container->get(HelperFactoryInterface::class)
- );
- }
- );
- }
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ ModuleInterface::class,
+ function (Container $container) {
+ return new \Joomla\CMS\Extension\Module(
+ $container->get(ModuleDispatcherFactoryInterface::class),
+ $container->get(HelperFactoryInterface::class)
+ );
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/ModuleDispatcherFactory.php b/libraries/src/Extension/Service/Provider/ModuleDispatcherFactory.php
index 85cfb68320c4a..8a2b7af0dadc6 100644
--- a/libraries/src/Extension/Service/Provider/ModuleDispatcherFactory.php
+++ b/libraries/src/Extension/Service/Provider/ModuleDispatcherFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * ComponentDispatcherFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- ModuleDispatcherFactoryInterface::class,
- function (Container $container)
- {
- return new \Joomla\CMS\Dispatcher\ModuleDispatcherFactory($this->namespace);
- }
- );
- }
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ ModuleDispatcherFactoryInterface::class,
+ function (Container $container) {
+ return new \Joomla\CMS\Dispatcher\ModuleDispatcherFactory($this->namespace);
+ }
+ );
+ }
}
diff --git a/libraries/src/Extension/Service/Provider/RouterFactory.php b/libraries/src/Extension/Service/Provider/RouterFactory.php
index 90d3924e611ed..1aa7c61b206e8 100644
--- a/libraries/src/Extension/Service/Provider/RouterFactory.php
+++ b/libraries/src/Extension/Service/Provider/RouterFactory.php
@@ -1,4 +1,5 @@
namespace = $namespace;
- }
+ /**
+ * DispatcherFactory constructor.
+ *
+ * @param string $namespace The namespace
+ *
+ * @since 4.0.0
+ */
+ public function __construct(string $namespace)
+ {
+ $this->namespace = $namespace;
+ }
- /**
- * Registers the service provider with a DI container.
- *
- * @param Container $container The DI container.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function register(Container $container)
- {
- $container->set(
- RouterFactoryInterface::class,
- function (Container $container)
- {
- $categoryFactory = null;
+ /**
+ * Registers the service provider with a DI container.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function register(Container $container)
+ {
+ $container->set(
+ RouterFactoryInterface::class,
+ function (Container $container) {
+ $categoryFactory = null;
- if ($container->has(CategoryFactoryInterface::class))
- {
- $categoryFactory = $container->get(CategoryFactoryInterface::class);
- }
+ if ($container->has(CategoryFactoryInterface::class)) {
+ $categoryFactory = $container->get(CategoryFactoryInterface::class);
+ }
- return new \Joomla\CMS\Component\Router\RouterFactory(
- $this->namespace,
- $categoryFactory,
- $container->get(DatabaseInterface::class)
- );
- }
- );
- }
+ return new \Joomla\CMS\Component\Router\RouterFactory(
+ $this->namespace,
+ $categoryFactory,
+ $container->get(DatabaseInterface::class)
+ );
+ }
+ );
+ }
}
diff --git a/libraries/src/Factory.php b/libraries/src/Factory.php
index b43793c48a306..afe09f5b593f9 100644
--- a/libraries/src/Factory.php
+++ b/libraries/src/Factory.php
@@ -1,4 +1,5 @@
getConfig() !== null)
- {
- return self::$application->getConfig();
- }
-
- if (!self::$config)
- {
- if ($file === null)
- {
- $file = JPATH_CONFIGURATION . '/configuration.php';
- }
-
- self::$config = self::createConfig($file, $type, $namespace);
- }
-
- return self::$config;
- }
-
- /**
- * Get a container object
- *
- * Returns the global service container object, only creating it if it doesn't already exist.
- *
- * This method is only suggested for use in code whose responsibility is to create new services
- * and needs to be able to resolve the dependencies, and should therefore only be used when the
- * container is not accessible by other means. Valid uses of this method include:
- *
- * - A static `getInstance()` method calling a factory service from the container,
- * see `Joomla\CMS\Toolbar\Toolbar::getInstance()` as an example
- * - An application front controller loading and executing the Joomla application class,
- * see the `cli/joomla.php` file as an example
- * - Retrieving optional constructor dependencies when not injected into a class during a transitional
- * period to retain backward compatibility, in this case a deprecation notice should also be emitted to
- * notify developers of changes needed in their code
- *
- * This method is not suggested for use as a one-for-one replacement of static calls, such as
- * replacing calls to `Factory::getDbo()` with calls to `Factory::getContainer()->get('db')`, code
- * should be refactored to support dependency injection instead of making this change.
- *
- * @return Container
- *
- * @since 4.0.0
- */
- public static function getContainer(): Container
- {
- if (!self::$container)
- {
- self::$container = self::createContainer();
- }
-
- return self::$container;
- }
-
- /**
- * Get a session object.
- *
- * Returns the global {@link Session} object, only creating it if it doesn't already exist.
- *
- * @param array $options An array containing session options
- *
- * @return Session object
- *
- * @see Session
- * @since 1.7.0
- * @deprecated 5.0 Load the session service from the dependency injection container or via $app->getSession()
- */
- public static function getSession(array $options = array())
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the session from the dependency injection container or via %2$s::getApplication()->getSession().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- return self::getApplication()->getSession();
- }
-
- /**
- * Get a language object.
- *
- * Returns the global {@link Language} object, only creating it if it doesn't already exist.
- *
- * @return Language object
- *
- * @see Language
- * @since 1.7.0
- * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
- */
- public static function getLanguage()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- if (!self::$language)
- {
- self::$language = self::createLanguage();
- }
-
- return self::$language;
- }
-
- /**
- * Get a document object.
- *
- * Returns the global {@link \Joomla\CMS\Document\Document} object, only creating it if it doesn't already exist.
- *
- * @return Document object
- *
- * @see Document
- * @since 1.7.0
- * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
- */
- public static function getDocument()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- if (!self::$document)
- {
- self::$document = self::createDocument();
- }
-
- return self::$document;
- }
-
- /**
- * Get a user object.
- *
- * Returns the global {@link User} object, only creating it if it doesn't already exist.
- *
- * @param integer $id The user to load - Can be an integer or string - If string, it is converted to ID automatically.
- *
- * @return User object
- *
- * @see User
- * @since 1.7.0
- * @deprecated 5.0 Load the user service from the dependency injection container or via $app->getIdentity()
- */
- public static function getUser($id = null)
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the user from the dependency injection container or via %2$s::getApplication()->getIdentity().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- $instance = self::getApplication()->getSession()->get('user');
-
- if (\is_null($id))
- {
- if (!($instance instanceof User))
- {
- $instance = User::getInstance();
- }
- }
- // Check if we have a string as the id or if the numeric id is the current instance
- elseif (!($instance instanceof User) || \is_string($id) || $instance->id !== $id)
- {
- $instance = User::getInstance($id);
- }
-
- return $instance;
- }
-
- /**
- * Get a cache object
- *
- * Returns the global {@link CacheController} object
- *
- * @param string $group The cache group name
- * @param string $handler The handler to use
- * @param string $storage The storage method
- *
- * @return \Joomla\CMS\Cache\CacheController object
- *
- * @see Cache
- * @since 1.7.0
- * @deprecated 5.0 Use the cache controller factory instead
- */
- public static function getCache($group = '', $handler = 'callback', $storage = null)
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. The cache controller should be fetched from the factory.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- $hash = md5($group . $handler . $storage);
-
- if (isset(self::$cache[$hash]))
- {
- return self::$cache[$hash];
- }
-
- $handler = ($handler === 'function') ? 'callback' : $handler;
-
- $options = array('defaultgroup' => $group);
-
- if (isset($storage))
- {
- $options['storage'] = $storage;
- }
-
- $cache = self::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($handler, $options);
-
- self::$cache[$hash] = $cache;
-
- return self::$cache[$hash];
- }
-
- /**
- * Get a database object.
- *
- * Returns the global {@link DatabaseDriver} object, only creating it if it doesn't already exist.
- *
- * @return DatabaseDriver
- *
- * @see DatabaseDriver
- * @since 1.7.0
- * @deprecated 5.0 Load the database service from the dependency injection container
- */
- public static function getDbo()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the database from the dependency injection container.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- if (!self::$database)
- {
- if (self::getContainer()->has('DatabaseDriver'))
- {
- self::$database = self::getContainer()->get('DatabaseDriver');
- }
- else
- {
- self::$database = self::createDbo();
- }
- }
-
- return self::$database;
- }
-
- /**
- * Get a mailer object.
- *
- * Returns the global {@link Mail} object, only creating it if it doesn't already exist.
- *
- * @return Mail object
- *
- * @see Mail
- * @since 1.7.0
- */
- public static function getMailer()
- {
- if (!self::$mailer)
- {
- self::$mailer = self::createMailer();
- }
-
- $copy = clone self::$mailer;
-
- return $copy;
- }
-
- /**
- * Return the {@link Date} object
- *
- * @param mixed $time The initial time for the Date object
- * @param mixed $tzOffset The timezone offset.
- *
- * @return Date object
- *
- * @see Date
- * @since 1.7.0
- */
- public static function getDate($time = 'now', $tzOffset = null)
- {
- static $classname;
- static $mainLocale;
-
- $language = self::getLanguage();
- $locale = $language->getTag();
-
- if (!isset($classname) || $locale != $mainLocale)
- {
- // Store the locale for future reference
- $mainLocale = $locale;
-
- if ($mainLocale !== false)
- {
- $classname = str_replace('-', '_', $mainLocale) . 'Date';
-
- if (!class_exists($classname))
- {
- // The class does not exist, default to Date
- $classname = 'Joomla\\CMS\\Date\\Date';
- }
- }
- else
- {
- // No tag, so default to Date
- $classname = 'Joomla\\CMS\\Date\\Date';
- }
- }
-
- $key = $time . '-' . ($tzOffset instanceof \DateTimeZone ? $tzOffset->getName() : (string) $tzOffset);
-
- if (!isset(self::$dates[$classname][$key]))
- {
- self::$dates[$classname][$key] = new $classname($time, $tzOffset);
- }
-
- $date = clone self::$dates[$classname][$key];
-
- return $date;
- }
-
- /**
- * Create a configuration object
- *
- * @param string $file The path to the configuration file.
- * @param string $type The type of the configuration file.
- * @param string $namespace The namespace of the configuration file.
- *
- * @return Registry
- *
- * @see Registry
- * @since 1.7.0
- * @deprecated 5.0 Use the configuration object within the application.
- */
- protected static function createConfig($file, $type = 'PHP', $namespace = '')
- {
- @trigger_error(
- sprintf(
- '%s() is deprecated. The configuration object should be read from the application.',
- __METHOD__
- ),
- E_USER_DEPRECATED
- );
-
- if (is_file($file))
- {
- include_once $file;
- }
-
- // Create the registry with a default namespace of config
- $registry = new Registry;
-
- // Sanitize the namespace.
- $namespace = ucfirst((string) preg_replace('/[^A-Z_]/i', '', $namespace));
-
- // Build the config name.
- $name = 'JConfig' . $namespace;
-
- // Handle the PHP configuration type.
- if ($type === 'PHP' && class_exists($name))
- {
- // Create the JConfig object
- $config = new $name;
-
- // Load the configuration values into the registry
- $registry->loadObject($config);
- }
-
- return $registry;
- }
-
- /**
- * Create a container object
- *
- * @return Container
- *
- * @since 4.0.0
- */
- protected static function createContainer(): Container
- {
- $container = (new Container)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Application)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Authentication)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\CacheController)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Config)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Console)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Database)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Dispatcher)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Document)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Form)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Logger)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Language)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Menu)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Pathway)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\HTMLRegistry)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Session)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Toolbar)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\WebAssetRegistry)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Router)
- ->registerServiceProvider(new \Joomla\CMS\Service\Provider\User);
-
- return $container;
- }
-
- /**
- * Create a database object
- *
- * @return DatabaseDriver
- *
- * @see DatabaseDriver
- * @since 1.7.0
- * @deprecated 5.0 Use the database service in the DI container
- */
- protected static function createDbo()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated, register a service provider to create a %2$s instance instead.',
- __METHOD__,
- DatabaseInterface::class
- ),
- E_USER_DEPRECATED
- );
-
- $conf = self::getConfig();
-
- $host = $conf->get('host');
- $user = $conf->get('user');
- $password = $conf->get('password');
- $database = $conf->get('db');
- $prefix = $conf->get('dbprefix');
- $driver = $conf->get('dbtype');
-
- $options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database, 'prefix' => $prefix);
-
- if ((int) $conf->get('dbencryption') !== 0)
- {
- $options['ssl'] = [
- 'enable' => true,
- 'verify_server_cert' => (bool) $conf->get('dbsslverifyservercert'),
- ];
-
- foreach (['cipher', 'ca', 'key', 'cert'] as $value)
- {
- $confVal = trim($conf->get('dbssl' . $value, ''));
-
- if ($confVal !== '')
- {
- $options['ssl'][$value] = $confVal;
- }
- }
- }
-
- try
- {
- $db = DatabaseDriver::getInstance($options);
- }
- catch (\RuntimeException $e)
- {
- if (!headers_sent())
- {
- header('HTTP/1.1 500 Internal Server Error');
- }
-
- jexit('Database Error: ' . $e->getMessage());
- }
-
- return $db;
- }
-
- /**
- * Create a mailer object
- *
- * @return Mail object
- *
- * @see Mail
- * @since 1.7.0
- */
- protected static function createMailer()
- {
- $conf = self::getConfig();
-
- $smtpauth = ($conf->get('smtpauth') == 0) ? null : 1;
- $smtpuser = $conf->get('smtpuser');
- $smtppass = $conf->get('smtppass');
- $smtphost = $conf->get('smtphost');
- $smtpsecure = $conf->get('smtpsecure');
- $smtpport = $conf->get('smtpport');
- $mailfrom = $conf->get('mailfrom');
- $fromname = $conf->get('fromname');
- $mailer = $conf->get('mailer');
-
- // Create a Mail object
- $mail = Mail::getInstance();
-
- // Clean the email address
- $mailfrom = MailHelper::cleanLine($mailfrom);
-
- // Set default sender without Reply-to if the mailfrom is a valid address
- if (MailHelper::isEmailAddress($mailfrom))
- {
- // Wrap in try/catch to catch phpmailerExceptions if it is throwing them
- try
- {
- // Check for a false return value if exception throwing is disabled
- if ($mail->setFrom($mailfrom, MailHelper::cleanLine($fromname), false) === false)
- {
- Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
- }
- }
- catch (phpmailerException $e)
- {
- Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
- }
- }
-
- // Default mailer is to use PHP's mail function
- switch ($mailer)
- {
- case 'smtp':
- $mail->useSmtp($smtpauth, $smtphost, $smtpuser, $smtppass, $smtpsecure, $smtpport);
- break;
-
- case 'sendmail':
- $mail->isSendmail();
- break;
-
- default:
- $mail->isMail();
- break;
- }
-
- return $mail;
- }
-
- /**
- * Create a language object
- *
- * @return Language object
- *
- * @see Language
- * @since 1.7.0
- * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
- */
- protected static function createLanguage()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- $conf = self::getConfig();
- $locale = $conf->get('language');
- $debug = $conf->get('debug_lang');
- $lang = self::getContainer()->get(LanguageFactoryInterface::class)->createLanguage($locale, $debug);
-
- return $lang;
- }
-
- /**
- * Create a document object
- *
- * @return Document object
- *
- * @see Document
- * @since 1.7.0
- * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
- */
- protected static function createDocument()
- {
- @trigger_error(
- sprintf(
- '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
- __METHOD__,
- __CLASS__
- ),
- E_USER_DEPRECATED
- );
-
- $lang = self::getLanguage();
-
- $input = self::getApplication()->input;
- $type = $input->get('format', 'html', 'cmd');
-
- $version = new Version;
-
- $attributes = array(
- 'charset' => 'utf-8',
- 'lineend' => 'unix',
- 'tab' => "\t",
- 'language' => $lang->getTag(),
- 'direction' => $lang->isRtl() ? 'rtl' : 'ltr',
- 'mediaversion' => $version->getMediaVersion(),
- );
-
- return self::getContainer()->get(FactoryInterface::class)->createDocument($type, $attributes);
- }
-
- /**
- * Creates a new stream object with appropriate prefix
- *
- * @param boolean $usePrefix Prefix the connections for writing
- * @param boolean $useNetwork Use network if available for writing; use false to disable (e.g. FTP, SCP)
- * @param string $userAgentSuffix String to append to user agent
- * @param boolean $maskUserAgent User agent masking (prefix Mozilla)
- *
- * @return Stream
- *
- * @see Stream
- * @since 1.7.0
- */
- public static function getStream($usePrefix = true, $useNetwork = true, $userAgentSuffix = 'Joomla', $maskUserAgent = false)
- {
- // Setup the context; Joomla! UA and overwrite
- $context = array();
- $version = new Version;
-
- // Set the UA for HTTP and overwrite for FTP
- $context['http']['user_agent'] = $version->getUserAgent($userAgentSuffix, $maskUserAgent);
- $context['ftp']['overwrite'] = true;
-
- if ($usePrefix)
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
- $SCPOptions = ClientHelper::getCredentials('scp');
-
- if ($FTPOptions['enabled'] == 1 && $useNetwork)
- {
- $prefix = 'ftp://' . $FTPOptions['user'] . ':' . $FTPOptions['pass'] . '@' . $FTPOptions['host'];
- $prefix .= $FTPOptions['port'] ? ':' . $FTPOptions['port'] : '';
- $prefix .= $FTPOptions['root'];
- }
- elseif ($SCPOptions['enabled'] == 1 && $useNetwork)
- {
- $prefix = 'ssh2.sftp://' . $SCPOptions['user'] . ':' . $SCPOptions['pass'] . '@' . $SCPOptions['host'];
- $prefix .= $SCPOptions['port'] ? ':' . $SCPOptions['port'] : '';
- $prefix .= $SCPOptions['root'];
- }
- else
- {
- $prefix = JPATH_ROOT . '/';
- }
-
- $retval = new Stream($prefix, JPATH_ROOT, $context);
- }
- else
- {
- $retval = new Stream('', '', $context);
- }
-
- return $retval;
- }
+ /**
+ * Global application object
+ *
+ * @var CMSApplicationInterface
+ * @since 1.7.0
+ */
+ public static $application = null;
+
+ /**
+ * Global cache object
+ *
+ * @var Cache
+ * @since 1.7.0
+ */
+ public static $cache = null;
+
+ /**
+ * Global configuration object
+ *
+ * @var \JConfig
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application
+ */
+ public static $config = null;
+
+ /**
+ * Global container object
+ *
+ * @var Container
+ * @since 4.0.0
+ */
+ public static $container = null;
+
+ /**
+ * Container for Date instances
+ *
+ * @var array
+ * @since 1.7.3
+ */
+ public static $dates = array();
+
+ /**
+ * Global session object
+ *
+ * @var Session
+ * @since 1.7.0
+ * @deprecated 5.0 Use the session service in the DI container
+ */
+ public static $session = null;
+
+ /**
+ * Global language object
+ *
+ * @var Language
+ * @since 1.7.0
+ * @deprecated 5.0 Use the language service in the DI container
+ */
+ public static $language = null;
+
+ /**
+ * Global document object
+ *
+ * @var Document
+ * @since 1.7.0
+ * @deprecated 5.0 Use the document service in the DI container
+ */
+ public static $document = null;
+
+ /**
+ * Global database object
+ *
+ * @var DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Use the database service in the DI container
+ */
+ public static $database = null;
+
+ /**
+ * Global mailer object
+ *
+ * @var Mail
+ * @since 1.7.0
+ */
+ public static $mailer = null;
+
+ /**
+ * Get the global application object. When the global application doesn't exist, an exception is thrown.
+ *
+ * @return CMSApplicationInterface object
+ *
+ * @since 1.7.0
+ * @throws \Exception
+ */
+ public static function getApplication()
+ {
+ if (!self::$application) {
+ throw new \Exception('Failed to start application', 500);
+ }
+
+ return self::$application;
+ }
+
+ /**
+ * Get a configuration object
+ *
+ * Returns the global {@link \JConfig} object, only creating it if it doesn't already exist.
+ *
+ * @param string $file The path to the configuration file
+ * @param string $type The type of the configuration file
+ * @param string $namespace The namespace of the configuration file
+ *
+ * @return Registry
+ *
+ * @see Registry
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application.
+ */
+ public static function getConfig($file = null, $type = 'PHP', $namespace = '')
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The configuration object should be read from the application.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ /**
+ * If there is an application object, fetch the configuration from there.
+ * Check it's not null because LanguagesModel can make it null and if it's null
+ * we would want to re-init it from configuration.php.
+ */
+ if (self::$application && self::$application->getConfig() !== null) {
+ return self::$application->getConfig();
+ }
+
+ if (!self::$config) {
+ if ($file === null) {
+ $file = JPATH_CONFIGURATION . '/configuration.php';
+ }
+
+ self::$config = self::createConfig($file, $type, $namespace);
+ }
+
+ return self::$config;
+ }
+
+ /**
+ * Get a container object
+ *
+ * Returns the global service container object, only creating it if it doesn't already exist.
+ *
+ * This method is only suggested for use in code whose responsibility is to create new services
+ * and needs to be able to resolve the dependencies, and should therefore only be used when the
+ * container is not accessible by other means. Valid uses of this method include:
+ *
+ * - A static `getInstance()` method calling a factory service from the container,
+ * see `Joomla\CMS\Toolbar\Toolbar::getInstance()` as an example
+ * - An application front controller loading and executing the Joomla application class,
+ * see the `cli/joomla.php` file as an example
+ * - Retrieving optional constructor dependencies when not injected into a class during a transitional
+ * period to retain backward compatibility, in this case a deprecation notice should also be emitted to
+ * notify developers of changes needed in their code
+ *
+ * This method is not suggested for use as a one-for-one replacement of static calls, such as
+ * replacing calls to `Factory::getDbo()` with calls to `Factory::getContainer()->get('db')`, code
+ * should be refactored to support dependency injection instead of making this change.
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ public static function getContainer(): Container
+ {
+ if (!self::$container) {
+ self::$container = self::createContainer();
+ }
+
+ return self::$container;
+ }
+
+ /**
+ * Get a session object.
+ *
+ * Returns the global {@link Session} object, only creating it if it doesn't already exist.
+ *
+ * @param array $options An array containing session options
+ *
+ * @return Session object
+ *
+ * @see Session
+ * @since 1.7.0
+ * @deprecated 5.0 Load the session service from the dependency injection container or via $app->getSession()
+ */
+ public static function getSession(array $options = array())
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the session from the dependency injection container or via %2$s::getApplication()->getSession().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ return self::getApplication()->getSession();
+ }
+
+ /**
+ * Get a language object.
+ *
+ * Returns the global {@link Language} object, only creating it if it doesn't already exist.
+ *
+ * @return Language object
+ *
+ * @see Language
+ * @since 1.7.0
+ * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
+ */
+ public static function getLanguage()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$language) {
+ self::$language = self::createLanguage();
+ }
+
+ return self::$language;
+ }
+
+ /**
+ * Get a document object.
+ *
+ * Returns the global {@link \Joomla\CMS\Document\Document} object, only creating it if it doesn't already exist.
+ *
+ * @return Document object
+ *
+ * @see Document
+ * @since 1.7.0
+ * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
+ */
+ public static function getDocument()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$document) {
+ self::$document = self::createDocument();
+ }
+
+ return self::$document;
+ }
+
+ /**
+ * Get a user object.
+ *
+ * Returns the global {@link User} object, only creating it if it doesn't already exist.
+ *
+ * @param integer $id The user to load - Can be an integer or string - If string, it is converted to ID automatically.
+ *
+ * @return User object
+ *
+ * @see User
+ * @since 1.7.0
+ * @deprecated 5.0 Load the user service from the dependency injection container or via $app->getIdentity()
+ */
+ public static function getUser($id = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the user from the dependency injection container or via %2$s::getApplication()->getIdentity().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $instance = self::getApplication()->getSession()->get('user');
+
+ if (\is_null($id)) {
+ if (!($instance instanceof User)) {
+ $instance = User::getInstance();
+ }
+ } elseif (!($instance instanceof User) || \is_string($id) || $instance->id !== $id) {
+ // Check if we have a string as the id or if the numeric id is the current instance
+ $instance = User::getInstance($id);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Get a cache object
+ *
+ * Returns the global {@link CacheController} object
+ *
+ * @param string $group The cache group name
+ * @param string $handler The handler to use
+ * @param string $storage The storage method
+ *
+ * @return \Joomla\CMS\Cache\CacheController object
+ *
+ * @see Cache
+ * @since 1.7.0
+ * @deprecated 5.0 Use the cache controller factory instead
+ */
+ public static function getCache($group = '', $handler = 'callback', $storage = null)
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The cache controller should be fetched from the factory.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $hash = md5($group . $handler . $storage);
+
+ if (isset(self::$cache[$hash])) {
+ return self::$cache[$hash];
+ }
+
+ $handler = ($handler === 'function') ? 'callback' : $handler;
+
+ $options = array('defaultgroup' => $group);
+
+ if (isset($storage)) {
+ $options['storage'] = $storage;
+ }
+
+ $cache = self::getContainer()->get(CacheControllerFactoryInterface::class)->createCacheController($handler, $options);
+
+ self::$cache[$hash] = $cache;
+
+ return self::$cache[$hash];
+ }
+
+ /**
+ * Get a database object.
+ *
+ * Returns the global {@link DatabaseDriver} object, only creating it if it doesn't already exist.
+ *
+ * @return DatabaseDriver
+ *
+ * @see DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Load the database service from the dependency injection container
+ */
+ public static function getDbo()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the database from the dependency injection container.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (!self::$database) {
+ if (self::getContainer()->has('DatabaseDriver')) {
+ self::$database = self::getContainer()->get('DatabaseDriver');
+ } else {
+ self::$database = self::createDbo();
+ }
+ }
+
+ return self::$database;
+ }
+
+ /**
+ * Get a mailer object.
+ *
+ * Returns the global {@link Mail} object, only creating it if it doesn't already exist.
+ *
+ * @return Mail object
+ *
+ * @see Mail
+ * @since 1.7.0
+ */
+ public static function getMailer()
+ {
+ if (!self::$mailer) {
+ self::$mailer = self::createMailer();
+ }
+
+ $copy = clone self::$mailer;
+
+ return $copy;
+ }
+
+ /**
+ * Return the {@link Date} object
+ *
+ * @param mixed $time The initial time for the Date object
+ * @param mixed $tzOffset The timezone offset.
+ *
+ * @return Date object
+ *
+ * @see Date
+ * @since 1.7.0
+ */
+ public static function getDate($time = 'now', $tzOffset = null)
+ {
+ static $classname;
+ static $mainLocale;
+
+ $language = self::getLanguage();
+ $locale = $language->getTag();
+
+ if (!isset($classname) || $locale != $mainLocale) {
+ // Store the locale for future reference
+ $mainLocale = $locale;
+
+ if ($mainLocale !== false) {
+ $classname = str_replace('-', '_', $mainLocale) . 'Date';
+
+ if (!class_exists($classname)) {
+ // The class does not exist, default to Date
+ $classname = 'Joomla\\CMS\\Date\\Date';
+ }
+ } else {
+ // No tag, so default to Date
+ $classname = 'Joomla\\CMS\\Date\\Date';
+ }
+ }
+
+ $key = $time . '-' . ($tzOffset instanceof \DateTimeZone ? $tzOffset->getName() : (string) $tzOffset);
+
+ if (!isset(self::$dates[$classname][$key])) {
+ self::$dates[$classname][$key] = new $classname($time, $tzOffset);
+ }
+
+ $date = clone self::$dates[$classname][$key];
+
+ return $date;
+ }
+
+ /**
+ * Create a configuration object
+ *
+ * @param string $file The path to the configuration file.
+ * @param string $type The type of the configuration file.
+ * @param string $namespace The namespace of the configuration file.
+ *
+ * @return Registry
+ *
+ * @see Registry
+ * @since 1.7.0
+ * @deprecated 5.0 Use the configuration object within the application.
+ */
+ protected static function createConfig($file, $type = 'PHP', $namespace = '')
+ {
+ @trigger_error(
+ sprintf(
+ '%s() is deprecated. The configuration object should be read from the application.',
+ __METHOD__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ if (is_file($file)) {
+ include_once $file;
+ }
+
+ // Create the registry with a default namespace of config
+ $registry = new Registry();
+
+ // Sanitize the namespace.
+ $namespace = ucfirst((string) preg_replace('/[^A-Z_]/i', '', $namespace));
+
+ // Build the config name.
+ $name = 'JConfig' . $namespace;
+
+ // Handle the PHP configuration type.
+ if ($type === 'PHP' && class_exists($name)) {
+ // Create the JConfig object
+ $config = new $name();
+
+ // Load the configuration values into the registry
+ $registry->loadObject($config);
+ }
+
+ return $registry;
+ }
+
+ /**
+ * Create a container object
+ *
+ * @return Container
+ *
+ * @since 4.0.0
+ */
+ protected static function createContainer(): Container
+ {
+ $container = (new Container())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Application())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Authentication())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\CacheController())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Config())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Console())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Database())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Dispatcher())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Document())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Form())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Logger())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Language())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Menu())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Pathway())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\HTMLRegistry())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Session())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Toolbar())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\WebAssetRegistry())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\Router())
+ ->registerServiceProvider(new \Joomla\CMS\Service\Provider\User());
+
+ return $container;
+ }
+
+ /**
+ * Create a database object
+ *
+ * @return DatabaseDriver
+ *
+ * @see DatabaseDriver
+ * @since 1.7.0
+ * @deprecated 5.0 Use the database service in the DI container
+ */
+ protected static function createDbo()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated, register a service provider to create a %2$s instance instead.',
+ __METHOD__,
+ DatabaseInterface::class
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $conf = self::getConfig();
+
+ $host = $conf->get('host');
+ $user = $conf->get('user');
+ $password = $conf->get('password');
+ $database = $conf->get('db');
+ $prefix = $conf->get('dbprefix');
+ $driver = $conf->get('dbtype');
+
+ $options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database, 'prefix' => $prefix);
+
+ if ((int) $conf->get('dbencryption') !== 0) {
+ $options['ssl'] = [
+ 'enable' => true,
+ 'verify_server_cert' => (bool) $conf->get('dbsslverifyservercert'),
+ ];
+
+ foreach (['cipher', 'ca', 'key', 'cert'] as $value) {
+ $confVal = trim($conf->get('dbssl' . $value, ''));
+
+ if ($confVal !== '') {
+ $options['ssl'][$value] = $confVal;
+ }
+ }
+ }
+
+ try {
+ $db = DatabaseDriver::getInstance($options);
+ } catch (\RuntimeException $e) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+
+ jexit('Database Error: ' . $e->getMessage());
+ }
+
+ return $db;
+ }
+
+ /**
+ * Create a mailer object
+ *
+ * @return Mail object
+ *
+ * @see Mail
+ * @since 1.7.0
+ */
+ protected static function createMailer()
+ {
+ $conf = self::getConfig();
+
+ $smtpauth = ($conf->get('smtpauth') == 0) ? null : 1;
+ $smtpuser = $conf->get('smtpuser');
+ $smtppass = $conf->get('smtppass');
+ $smtphost = $conf->get('smtphost');
+ $smtpsecure = $conf->get('smtpsecure');
+ $smtpport = $conf->get('smtpport');
+ $mailfrom = $conf->get('mailfrom');
+ $fromname = $conf->get('fromname');
+ $mailer = $conf->get('mailer');
+
+ // Create a Mail object
+ $mail = Mail::getInstance();
+
+ // Clean the email address
+ $mailfrom = MailHelper::cleanLine($mailfrom);
+
+ // Set default sender without Reply-to if the mailfrom is a valid address
+ if (MailHelper::isEmailAddress($mailfrom)) {
+ // Wrap in try/catch to catch phpmailerExceptions if it is throwing them
+ try {
+ // Check for a false return value if exception throwing is disabled
+ if ($mail->setFrom($mailfrom, MailHelper::cleanLine($fromname), false) === false) {
+ Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
+ }
+ } catch (phpmailerException $e) {
+ Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
+ }
+ }
+
+ // Default mailer is to use PHP's mail function
+ switch ($mailer) {
+ case 'smtp':
+ $mail->useSmtp($smtpauth, $smtphost, $smtpuser, $smtppass, $smtpsecure, $smtpport);
+ break;
+
+ case 'sendmail':
+ $mail->isSendmail();
+ break;
+
+ default:
+ $mail->isMail();
+ break;
+ }
+
+ return $mail;
+ }
+
+ /**
+ * Create a language object
+ *
+ * @return Language object
+ *
+ * @see Language
+ * @since 1.7.0
+ * @deprecated 5.0 Load the language service from the dependency injection container or via $app->getLanguage()
+ */
+ protected static function createLanguage()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the language from the dependency injection container or via %2$s::getApplication()->getLanguage().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $conf = self::getConfig();
+ $locale = $conf->get('language');
+ $debug = $conf->get('debug_lang');
+ $lang = self::getContainer()->get(LanguageFactoryInterface::class)->createLanguage($locale, $debug);
+
+ return $lang;
+ }
+
+ /**
+ * Create a document object
+ *
+ * @return Document object
+ *
+ * @see Document
+ * @since 1.7.0
+ * @deprecated 5.0 Load the document service from the dependency injection container or via $app->getDocument()
+ */
+ protected static function createDocument()
+ {
+ @trigger_error(
+ sprintf(
+ '%1$s() is deprecated. Load the document from the dependency injection container or via %2$s::getApplication()->getDocument().',
+ __METHOD__,
+ __CLASS__
+ ),
+ E_USER_DEPRECATED
+ );
+
+ $lang = self::getLanguage();
+
+ $input = self::getApplication()->input;
+ $type = $input->get('format', 'html', 'cmd');
+
+ $version = new Version();
+
+ $attributes = array(
+ 'charset' => 'utf-8',
+ 'lineend' => 'unix',
+ 'tab' => "\t",
+ 'language' => $lang->getTag(),
+ 'direction' => $lang->isRtl() ? 'rtl' : 'ltr',
+ 'mediaversion' => $version->getMediaVersion(),
+ );
+
+ return self::getContainer()->get(FactoryInterface::class)->createDocument($type, $attributes);
+ }
+
+ /**
+ * Creates a new stream object with appropriate prefix
+ *
+ * @param boolean $usePrefix Prefix the connections for writing
+ * @param boolean $useNetwork Use network if available for writing; use false to disable (e.g. FTP, SCP)
+ * @param string $userAgentSuffix String to append to user agent
+ * @param boolean $maskUserAgent User agent masking (prefix Mozilla)
+ *
+ * @return Stream
+ *
+ * @see Stream
+ * @since 1.7.0
+ */
+ public static function getStream($usePrefix = true, $useNetwork = true, $userAgentSuffix = 'Joomla', $maskUserAgent = false)
+ {
+ // Setup the context; Joomla! UA and overwrite
+ $context = array();
+ $version = new Version();
+
+ // Set the UA for HTTP and overwrite for FTP
+ $context['http']['user_agent'] = $version->getUserAgent($userAgentSuffix, $maskUserAgent);
+ $context['ftp']['overwrite'] = true;
+
+ if ($usePrefix) {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+ $SCPOptions = ClientHelper::getCredentials('scp');
+
+ if ($FTPOptions['enabled'] == 1 && $useNetwork) {
+ $prefix = 'ftp://' . $FTPOptions['user'] . ':' . $FTPOptions['pass'] . '@' . $FTPOptions['host'];
+ $prefix .= $FTPOptions['port'] ? ':' . $FTPOptions['port'] : '';
+ $prefix .= $FTPOptions['root'];
+ } elseif ($SCPOptions['enabled'] == 1 && $useNetwork) {
+ $prefix = 'ssh2.sftp://' . $SCPOptions['user'] . ':' . $SCPOptions['pass'] . '@' . $SCPOptions['host'];
+ $prefix .= $SCPOptions['port'] ? ':' . $SCPOptions['port'] : '';
+ $prefix .= $SCPOptions['root'];
+ } else {
+ $prefix = JPATH_ROOT . '/';
+ }
+
+ $retval = new Stream($prefix, JPATH_ROOT, $context);
+ } else {
+ $retval = new Stream('', '', $context);
+ }
+
+ return $retval;
+ }
}
diff --git a/libraries/src/Feed/Feed.php b/libraries/src/Feed/Feed.php
index 74716fe28e540..6b2edc0f02b3b 100644
--- a/libraries/src/Feed/Feed.php
+++ b/libraries/src/Feed/Feed.php
@@ -1,4 +1,5 @@
'',
- 'title' => '',
- 'updatedDate' => '',
- 'description' => '',
- 'categories' => array(),
- 'contributors' => array(),
- );
-
- /**
- * @var array The list of feed entry objects.
- * @since 3.1.4
- */
- protected $entries = array();
-
- /**
- * Magic method to return values for feed properties.
- *
- * @param string $name The name of the property.
- *
- * @return mixed
- *
- * @since 3.1.4
- */
- public function __get($name)
- {
- return $this->properties[$name] ?? null;
- }
-
- /**
- * Magic method to set values for feed properties.
- *
- * @param string $name The name of the property.
- * @param mixed $value The value to set for the property.
- *
- * @return void
- *
- * @since 3.1.4
- */
- public function __set($name, $value)
- {
- // Ensure that setting a date always sets a Date instance.
- if ((($name === 'updatedDate') || ($name === 'publishedDate')) && !($value instanceof Date))
- {
- $value = new Date($value);
- }
-
- // Validate that any authors that are set are instances of JFeedPerson or null.
- if (($name === 'author') && (!($value instanceof FeedPerson) || ($value === null)))
- {
- throw new \InvalidArgumentException(
- sprintf(
- '%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
- \get_class($this),
- \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
- )
- );
- }
-
- // Disallow setting categories or contributors directly.
- if (\in_array($name, array('categories', 'contributors')))
- {
- throw new \InvalidArgumentException(
- sprintf(
- 'Cannot directly set %1$s property "%2$s".',
- \get_class($this),
- $name
- )
- );
- }
-
- $this->properties[$name] = $value;
- }
-
- /**
- * Method to add a category to the feed object.
- *
- * @param string $name The name of the category to add.
- * @param string $uri The optional URI for the category to add.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function addCategory($name, $uri = '')
- {
- $this->properties['categories'][$name] = $uri;
-
- return $this;
- }
-
- /**
- * Method to add a contributor to the feed object.
- *
- * @param string $name The full name of the person to add.
- * @param string $email The email address of the person to add.
- * @param string $uri The optional URI for the person to add.
- * @param string $type The optional type of person to add.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function addContributor($name, $email, $uri = null, $type = null)
- {
- $contributor = new FeedPerson($name, $email, $uri, $type);
-
- // If the new contributor already exists then there is nothing to do, so just return.
- foreach ($this->properties['contributors'] as $c)
- {
- if ($c == $contributor)
- {
- return $this;
- }
- }
-
- // Add the new contributor.
- $this->properties['contributors'][] = $contributor;
-
- return $this;
- }
-
- /**
- * Method to add an entry to the feed object.
- *
- * @param FeedEntry $entry The entry object to add.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function addEntry(FeedEntry $entry)
- {
- // If the new entry already exists then there is nothing to do, so just return.
- foreach ($this->entries as $e)
- {
- if ($e == $entry)
- {
- return $this;
- }
- }
-
- // Add the new entry.
- $this->entries[] = $entry;
-
- return $this;
- }
-
- /**
- * Returns a count of the number of entries in the feed.
- *
- * This method is here to implement the Countable interface.
- * You can call it by doing count($feed) rather than $feed->count();
- *
- * @return integer number of entries in the feed.
- */
- #[\ReturnTypeWillChange]
- public function count()
- {
- return \count($this->entries);
- }
-
- /**
- * Whether or not an offset exists. This method is executed when using isset() or empty() on
- * objects implementing ArrayAccess.
- *
- * @param mixed $offset An offset to check for.
- *
- * @return boolean
- *
- * @see ArrayAccess::offsetExists()
- * @since 3.1.4
- */
- #[\ReturnTypeWillChange]
- public function offsetExists($offset)
- {
- return isset($this->entries[$offset]);
- }
-
- /**
- * Returns the value at specified offset.
- *
- * @param mixed $offset The offset to retrieve.
- *
- * @return mixed The value at the offset.
- *
- * @see ArrayAccess::offsetGet()
- * @since 3.1.4
- */
- #[\ReturnTypeWillChange]
- public function offsetGet($offset)
- {
- return $this->entries[$offset];
- }
-
- /**
- * Assigns a value to the specified offset.
- *
- * @param mixed $offset The offset to assign the value to.
- * @param FeedEntry $value The JFeedEntry to set.
- *
- * @return boolean
- *
- * @see ArrayAccess::offsetSet()
- * @since 3.1.4
- * @throws \InvalidArgumentException
- */
- #[\ReturnTypeWillChange]
- public function offsetSet($offset, $value)
- {
- if (!($value instanceof FeedEntry))
- {
- throw new \InvalidArgumentException(
- sprintf(
- '%1$s entries must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
- \get_class($this),
- \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
- )
- );
- }
-
- $this->entries[$offset] = $value;
-
- return true;
- }
-
- /**
- * Unsets an offset.
- *
- * @param mixed $offset The offset to unset.
- *
- * @return void
- *
- * @see ArrayAccess::offsetUnset()
- * @since 3.1.4
- */
- #[\ReturnTypeWillChange]
- public function offsetUnset($offset)
- {
- unset($this->entries[$offset]);
- }
-
- /**
- * Method to remove a category from the feed object.
- *
- * @param string $name The name of the category to remove.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function removeCategory($name)
- {
- unset($this->properties['categories'][$name]);
-
- return $this;
- }
-
- /**
- * Method to remove a contributor from the feed object.
- *
- * @param FeedPerson $contributor The person object to remove.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function removeContributor(FeedPerson $contributor)
- {
- // If the contributor exists remove it.
- foreach ($this->properties['contributors'] as $k => $c)
- {
- if ($c == $contributor)
- {
- unset($this->properties['contributors'][$k]);
- $this->properties['contributors'] = array_values($this->properties['contributors']);
-
- return $this;
- }
- }
-
- return $this;
- }
-
- /**
- * Method to remove an entry from the feed object.
- *
- * @param FeedEntry $entry The entry object to remove.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function removeEntry(FeedEntry $entry)
- {
- // If the entry exists remove it.
- foreach ($this->entries as $k => $e)
- {
- if ($e == $entry)
- {
- unset($this->entries[$k]);
- $this->entries = array_values($this->entries);
-
- return $this;
- }
- }
-
- return $this;
- }
-
- /**
- * Shortcut method to set the author for the feed object.
- *
- * @param string $name The full name of the person to set.
- * @param string $email The email address of the person to set.
- * @param string $uri The optional URI for the person to set.
- * @param string $type The optional type of person to set.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function setAuthor($name, $email, $uri = null, $type = null)
- {
- $author = new FeedPerson($name, $email, $uri, $type);
-
- $this->properties['author'] = $author;
-
- return $this;
- }
-
- /**
- * Method to reverse the items if display is set to 'oldest first'
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function reverseItems()
- {
- if (\is_array($this->entries) && !empty($this->entries))
- {
- $this->entries = array_reverse($this->entries);
- }
-
- return $this;
- }
+ /**
+ * @var array The entry properties.
+ * @since 3.1.4
+ */
+ protected $properties = array(
+ 'uri' => '',
+ 'title' => '',
+ 'updatedDate' => '',
+ 'description' => '',
+ 'categories' => array(),
+ 'contributors' => array(),
+ );
+
+ /**
+ * @var array The list of feed entry objects.
+ * @since 3.1.4
+ */
+ protected $entries = array();
+
+ /**
+ * Magic method to return values for feed properties.
+ *
+ * @param string $name The name of the property.
+ *
+ * @return mixed
+ *
+ * @since 3.1.4
+ */
+ public function __get($name)
+ {
+ return $this->properties[$name] ?? null;
+ }
+
+ /**
+ * Magic method to set values for feed properties.
+ *
+ * @param string $name The name of the property.
+ * @param mixed $value The value to set for the property.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ public function __set($name, $value)
+ {
+ // Ensure that setting a date always sets a Date instance.
+ if ((($name === 'updatedDate') || ($name === 'publishedDate')) && !($value instanceof Date)) {
+ $value = new Date($value);
+ }
+
+ // Validate that any authors that are set are instances of JFeedPerson or null.
+ if (($name === 'author') && (!($value instanceof FeedPerson) || ($value === null))) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ '%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
+ \get_class($this),
+ \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
+ )
+ );
+ }
+
+ // Disallow setting categories or contributors directly.
+ if (\in_array($name, array('categories', 'contributors'))) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Cannot directly set %1$s property "%2$s".',
+ \get_class($this),
+ $name
+ )
+ );
+ }
+
+ $this->properties[$name] = $value;
+ }
+
+ /**
+ * Method to add a category to the feed object.
+ *
+ * @param string $name The name of the category to add.
+ * @param string $uri The optional URI for the category to add.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function addCategory($name, $uri = '')
+ {
+ $this->properties['categories'][$name] = $uri;
+
+ return $this;
+ }
+
+ /**
+ * Method to add a contributor to the feed object.
+ *
+ * @param string $name The full name of the person to add.
+ * @param string $email The email address of the person to add.
+ * @param string $uri The optional URI for the person to add.
+ * @param string $type The optional type of person to add.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function addContributor($name, $email, $uri = null, $type = null)
+ {
+ $contributor = new FeedPerson($name, $email, $uri, $type);
+
+ // If the new contributor already exists then there is nothing to do, so just return.
+ foreach ($this->properties['contributors'] as $c) {
+ if ($c == $contributor) {
+ return $this;
+ }
+ }
+
+ // Add the new contributor.
+ $this->properties['contributors'][] = $contributor;
+
+ return $this;
+ }
+
+ /**
+ * Method to add an entry to the feed object.
+ *
+ * @param FeedEntry $entry The entry object to add.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function addEntry(FeedEntry $entry)
+ {
+ // If the new entry already exists then there is nothing to do, so just return.
+ foreach ($this->entries as $e) {
+ if ($e == $entry) {
+ return $this;
+ }
+ }
+
+ // Add the new entry.
+ $this->entries[] = $entry;
+
+ return $this;
+ }
+
+ /**
+ * Returns a count of the number of entries in the feed.
+ *
+ * This method is here to implement the Countable interface.
+ * You can call it by doing count($feed) rather than $feed->count();
+ *
+ * @return integer number of entries in the feed.
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return \count($this->entries);
+ }
+
+ /**
+ * Whether or not an offset exists. This method is executed when using isset() or empty() on
+ * objects implementing ArrayAccess.
+ *
+ * @param mixed $offset An offset to check for.
+ *
+ * @return boolean
+ *
+ * @see ArrayAccess::offsetExists()
+ * @since 3.1.4
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return isset($this->entries[$offset]);
+ }
+
+ /**
+ * Returns the value at specified offset.
+ *
+ * @param mixed $offset The offset to retrieve.
+ *
+ * @return mixed The value at the offset.
+ *
+ * @see ArrayAccess::offsetGet()
+ * @since 3.1.4
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->entries[$offset];
+ }
+
+ /**
+ * Assigns a value to the specified offset.
+ *
+ * @param mixed $offset The offset to assign the value to.
+ * @param FeedEntry $value The JFeedEntry to set.
+ *
+ * @return boolean
+ *
+ * @see ArrayAccess::offsetSet()
+ * @since 3.1.4
+ * @throws \InvalidArgumentException
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ if (!($value instanceof FeedEntry)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ '%1$s entries must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
+ \get_class($this),
+ \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
+ )
+ );
+ }
+
+ $this->entries[$offset] = $value;
+
+ return true;
+ }
+
+ /**
+ * Unsets an offset.
+ *
+ * @param mixed $offset The offset to unset.
+ *
+ * @return void
+ *
+ * @see ArrayAccess::offsetUnset()
+ * @since 3.1.4
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ unset($this->entries[$offset]);
+ }
+
+ /**
+ * Method to remove a category from the feed object.
+ *
+ * @param string $name The name of the category to remove.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function removeCategory($name)
+ {
+ unset($this->properties['categories'][$name]);
+
+ return $this;
+ }
+
+ /**
+ * Method to remove a contributor from the feed object.
+ *
+ * @param FeedPerson $contributor The person object to remove.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function removeContributor(FeedPerson $contributor)
+ {
+ // If the contributor exists remove it.
+ foreach ($this->properties['contributors'] as $k => $c) {
+ if ($c == $contributor) {
+ unset($this->properties['contributors'][$k]);
+ $this->properties['contributors'] = array_values($this->properties['contributors']);
+
+ return $this;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to remove an entry from the feed object.
+ *
+ * @param FeedEntry $entry The entry object to remove.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function removeEntry(FeedEntry $entry)
+ {
+ // If the entry exists remove it.
+ foreach ($this->entries as $k => $e) {
+ if ($e == $entry) {
+ unset($this->entries[$k]);
+ $this->entries = array_values($this->entries);
+
+ return $this;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Shortcut method to set the author for the feed object.
+ *
+ * @param string $name The full name of the person to set.
+ * @param string $email The email address of the person to set.
+ * @param string $uri The optional URI for the person to set.
+ * @param string $type The optional type of person to set.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function setAuthor($name, $email, $uri = null, $type = null)
+ {
+ $author = new FeedPerson($name, $email, $uri, $type);
+
+ $this->properties['author'] = $author;
+
+ return $this;
+ }
+
+ /**
+ * Method to reverse the items if display is set to 'oldest first'
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function reverseItems()
+ {
+ if (\is_array($this->entries) && !empty($this->entries)) {
+ $this->entries = array_reverse($this->entries);
+ }
+
+ return $this;
+ }
}
diff --git a/libraries/src/Feed/FeedEntry.php b/libraries/src/Feed/FeedEntry.php
index 0ef3684645346..be7be8cbcb80f 100644
--- a/libraries/src/Feed/FeedEntry.php
+++ b/libraries/src/Feed/FeedEntry.php
@@ -1,4 +1,5 @@
'',
- 'title' => '',
- 'updatedDate' => '',
- 'content' => '',
- 'categories' => array(),
- 'contributors' => array(),
- 'links' => array(),
- );
+ /**
+ * @var array The entry properties.
+ * @since 3.1.4
+ */
+ protected $properties = array(
+ 'uri' => '',
+ 'title' => '',
+ 'updatedDate' => '',
+ 'content' => '',
+ 'categories' => array(),
+ 'contributors' => array(),
+ 'links' => array(),
+ );
- /**
- * Magic method to return values for feed entry properties.
- *
- * @param string $name The name of the property.
- *
- * @return mixed
- *
- * @since 3.1.4
- */
- public function __get($name)
- {
- return $this->properties[$name] ?? null;
- }
+ /**
+ * Magic method to return values for feed entry properties.
+ *
+ * @param string $name The name of the property.
+ *
+ * @return mixed
+ *
+ * @since 3.1.4
+ */
+ public function __get($name)
+ {
+ return $this->properties[$name] ?? null;
+ }
- /**
- * Magic method to set values for feed properties.
- *
- * @param string $name The name of the property.
- * @param mixed $value The value to set for the property.
- *
- * @return void
- *
- * @since 3.1.4
- */
- public function __set($name, $value)
- {
- // Ensure that setting a date always sets a Date instance.
- if ((($name === 'updatedDate') || ($name === 'publishedDate')) && !($value instanceof Date))
- {
- $value = new Date($value);
- }
+ /**
+ * Magic method to set values for feed properties.
+ *
+ * @param string $name The name of the property.
+ * @param mixed $value The value to set for the property.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ public function __set($name, $value)
+ {
+ // Ensure that setting a date always sets a Date instance.
+ if ((($name === 'updatedDate') || ($name === 'publishedDate')) && !($value instanceof Date)) {
+ $value = new Date($value);
+ }
- // Validate that any authors that are set are instances of JFeedPerson or null.
- if (($name === 'author') && (!($value instanceof FeedPerson) || ($value === null)))
- {
- throw new \InvalidArgumentException(
- sprintf(
- '%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
- \get_class($this),
- \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
- )
- );
- }
+ // Validate that any authors that are set are instances of JFeedPerson or null.
+ if (($name === 'author') && (!($value instanceof FeedPerson) || ($value === null))) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ '%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
+ \get_class($this),
+ \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
+ )
+ );
+ }
- // Validate that any sources that are set are instances of JFeed or null.
- if (($name === 'source') && (!($value instanceof Feed) || ($value === null)))
- {
- throw new \InvalidArgumentException(
- sprintf(
- '%1$s "source" must be an instance of Joomla\\CMS\\Feed\\Feed. %2$s given.',
- \get_class($this),
- \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
- )
- );
- }
+ // Validate that any sources that are set are instances of JFeed or null.
+ if (($name === 'source') && (!($value instanceof Feed) || ($value === null))) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ '%1$s "source" must be an instance of Joomla\\CMS\\Feed\\Feed. %2$s given.',
+ \get_class($this),
+ \gettype($value) === 'object' ? \get_class($value) : \gettype($value)
+ )
+ );
+ }
- // Disallow setting categories, contributors, or links directly.
- if (\in_array($name, array('categories', 'contributors', 'links')))
- {
- throw new \InvalidArgumentException(
- sprintf(
- 'Cannot directly set %1$s property "%2$s".',
- \get_class($this),
- $name
- )
- );
- }
+ // Disallow setting categories, contributors, or links directly.
+ if (\in_array($name, array('categories', 'contributors', 'links'))) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'Cannot directly set %1$s property "%2$s".',
+ \get_class($this),
+ $name
+ )
+ );
+ }
- $this->properties[$name] = $value;
- }
+ $this->properties[$name] = $value;
+ }
- /**
- * Method to add a category to the feed entry object.
- *
- * @param string $name The name of the category to add.
- * @param string $uri The optional URI for the category to add.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function addCategory($name, $uri = '')
- {
- $this->properties['categories'][$name] = $uri;
+ /**
+ * Method to add a category to the feed entry object.
+ *
+ * @param string $name The name of the category to add.
+ * @param string $uri The optional URI for the category to add.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function addCategory($name, $uri = '')
+ {
+ $this->properties['categories'][$name] = $uri;
- return $this;
- }
+ return $this;
+ }
- /**
- * Method to add a contributor to the feed entry object.
- *
- * @param string $name The full name of the person to add.
- * @param string $email The email address of the person to add.
- * @param string $uri The optional URI for the person to add.
- * @param string $type The optional type of person to add.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function addContributor($name, $email, $uri = null, $type = null)
- {
- $contributor = new FeedPerson($name, $email, $uri, $type);
+ /**
+ * Method to add a contributor to the feed entry object.
+ *
+ * @param string $name The full name of the person to add.
+ * @param string $email The email address of the person to add.
+ * @param string $uri The optional URI for the person to add.
+ * @param string $type The optional type of person to add.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function addContributor($name, $email, $uri = null, $type = null)
+ {
+ $contributor = new FeedPerson($name, $email, $uri, $type);
- // If the new contributor already exists then there is nothing to do, so just return.
- foreach ($this->properties['contributors'] as $c)
- {
- if ($c == $contributor)
- {
- return $this;
- }
- }
+ // If the new contributor already exists then there is nothing to do, so just return.
+ foreach ($this->properties['contributors'] as $c) {
+ if ($c == $contributor) {
+ return $this;
+ }
+ }
- // Add the new contributor.
- $this->properties['contributors'][] = $contributor;
+ // Add the new contributor.
+ $this->properties['contributors'][] = $contributor;
- return $this;
- }
+ return $this;
+ }
- /**
- * Method to add a link to the feed entry object.
- *
- * @param FeedLink $link The link object to add.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function addLink(FeedLink $link)
- {
- // If the new link already exists then there is nothing to do, so just return.
- foreach ($this->properties['links'] as $l)
- {
- if ($l == $link)
- {
- return $this;
- }
- }
+ /**
+ * Method to add a link to the feed entry object.
+ *
+ * @param FeedLink $link The link object to add.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function addLink(FeedLink $link)
+ {
+ // If the new link already exists then there is nothing to do, so just return.
+ foreach ($this->properties['links'] as $l) {
+ if ($l == $link) {
+ return $this;
+ }
+ }
- // Add the new link.
- $this->properties['links'][] = $link;
+ // Add the new link.
+ $this->properties['links'][] = $link;
- return $this;
- }
+ return $this;
+ }
- /**
- * Method to remove a category from the feed entry object.
- *
- * @param string $name The name of the category to remove.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function removeCategory($name)
- {
- unset($this->properties['categories'][$name]);
+ /**
+ * Method to remove a category from the feed entry object.
+ *
+ * @param string $name The name of the category to remove.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function removeCategory($name)
+ {
+ unset($this->properties['categories'][$name]);
- return $this;
- }
+ return $this;
+ }
- /**
- * Method to remove a contributor from the feed entry object.
- *
- * @param FeedPerson $contributor The person object to remove.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function removeContributor(FeedPerson $contributor)
- {
- // If the contributor exists remove it.
- foreach ($this->properties['contributors'] as $k => $c)
- {
- if ($c == $contributor)
- {
- unset($this->properties['contributors'][$k]);
- $this->properties['contributors'] = array_values($this->properties['contributors']);
+ /**
+ * Method to remove a contributor from the feed entry object.
+ *
+ * @param FeedPerson $contributor The person object to remove.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function removeContributor(FeedPerson $contributor)
+ {
+ // If the contributor exists remove it.
+ foreach ($this->properties['contributors'] as $k => $c) {
+ if ($c == $contributor) {
+ unset($this->properties['contributors'][$k]);
+ $this->properties['contributors'] = array_values($this->properties['contributors']);
- return $this;
- }
- }
+ return $this;
+ }
+ }
- return $this;
- }
+ return $this;
+ }
- /**
- * Method to remove a link from the feed entry object.
- *
- * @param FeedLink $link The link object to remove.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function removeLink(FeedLink $link)
- {
- // If the link exists remove it.
- foreach ($this->properties['links'] as $k => $l)
- {
- if ($l == $link)
- {
- unset($this->properties['links'][$k]);
- $this->properties['links'] = array_values($this->properties['links']);
+ /**
+ * Method to remove a link from the feed entry object.
+ *
+ * @param FeedLink $link The link object to remove.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function removeLink(FeedLink $link)
+ {
+ // If the link exists remove it.
+ foreach ($this->properties['links'] as $k => $l) {
+ if ($l == $link) {
+ unset($this->properties['links'][$k]);
+ $this->properties['links'] = array_values($this->properties['links']);
- return $this;
- }
- }
+ return $this;
+ }
+ }
- return $this;
- }
+ return $this;
+ }
- /**
- * Shortcut method to set the author for the feed entry object.
- *
- * @param string $name The full name of the person to set.
- * @param string $email The email address of the person to set.
- * @param string $uri The optional URI for the person to set.
- * @param string $type The optional type of person to set.
- *
- * @return FeedEntry
- *
- * @since 3.1.4
- */
- public function setAuthor($name, $email, $uri = null, $type = null)
- {
- $author = new FeedPerson($name, $email, $uri, $type);
+ /**
+ * Shortcut method to set the author for the feed entry object.
+ *
+ * @param string $name The full name of the person to set.
+ * @param string $email The email address of the person to set.
+ * @param string $uri The optional URI for the person to set.
+ * @param string $type The optional type of person to set.
+ *
+ * @return FeedEntry
+ *
+ * @since 3.1.4
+ */
+ public function setAuthor($name, $email, $uri = null, $type = null)
+ {
+ $author = new FeedPerson($name, $email, $uri, $type);
- $this->properties['author'] = $author;
+ $this->properties['author'] = $author;
- return $this;
- }
+ return $this;
+ }
}
diff --git a/libraries/src/Feed/FeedFactory.php b/libraries/src/Feed/FeedFactory.php
index 45463c856d11b..4795b9f730cc5 100644
--- a/libraries/src/Feed/FeedFactory.php
+++ b/libraries/src/Feed/FeedFactory.php
@@ -1,4 +1,5 @@
'Joomla\\CMS\\Feed\\Parser\\RssParser', 'feed' => 'Joomla\\CMS\\Feed\\Parser\\AtomParser');
-
- /**
- * Method to load a URI into the feed reader for parsing.
- *
- * @param string $uri The URI of the feed to load. Idn uris must be passed already converted to punycode.
- *
- * @return Feed
- *
- * @since 3.1.4
- * @throws \InvalidArgumentException
- * @throws \RuntimeException
- */
- public function getFeed($uri)
- {
- // Create the XMLReader object.
- $reader = new \XMLReader;
-
- // Open the URI within the stream reader.
- if (!@$reader->open($uri, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
- {
- // Retry with JHttpFactory that allow using CURL and Sockets as alternative method when available
-
- // Adding a valid user agent string, otherwise some feed-servers returning an error
- $options = new Registry;
- $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
-
- try
- {
- $response = HttpFactory::getHttp($options)->get($uri);
- }
- catch (\RuntimeException $e)
- {
- throw new \RuntimeException('Unable to open the feed.', $e->getCode(), $e);
- }
-
- if ($response->code != 200)
- {
- throw new \RuntimeException('Unable to open the feed.');
- }
-
- // Set the value to the XMLReader parser
- if (!$reader->XML($response->body, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
- {
- throw new \RuntimeException('Unable to parse the feed.');
- }
- }
-
- try
- {
- // Skip ahead to the root node.
- while ($reader->read())
- {
- if ($reader->nodeType == \XMLReader::ELEMENT)
- {
- break;
- }
- }
- }
- catch (\Exception $e)
- {
- throw new \RuntimeException('Error reading feed.', $e->getCode(), $e);
- }
-
- // Setup the appropriate feed parser for the feed.
- $parser = $this->_fetchFeedParser($reader->name, $reader);
-
- return $parser->parse();
- }
-
- /**
- * Method to register a FeedParser class for a given root tag name.
- *
- * @param string $tagName The root tag name for which to register the parser class.
- * @param string $className The FeedParser class name to register for a root tag name.
- * @param boolean $overwrite True to overwrite the parser class if one is already registered.
- *
- * @return FeedFactory
- *
- * @since 3.1.4
- * @throws \InvalidArgumentException
- */
- public function registerParser($tagName, $className, $overwrite = false)
- {
- // Verify that the class exists.
- if (!class_exists($className))
- {
- throw new \InvalidArgumentException('The feed parser class ' . $className . ' does not exist.');
- }
-
- // Validate that the tag name is valid.
- if (!preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $tagName))
- {
- throw new \InvalidArgumentException('The tag name ' . $tagName . ' is not valid.');
- }
-
- // Register the given parser class for the tag name if nothing registered or the overwrite flag set.
- if (empty($this->parsers[$tagName]) || (bool) $overwrite)
- {
- $this->parsers[(string) $tagName] = (string) $className;
- }
-
- return $this;
- }
-
- /**
- * Method to get the registered Parsers
- *
- * @return array
- *
- * @since 4.0.0
- */
- public function getParsers()
- {
- return $this->parsers;
- }
-
- /**
- * Method to return a new JFeedParser object based on the registered parsers and a given type.
- *
- * @param string $type The name of parser to return.
- * @param \XMLReader $reader The XMLReader instance for the feed.
- *
- * @return FeedParser
- *
- * @since 3.1.4
- * @throws \LogicException
- */
- private function _fetchFeedParser($type, \XMLReader $reader)
- {
- // Look for a registered parser for the feed type.
- if (empty($this->parsers[$type]))
- {
- throw new \LogicException('No registered feed parser for type ' . $type . '.');
- }
-
- return new $this->parsers[$type]($reader);
- }
+ /**
+ * @var array The list of registered parser classes for feeds.
+ * @since 3.1.4
+ */
+ protected $parsers = array('rss' => 'Joomla\\CMS\\Feed\\Parser\\RssParser', 'feed' => 'Joomla\\CMS\\Feed\\Parser\\AtomParser');
+
+ /**
+ * Method to load a URI into the feed reader for parsing.
+ *
+ * @param string $uri The URI of the feed to load. Idn uris must be passed already converted to punycode.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ * @throws \InvalidArgumentException
+ * @throws \RuntimeException
+ */
+ public function getFeed($uri)
+ {
+ // Create the XMLReader object.
+ $reader = new \XMLReader();
+
+ // Open the URI within the stream reader.
+ if (!@$reader->open($uri, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING)) {
+ // Retry with JHttpFactory that allow using CURL and Sockets as alternative method when available
+
+ // Adding a valid user agent string, otherwise some feed-servers returning an error
+ $options = new Registry();
+ $options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');
+
+ try {
+ $response = HttpFactory::getHttp($options)->get($uri);
+ } catch (\RuntimeException $e) {
+ throw new \RuntimeException('Unable to open the feed.', $e->getCode(), $e);
+ }
+
+ if ($response->code != 200) {
+ throw new \RuntimeException('Unable to open the feed.');
+ }
+
+ // Set the value to the XMLReader parser
+ if (!$reader->XML($response->body, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING)) {
+ throw new \RuntimeException('Unable to parse the feed.');
+ }
+ }
+
+ try {
+ // Skip ahead to the root node.
+ while ($reader->read()) {
+ if ($reader->nodeType == \XMLReader::ELEMENT) {
+ break;
+ }
+ }
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Error reading feed.', $e->getCode(), $e);
+ }
+
+ // Setup the appropriate feed parser for the feed.
+ $parser = $this->_fetchFeedParser($reader->name, $reader);
+
+ return $parser->parse();
+ }
+
+ /**
+ * Method to register a FeedParser class for a given root tag name.
+ *
+ * @param string $tagName The root tag name for which to register the parser class.
+ * @param string $className The FeedParser class name to register for a root tag name.
+ * @param boolean $overwrite True to overwrite the parser class if one is already registered.
+ *
+ * @return FeedFactory
+ *
+ * @since 3.1.4
+ * @throws \InvalidArgumentException
+ */
+ public function registerParser($tagName, $className, $overwrite = false)
+ {
+ // Verify that the class exists.
+ if (!class_exists($className)) {
+ throw new \InvalidArgumentException('The feed parser class ' . $className . ' does not exist.');
+ }
+
+ // Validate that the tag name is valid.
+ if (!preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $tagName)) {
+ throw new \InvalidArgumentException('The tag name ' . $tagName . ' is not valid.');
+ }
+
+ // Register the given parser class for the tag name if nothing registered or the overwrite flag set.
+ if (empty($this->parsers[$tagName]) || (bool) $overwrite) {
+ $this->parsers[(string) $tagName] = (string) $className;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to get the registered Parsers
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ public function getParsers()
+ {
+ return $this->parsers;
+ }
+
+ /**
+ * Method to return a new JFeedParser object based on the registered parsers and a given type.
+ *
+ * @param string $type The name of parser to return.
+ * @param \XMLReader $reader The XMLReader instance for the feed.
+ *
+ * @return FeedParser
+ *
+ * @since 3.1.4
+ * @throws \LogicException
+ */
+ private function _fetchFeedParser($type, \XMLReader $reader)
+ {
+ // Look for a registered parser for the feed type.
+ if (empty($this->parsers[$type])) {
+ throw new \LogicException('No registered feed parser for type ' . $type . '.');
+ }
+
+ return new $this->parsers[$type]($reader);
+ }
}
diff --git a/libraries/src/Feed/FeedLink.php b/libraries/src/Feed/FeedLink.php
index 6508449655b80..69cfbfff4159f 100644
--- a/libraries/src/Feed/FeedLink.php
+++ b/libraries/src/Feed/FeedLink.php
@@ -1,4 +1,5 @@
uri = $uri;
- $this->relation = $relation;
- $this->type = $type;
- $this->language = $language;
- $this->title = $title;
+ /**
+ * Constructor.
+ *
+ * @param string $uri The URI to the linked resource.
+ * @param string $relation The relationship between the feed and the linked resource.
+ * @param string $type The resource type.
+ * @param string $language The language of the resource found at the given URI.
+ * @param string $title The title of the resource.
+ * @param integer $length The length of the resource in bytes.
+ *
+ * @since 3.1.4
+ * @throws \InvalidArgumentException
+ */
+ public function __construct($uri = null, $relation = null, $type = null, $language = null, $title = null, $length = null)
+ {
+ $this->uri = $uri;
+ $this->relation = $relation;
+ $this->type = $type;
+ $this->language = $language;
+ $this->title = $title;
- // Validate the length input.
- if (isset($length) && !is_numeric($length))
- {
- throw new \InvalidArgumentException('Length must be numeric.');
- }
+ // Validate the length input.
+ if (isset($length) && !is_numeric($length)) {
+ throw new \InvalidArgumentException('Length must be numeric.');
+ }
- $this->length = (int) $length;
- }
+ $this->length = (int) $length;
+ }
}
diff --git a/libraries/src/Feed/FeedParser.php b/libraries/src/Feed/FeedParser.php
index 9e727ddbf8441..9e5fa7c1ba748 100644
--- a/libraries/src/Feed/FeedParser.php
+++ b/libraries/src/Feed/FeedParser.php
@@ -1,4 +1,5 @@
stream = $stream;
- $this->inputFilter = $inputFilter ?: InputFilter::getInstance([], [], 1, 1);
- }
-
- /**
- * Method to parse the feed into a JFeed object.
- *
- * @return Feed
- *
- * @since 3.1.4
- */
- public function parse()
- {
- $feed = new Feed;
-
- // Detect the feed version.
- $this->initialise();
-
- // Let's get this party started...
- do
- {
- // Expand the element for processing.
- $el = new \SimpleXMLElement($this->stream->readOuterXml());
-
- // Get the list of namespaces used within this element.
- $ns = $el->getNamespaces(true);
-
- // Get an array of available namespace objects for the element.
- $namespaces = array();
-
- foreach ($ns as $prefix => $uri)
- {
- // Ignore the empty namespace prefix.
- if (empty($prefix))
- {
- continue;
- }
-
- // Get the necessary namespace objects for the element.
- $namespace = $this->fetchNamespace($prefix);
-
- if ($namespace)
- {
- $namespaces[] = $namespace;
- }
- }
-
- // Process the element.
- $this->processElement($feed, $el, $namespaces);
-
- // Skip over this element's children since it has been processed.
- $this->moveToClosingElement();
- }
-
- while ($this->moveToNextElement());
-
- return $feed;
- }
-
- /**
- * Method to register a namespace handler object.
- *
- * @param string $prefix The XML namespace prefix for which to register the namespace object.
- * @param NamespaceParserInterface $namespace The namespace object to register.
- *
- * @return FeedParser
- *
- * @since 3.1.4
- */
- public function registerNamespace($prefix, NamespaceParserInterface $namespace)
- {
- $this->namespaces[$prefix] = $namespace;
-
- return $this;
- }
-
- /**
- * Method to initialise the feed for parsing. If child parsers need to detect versions or other
- * such things this is where you'll want to implement that logic.
- *
- * @return void
- *
- * @since 3.1.4
- */
- abstract protected function initialise();
-
- /**
- * Method to parse a specific feed element.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- * @param array $namespaces The array of relevant namespace objects to process for the element.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function processElement(Feed $feed, \SimpleXMLElement $el, array $namespaces)
- {
- // Build the internal method name.
- $method = 'handle' . ucfirst($el->getName());
-
- // If we are dealing with an item then it is feed entry time.
- if ($el->getName() == $this->entryElementName)
- {
- // Create a new feed entry for the item.
- $entry = new FeedEntry;
-
- // First call the internal method.
- $this->processFeedEntry($entry, $el);
-
- foreach ($namespaces as $namespace)
- {
- if ($namespace instanceof NamespaceParserInterface)
- {
- $namespace->processElementForFeedEntry($entry, $el);
- }
- }
-
- // Add the new entry to the feed.
- $feed->addEntry($entry);
-
- return;
- }
-
- // Otherwise we treat it like any other element.
-
- // First call the internal method.
- if (\is_callable(array($this, $method)))
- {
- $this->$method($feed, $el);
- }
-
- foreach ($namespaces as $namespace)
- {
- if ($namespace instanceof NamespaceParserInterface)
- {
- $namespace->processElementForFeed($feed, $el);
- }
- }
- }
-
- /**
- * Method to get a namespace object for a given namespace prefix.
- *
- * @param string $prefix The XML prefix for which to fetch the namespace object.
- *
- * @return mixed NamespaceParserInterface or false if none exists.
- *
- * @since 3.1.4
- */
- protected function fetchNamespace($prefix)
- {
- if (isset($this->namespaces[$prefix]))
- {
- return $this->namespaces[$prefix];
- }
-
- $className = \get_class($this) . ucfirst($prefix);
-
- if (class_exists($className))
- {
- $this->namespaces[$prefix] = new $className;
-
- return $this->namespaces[$prefix];
- }
-
- return false;
- }
-
- /**
- * Method to move the stream parser to the next XML element node.
- *
- * @param string $name The name of the element for which to move the stream forward until is found.
- *
- * @return boolean True if the stream parser is on an XML element node.
- *
- * @since 3.1.4
- */
- protected function moveToNextElement($name = null)
- {
- // Only keep looking until the end of the stream.
- while ($this->stream->read())
- {
- // As soon as we get to the next ELEMENT node we are done.
- if ($this->stream->nodeType == \XMLReader::ELEMENT)
- {
- // If we are looking for a specific name make sure we have it.
- if (isset($name) && ($this->stream->name != $name))
- {
- continue;
- }
-
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Method to move the stream parser to the closing XML node of the current element.
- *
- * @return void
- *
- * @since 3.1.4
- * @throws \RuntimeException If the closing tag cannot be found.
- */
- protected function moveToClosingElement()
- {
- // If we are on a self-closing tag then there is nothing to do.
- if ($this->stream->isEmptyElement)
- {
- return;
- }
-
- // Get the name and depth for the current node so that we can match the closing node.
- $name = $this->stream->name;
- $depth = $this->stream->depth;
-
- // Only keep looking until the end of the stream.
- while ($this->stream->read())
- {
- // If we have an END_ELEMENT node with the same name and depth as the node we started with we have a bingo. :-)
- if (($this->stream->name == $name) && ($this->stream->depth == $depth) && ($this->stream->nodeType == \XMLReader::END_ELEMENT))
- {
- return;
- }
- }
-
- throw new \RuntimeException('Unable to find the closing XML node.');
- }
+ /**
+ * The feed element name for the entry elements.
+ *
+ * @var string
+ * @since 3.1.4
+ */
+ protected $entryElementName = 'entry';
+
+ /**
+ * Array of NamespaceParserInterface objects
+ *
+ * @var array
+ * @since 3.1.4
+ */
+ protected $namespaces = array();
+
+ /**
+ * The XMLReader stream object for the feed.
+ *
+ * @var \XMLReader
+ * @since 3.1.4
+ */
+ protected $stream;
+
+ /**
+ * The InputFilter
+ *
+ * @var InputFilter
+ * @since 3.9.25
+ */
+ protected $inputFilter;
+
+ /**
+ * Constructor.
+ *
+ * @param \XMLReader $stream The XMLReader stream object for the feed.
+ * @param InputFilter $inputFilter The InputFilter object to be used
+ *
+ * @since 3.1.4
+ */
+ public function __construct(\XMLReader $stream, InputFilter $inputFilter = null)
+ {
+ $this->stream = $stream;
+ $this->inputFilter = $inputFilter ?: InputFilter::getInstance([], [], 1, 1);
+ }
+
+ /**
+ * Method to parse the feed into a JFeed object.
+ *
+ * @return Feed
+ *
+ * @since 3.1.4
+ */
+ public function parse()
+ {
+ $feed = new Feed();
+
+ // Detect the feed version.
+ $this->initialise();
+
+ // Let's get this party started...
+ do {
+ // Expand the element for processing.
+ $el = new \SimpleXMLElement($this->stream->readOuterXml());
+
+ // Get the list of namespaces used within this element.
+ $ns = $el->getNamespaces(true);
+
+ // Get an array of available namespace objects for the element.
+ $namespaces = array();
+
+ foreach ($ns as $prefix => $uri) {
+ // Ignore the empty namespace prefix.
+ if (empty($prefix)) {
+ continue;
+ }
+
+ // Get the necessary namespace objects for the element.
+ $namespace = $this->fetchNamespace($prefix);
+
+ if ($namespace) {
+ $namespaces[] = $namespace;
+ }
+ }
+
+ // Process the element.
+ $this->processElement($feed, $el, $namespaces);
+
+ // Skip over this element's children since it has been processed.
+ $this->moveToClosingElement();
+ } while ($this->moveToNextElement());
+
+ return $feed;
+ }
+
+ /**
+ * Method to register a namespace handler object.
+ *
+ * @param string $prefix The XML namespace prefix for which to register the namespace object.
+ * @param NamespaceParserInterface $namespace The namespace object to register.
+ *
+ * @return FeedParser
+ *
+ * @since 3.1.4
+ */
+ public function registerNamespace($prefix, NamespaceParserInterface $namespace)
+ {
+ $this->namespaces[$prefix] = $namespace;
+
+ return $this;
+ }
+
+ /**
+ * Method to initialise the feed for parsing. If child parsers need to detect versions or other
+ * such things this is where you'll want to implement that logic.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ abstract protected function initialise();
+
+ /**
+ * Method to parse a specific feed element.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ * @param array $namespaces The array of relevant namespace objects to process for the element.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function processElement(Feed $feed, \SimpleXMLElement $el, array $namespaces)
+ {
+ // Build the internal method name.
+ $method = 'handle' . ucfirst($el->getName());
+
+ // If we are dealing with an item then it is feed entry time.
+ if ($el->getName() == $this->entryElementName) {
+ // Create a new feed entry for the item.
+ $entry = new FeedEntry();
+
+ // First call the internal method.
+ $this->processFeedEntry($entry, $el);
+
+ foreach ($namespaces as $namespace) {
+ if ($namespace instanceof NamespaceParserInterface) {
+ $namespace->processElementForFeedEntry($entry, $el);
+ }
+ }
+
+ // Add the new entry to the feed.
+ $feed->addEntry($entry);
+
+ return;
+ }
+
+ // Otherwise we treat it like any other element.
+
+ // First call the internal method.
+ if (\is_callable(array($this, $method))) {
+ $this->$method($feed, $el);
+ }
+
+ foreach ($namespaces as $namespace) {
+ if ($namespace instanceof NamespaceParserInterface) {
+ $namespace->processElementForFeed($feed, $el);
+ }
+ }
+ }
+
+ /**
+ * Method to get a namespace object for a given namespace prefix.
+ *
+ * @param string $prefix The XML prefix for which to fetch the namespace object.
+ *
+ * @return mixed NamespaceParserInterface or false if none exists.
+ *
+ * @since 3.1.4
+ */
+ protected function fetchNamespace($prefix)
+ {
+ if (isset($this->namespaces[$prefix])) {
+ return $this->namespaces[$prefix];
+ }
+
+ $className = \get_class($this) . ucfirst($prefix);
+
+ if (class_exists($className)) {
+ $this->namespaces[$prefix] = new $className();
+
+ return $this->namespaces[$prefix];
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to move the stream parser to the next XML element node.
+ *
+ * @param string $name The name of the element for which to move the stream forward until is found.
+ *
+ * @return boolean True if the stream parser is on an XML element node.
+ *
+ * @since 3.1.4
+ */
+ protected function moveToNextElement($name = null)
+ {
+ // Only keep looking until the end of the stream.
+ while ($this->stream->read()) {
+ // As soon as we get to the next ELEMENT node we are done.
+ if ($this->stream->nodeType == \XMLReader::ELEMENT) {
+ // If we are looking for a specific name make sure we have it.
+ if (isset($name) && ($this->stream->name != $name)) {
+ continue;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to move the stream parser to the closing XML node of the current element.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ * @throws \RuntimeException If the closing tag cannot be found.
+ */
+ protected function moveToClosingElement()
+ {
+ // If we are on a self-closing tag then there is nothing to do.
+ if ($this->stream->isEmptyElement) {
+ return;
+ }
+
+ // Get the name and depth for the current node so that we can match the closing node.
+ $name = $this->stream->name;
+ $depth = $this->stream->depth;
+
+ // Only keep looking until the end of the stream.
+ while ($this->stream->read()) {
+ // If we have an END_ELEMENT node with the same name and depth as the node we started with we have a bingo. :-)
+ if (($this->stream->name == $name) && ($this->stream->depth == $depth) && ($this->stream->nodeType == \XMLReader::END_ELEMENT)) {
+ return;
+ }
+ }
+
+ throw new \RuntimeException('Unable to find the closing XML node.');
+ }
}
diff --git a/libraries/src/Feed/FeedPerson.php b/libraries/src/Feed/FeedPerson.php
index 6c89bfb355587..f6d0f96168979 100644
--- a/libraries/src/Feed/FeedPerson.php
+++ b/libraries/src/Feed/FeedPerson.php
@@ -1,4 +1,5 @@
name = $name;
- $this->email = $email;
- $this->uri = $uri;
- $this->type = $type;
- }
+ /**
+ * Constructor.
+ *
+ * @param string $name The full name of the person.
+ * @param string $email The email address of the person.
+ * @param string $uri The URI for the person.
+ * @param string $type The type of person.
+ *
+ * @since 3.1.4
+ */
+ public function __construct($name = null, $email = null, $uri = null, $type = null)
+ {
+ $this->name = $name;
+ $this->email = $email;
+ $this->uri = $uri;
+ $this->type = $type;
+ }
}
diff --git a/libraries/src/Feed/Parser/AtomParser.php b/libraries/src/Feed/Parser/AtomParser.php
index dd7a902f28376..57def5e92bb74 100644
--- a/libraries/src/Feed/Parser/AtomParser.php
+++ b/libraries/src/Feed/Parser/AtomParser.php
@@ -1,4 +1,5 @@
` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleAuthor(Feed $feed, \SimpleXMLElement $el)
- {
- // Set the author information from the XML element.
- $feed->setAuthor(
- $this->inputFilter->clean((string) $el->name, 'html'),
- filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
- filter_var((string) $el->uri, FILTER_VALIDATE_URL)
- );
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleAuthor(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Set the author information from the XML element.
+ $feed->setAuthor(
+ $this->inputFilter->clean((string) $el->name, 'html'),
+ filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
+ filter_var((string) $el->uri, FILTER_VALIDATE_URL)
+ );
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleContributor(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->addContributor(
- $this->inputFilter->clean((string) $el->name, 'html'),
- filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
- filter_var((string) $el->uri, FILTER_VALIDATE_URL)
- );
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleContributor(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->addContributor(
+ $this->inputFilter->clean((string) $el->name, 'html'),
+ filter_var((string) $el->email, FILTER_VALIDATE_EMAIL),
+ filter_var((string) $el->uri, FILTER_VALIDATE_URL)
+ );
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->generator = $this->inputFilter->clean((string) $el, 'html');
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->generator = $this->inputFilter->clean((string) $el, 'html');
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleId(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->uri = (string) $el;
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleId(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->uri = (string) $el;
+ }
- /**
- * Method to handle the ` ` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleLink(Feed $feed, \SimpleXMLElement $el)
- {
- $link = new FeedLink;
- $link->uri = (string) $el['href'];
- $link->language = (string) $el['hreflang'];
- $link->length = (int) $el['length'];
- $link->relation = (string) $el['rel'];
- $link->title = $this->inputFilter->clean((string) $el['title'], 'html');
- $link->type = (string) $el['type'];
+ /**
+ * Method to handle the ` ` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleLink(Feed $feed, \SimpleXMLElement $el)
+ {
+ $link = new FeedLink();
+ $link->uri = (string) $el['href'];
+ $link->language = (string) $el['hreflang'];
+ $link->length = (int) $el['length'];
+ $link->relation = (string) $el['rel'];
+ $link->title = $this->inputFilter->clean((string) $el['title'], 'html');
+ $link->type = (string) $el['type'];
- $feed->link = $link;
- }
+ $feed->link = $link;
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleRights(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->copyright = $this->inputFilter->clean((string) $el, 'html');
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleRights(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->copyright = $this->inputFilter->clean((string) $el, 'html');
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleSubtitle(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->description = $this->inputFilter->clean((string) $el, 'html');
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleSubtitle(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->description = $this->inputFilter->clean((string) $el, 'html');
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->title = $this->inputFilter->clean((string) $el, 'html');
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->title = $this->inputFilter->clean((string) $el, 'html');
+ }
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleUpdated(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->updatedDate = $this->inputFilter->clean((string) $el, 'html');
- }
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleUpdated(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->updatedDate = $this->inputFilter->clean((string) $el, 'html');
+ }
- /**
- * Method to initialise the feed for parsing. Here we detect the version and advance the stream
- * reader so that it is ready to parse feed elements.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function initialise()
- {
- // We want to move forward to the first XML Element after the xml doc type declaration
- $this->moveToNextElement();
+ /**
+ * Method to initialise the feed for parsing. Here we detect the version and advance the stream
+ * reader so that it is ready to parse feed elements.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function initialise()
+ {
+ // We want to move forward to the first XML Element after the xml doc type declaration
+ $this->moveToNextElement();
- $this->version = ($this->stream->getAttribute('version') == '0.3') ? '0.3' : '1.0';
- }
+ $this->version = ($this->stream->getAttribute('version') == '0.3') ? '0.3' : '1.0';
+ }
- /**
- * Method to handle a `` element for the feed.
- *
- * @param FeedEntry $entry The FeedEntry object being built from the parsed feed entry.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
- {
- $entry->uri = (string) $el->id;
- $entry->title = $this->inputFilter->clean((string) $el->title, 'html');
- $entry->updatedDate = $this->inputFilter->clean((string) $el->updated, 'html');
- $entry->content = $this->inputFilter->clean((string) $el->summary, 'html');
+ /**
+ * Method to handle a `` element for the feed.
+ *
+ * @param FeedEntry $entry The FeedEntry object being built from the parsed feed entry.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
+ {
+ $entry->uri = (string) $el->id;
+ $entry->title = $this->inputFilter->clean((string) $el->title, 'html');
+ $entry->updatedDate = $this->inputFilter->clean((string) $el->updated, 'html');
+ $entry->content = $this->inputFilter->clean((string) $el->summary, 'html');
- if (!$entry->content)
- {
- $entry->content = $this->inputFilter->clean((string) $el->content, 'html');
- }
+ if (!$entry->content) {
+ $entry->content = $this->inputFilter->clean((string) $el->content, 'html');
+ }
- if (filter_var($entry->uri, FILTER_VALIDATE_URL) === false && !\is_null($el->link) && $el->link)
- {
- $link = $el->link;
+ if (filter_var($entry->uri, FILTER_VALIDATE_URL) === false && !\is_null($el->link) && $el->link) {
+ $link = $el->link;
- if (\is_array($link))
- {
- $link = $this->bestLinkForUri($link);
- }
+ if (\is_array($link)) {
+ $link = $this->bestLinkForUri($link);
+ }
- $uri = (string) $link['href'];
+ $uri = (string) $link['href'];
- if ($uri)
- {
- $entry->uri = $uri;
- }
- }
- }
+ if ($uri) {
+ $entry->uri = $uri;
+ }
+ }
+ }
- /**
- * If there is more than one in the feed entry, find the most appropriate one and return it.
- *
- * @param array $links Array of elements from the feed entry.
- *
- * @return \SimpleXMLElement
- */
- private function bestLinkForUri(array $links)
- {
- $linkPrefs = array('', 'self', 'alternate');
+ /**
+ * If there is more than one in the feed entry, find the most appropriate one and return it.
+ *
+ * @param array $links Array of elements from the feed entry.
+ *
+ * @return \SimpleXMLElement
+ */
+ private function bestLinkForUri(array $links)
+ {
+ $linkPrefs = array('', 'self', 'alternate');
- foreach ($linkPrefs as $pref)
- {
- foreach ($links as $link)
- {
- $rel = (string) $link['rel'];
+ foreach ($linkPrefs as $pref) {
+ foreach ($links as $link) {
+ $rel = (string) $link['rel'];
- if ($rel === $pref)
- {
- return $link;
- }
- }
- }
+ if ($rel === $pref) {
+ return $link;
+ }
+ }
+ }
- return array_shift($links);
- }
+ return array_shift($links);
+ }
}
diff --git a/libraries/src/Feed/Parser/NamespaceParserInterface.php b/libraries/src/Feed/Parser/NamespaceParserInterface.php
index 8068ad8033e7a..fa64204239dcc 100644
--- a/libraries/src/Feed/Parser/NamespaceParserInterface.php
+++ b/libraries/src/Feed/Parser/NamespaceParserInterface.php
@@ -1,4 +1,5 @@
` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleCategory(Feed $feed, \SimpleXMLElement $el)
- {
- // Get the data from the element.
- $domain = (string) $el['domain'];
- $category = $this->inputFilter->clean((string) $el, 'html');
-
- $feed->addCategory($category, $domain);
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleCloud(Feed $feed, \SimpleXMLElement $el)
- {
- $cloud = new \stdClass;
- $cloud->domain = (string) $el['domain'];
- $cloud->port = (string) $el['port'];
- $cloud->path = (string) $el['path'];
- $cloud->protocol = (string) $el['protocol'];
- $cloud->registerProcedure = (string) $el['registerProcedure'];
-
- $feed->cloud = $cloud;
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleCopyright(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->copyright = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleDescription(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->description = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->generator = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleImage(Feed $feed, \SimpleXMLElement $el)
- {
- // Create a feed link object for the image.
- $image = new FeedLink(
- (string) $el->url,
- null,
- 'logo',
- null,
- $this->inputFilter->clean((string) $el->title, 'html')
- );
-
- // Populate extra fields if they exist.
- $image->link = (string) filter_var($el->link, FILTER_VALIDATE_URL);
- $image->description = $this->inputFilter->clean((string) $el->description, 'html');
- $image->height = (string) $el->height;
- $image->width = (string) $el->width;
-
- $feed->image = $image;
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleLanguage(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->language = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleLastBuildDate(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->updatedDate = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the ` ` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleLink(Feed $feed, \SimpleXMLElement $el)
- {
- $link = new FeedLink;
- $link->uri = (string) $el['href'];
- $feed->link = $link;
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleManagingEditor(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->author = $this->processPerson((string) $el);
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleSkipDays(Feed $feed, \SimpleXMLElement $el)
- {
- // Initialise the array.
- $days = array();
-
- // Add all of the day values from the feed to the array.
- foreach ($el->day as $day)
- {
- $days[] = (string) $day;
- }
-
- $feed->skipDays = $days;
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleSkipHours(Feed $feed, \SimpleXMLElement $el)
- {
- // Initialise the array.
- $hours = array();
-
- // Add all of the day values from the feed to the array.
- foreach ($el->hour as $hour)
- {
- $hours[] = (int) $hour;
- }
-
- $feed->skipHours = $hours;
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handlePubDate(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->publishedDate = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->title = $this->inputFilter->clean((string) $el, 'html');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleTtl(Feed $feed, \SimpleXMLElement $el)
- {
- $feed->ttl = (integer) $this->inputFilter->clean((string) $el, 'int');
- }
-
- /**
- * Method to handle the `` element for the feed.
- *
- * @param Feed $feed The Feed object being built from the parsed feed.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function handleWebmaster(Feed $feed, \SimpleXMLElement $el)
- {
- // Get the tag contents and split it over the first space.
- $tmp = (string) $el;
- $tmp = explode(' ', $tmp, 2);
-
- // This is really cheap parsing. Probably need to create a method to do this more robustly.
- $name = null;
-
- if (isset($tmp[1]))
- {
- $name = trim(
- $this->inputFilter->clean($tmp[1], 'html'),
- ' ()'
- );
- }
-
- $email = trim(
- filter_var((string) $tmp[0], FILTER_VALIDATE_EMAIL)
- );
-
- $feed->addContributor($name, $email, null, 'webmaster');
- }
-
- /**
- * Method to initialise the feed for parsing. Here we detect the version and advance the stream
- * reader so that it is ready to parse feed elements.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function initialise()
- {
- // We want to move forward to the first XML Element after the xml doc type declaration
- $this->moveToNextElement();
-
- // Read the version attribute.
- $this->version = $this->stream->getAttribute('version');
-
- // We want to move forward to the first element after the element.
- $this->moveToNextElement('channel');
- $this->moveToNextElement();
- }
-
- /**
- * Method to handle a `- ` element for the feed.
- *
- * @param FeedEntry $entry The FeedEntry object being built from the parsed feed entry.
- * @param \SimpleXMLElement $el The current XML element object to handle.
- *
- * @return void
- *
- * @since 3.1.4
- */
- protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
- {
- $entry->uri = (string) filter_var($el->link, FILTER_VALIDATE_URL);
- $entry->title = $this->inputFilter->clean((string) $el->title, 'html');
- $entry->publishedDate = $this->inputFilter->clean((string) $el->pubDate, 'html');
- $entry->updatedDate = $this->inputFilter->clean((string) $el->pubDate, 'html');
- $entry->content = $this->inputFilter->clean((string) $el->description, 'html');
- $entry->guid = $this->inputFilter->clean((string) $el->guid, 'html');
- $entry->isPermaLink = $entry->guid !== '' && (string) $el->guid['isPermaLink'] !== 'false';
- $entry->comments = $this->inputFilter->clean((string) $el->comments, 'html');
-
- // Add the feed entry author if available.
- $author = $this->inputFilter->clean((string) $el->author, 'html');
-
- if (!empty($author))
- {
- $entry->author = $this->processPerson($author);
- }
-
- // Add any categories to the entry.
- foreach ($el->category as $category)
- {
- $entry->addCategory((string) $category, (string) $category['domain']);
- }
-
- // Add any enclosures to the entry.
- foreach ($el->enclosure as $enclosure)
- {
- $link = new FeedLink(
- (string) $enclosure['url'],
- null,
- (string) $enclosure['type'],
- null,
- null,
- (int) $enclosure['length']
- );
-
- $entry->addLink($link);
- }
- }
-
- /**
- * Method to parse a string with person data and return a FeedPerson object.
- *
- * @param string $data The string to parse for a person.
- *
- * @return FeedPerson
- *
- * @since 3.1.4
- */
- protected function processPerson($data)
- {
- // Create a new person object.
- $person = new FeedPerson;
-
- // This is really cheap parsing, but so far good enough. :)
- $data = explode(' ', $data, 2);
-
- if (isset($data[1]))
- {
- $person->name = trim(
- $this->inputFilter->clean($data[1], 'html'),
- ' ()'
- );
- }
-
- // Set the email for the person.
- $person->email = trim(
- filter_var((string) $data[0], FILTER_VALIDATE_EMAIL)
- );
-
- return $person;
- }
+ /**
+ * @var string The feed element name for the entry elements.
+ * @since 3.1.4
+ */
+ protected $entryElementName = 'item';
+
+ /**
+ * @var string The feed format version.
+ * @since 3.1.4
+ */
+ protected $version;
+
+ /**
+ * Method to handle the `
` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleCategory(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Get the data from the element.
+ $domain = (string) $el['domain'];
+ $category = $this->inputFilter->clean((string) $el, 'html');
+
+ $feed->addCategory($category, $domain);
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleCloud(Feed $feed, \SimpleXMLElement $el)
+ {
+ $cloud = new \stdClass();
+ $cloud->domain = (string) $el['domain'];
+ $cloud->port = (string) $el['port'];
+ $cloud->path = (string) $el['path'];
+ $cloud->protocol = (string) $el['protocol'];
+ $cloud->registerProcedure = (string) $el['registerProcedure'];
+
+ $feed->cloud = $cloud;
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleCopyright(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->copyright = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleDescription(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->description = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->generator = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleImage(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Create a feed link object for the image.
+ $image = new FeedLink(
+ (string) $el->url,
+ null,
+ 'logo',
+ null,
+ $this->inputFilter->clean((string) $el->title, 'html')
+ );
+
+ // Populate extra fields if they exist.
+ $image->link = (string) filter_var($el->link, FILTER_VALIDATE_URL);
+ $image->description = $this->inputFilter->clean((string) $el->description, 'html');
+ $image->height = (string) $el->height;
+ $image->width = (string) $el->width;
+
+ $feed->image = $image;
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleLanguage(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->language = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleLastBuildDate(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->updatedDate = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the ` ` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleLink(Feed $feed, \SimpleXMLElement $el)
+ {
+ $link = new FeedLink();
+ $link->uri = (string) $el['href'];
+ $feed->link = $link;
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleManagingEditor(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->author = $this->processPerson((string) $el);
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleSkipDays(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Initialise the array.
+ $days = array();
+
+ // Add all of the day values from the feed to the array.
+ foreach ($el->day as $day) {
+ $days[] = (string) $day;
+ }
+
+ $feed->skipDays = $days;
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleSkipHours(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Initialise the array.
+ $hours = array();
+
+ // Add all of the day values from the feed to the array.
+ foreach ($el->hour as $hour) {
+ $hours[] = (int) $hour;
+ }
+
+ $feed->skipHours = $hours;
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handlePubDate(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->publishedDate = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->title = $this->inputFilter->clean((string) $el, 'html');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleTtl(Feed $feed, \SimpleXMLElement $el)
+ {
+ $feed->ttl = (int) $this->inputFilter->clean((string) $el, 'int');
+ }
+
+ /**
+ * Method to handle the `` element for the feed.
+ *
+ * @param Feed $feed The Feed object being built from the parsed feed.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function handleWebmaster(Feed $feed, \SimpleXMLElement $el)
+ {
+ // Get the tag contents and split it over the first space.
+ $tmp = (string) $el;
+ $tmp = explode(' ', $tmp, 2);
+
+ // This is really cheap parsing. Probably need to create a method to do this more robustly.
+ $name = null;
+
+ if (isset($tmp[1])) {
+ $name = trim(
+ $this->inputFilter->clean($tmp[1], 'html'),
+ ' ()'
+ );
+ }
+
+ $email = trim(
+ filter_var((string) $tmp[0], FILTER_VALIDATE_EMAIL)
+ );
+
+ $feed->addContributor($name, $email, null, 'webmaster');
+ }
+
+ /**
+ * Method to initialise the feed for parsing. Here we detect the version and advance the stream
+ * reader so that it is ready to parse feed elements.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function initialise()
+ {
+ // We want to move forward to the first XML Element after the xml doc type declaration
+ $this->moveToNextElement();
+
+ // Read the version attribute.
+ $this->version = $this->stream->getAttribute('version');
+
+ // We want to move forward to the first element after the element.
+ $this->moveToNextElement('channel');
+ $this->moveToNextElement();
+ }
+
+ /**
+ * Method to handle a `- ` element for the feed.
+ *
+ * @param FeedEntry $entry The FeedEntry object being built from the parsed feed entry.
+ * @param \SimpleXMLElement $el The current XML element object to handle.
+ *
+ * @return void
+ *
+ * @since 3.1.4
+ */
+ protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
+ {
+ $entry->uri = (string) filter_var($el->link, FILTER_VALIDATE_URL);
+ $entry->title = $this->inputFilter->clean((string) $el->title, 'html');
+ $entry->publishedDate = $this->inputFilter->clean((string) $el->pubDate, 'html');
+ $entry->updatedDate = $this->inputFilter->clean((string) $el->pubDate, 'html');
+ $entry->content = $this->inputFilter->clean((string) $el->description, 'html');
+ $entry->guid = $this->inputFilter->clean((string) $el->guid, 'html');
+ $entry->isPermaLink = $entry->guid !== '' && (string) $el->guid['isPermaLink'] !== 'false';
+ $entry->comments = $this->inputFilter->clean((string) $el->comments, 'html');
+
+ // Add the feed entry author if available.
+ $author = $this->inputFilter->clean((string) $el->author, 'html');
+
+ if (!empty($author)) {
+ $entry->author = $this->processPerson($author);
+ }
+
+ // Add any categories to the entry.
+ foreach ($el->category as $category) {
+ $entry->addCategory((string) $category, (string) $category['domain']);
+ }
+
+ // Add any enclosures to the entry.
+ foreach ($el->enclosure as $enclosure) {
+ $link = new FeedLink(
+ (string) $enclosure['url'],
+ null,
+ (string) $enclosure['type'],
+ null,
+ null,
+ (int) $enclosure['length']
+ );
+
+ $entry->addLink($link);
+ }
+ }
+
+ /**
+ * Method to parse a string with person data and return a FeedPerson object.
+ *
+ * @param string $data The string to parse for a person.
+ *
+ * @return FeedPerson
+ *
+ * @since 3.1.4
+ */
+ protected function processPerson($data)
+ {
+ // Create a new person object.
+ $person = new FeedPerson();
+
+ // This is really cheap parsing, but so far good enough. :)
+ $data = explode(' ', $data, 2);
+
+ if (isset($data[1])) {
+ $person->name = trim(
+ $this->inputFilter->clean($data[1], 'html'),
+ ' ()'
+ );
+ }
+
+ // Set the email for the person.
+ $person->email = trim(
+ filter_var((string) $data[0], FILTER_VALIDATE_EMAIL)
+ );
+
+ return $person;
+ }
}
diff --git a/libraries/src/Fields/FieldsServiceInterface.php b/libraries/src/Fields/FieldsServiceInterface.php
index 287f7df925e93..be6ae01f77876 100644
--- a/libraries/src/Fields/FieldsServiceInterface.php
+++ b/libraries/src/Fields/FieldsServiceInterface.php
@@ -1,4 +1,5 @@
copy($src, $dest))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FILE_STREAMS', __METHOD__, $src, $dest, $stream->getError()), Log::WARNING, 'jerror');
-
- return false;
- }
-
- self::invalidateFileCache($dest);
-
- return true;
- }
- else
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // If the parent folder doesn't exist we must create it
- if (!file_exists(\dirname($dest)))
- {
- Folder::create(\dirname($dest));
- }
-
- // Translate the destination path for the FTP account
- $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
-
- if (!$ftp->store($src, $dest))
- {
- // FTP connector throws an error
- return false;
- }
-
- $ret = true;
- }
- else
- {
- if (!@ copy($src, $dest))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');
-
- return false;
- }
-
- $ret = true;
- }
-
- self::invalidateFileCache($dest);
-
- return $ret;
- }
- }
-
- /**
- * Invalidate opcache for a newly written/deleted file immediately, if opcache* functions exist and if this was a PHP file.
- *
- * @param string $filepath The path to the file just written to, to flush from opcache
- * @param boolean $force If set to true, the script will be invalidated regardless of whether invalidation is necessary
- *
- * @return boolean TRUE if the opcode cache for script was invalidated/nothing to invalidate,
- * or FALSE if the opcode cache is disabled or other conditions returning
- * FALSE from opcache_invalidate (like file not found).
- *
- * @since 4.0.1
- */
- public static function invalidateFileCache($filepath, $force = true)
- {
- if (self::canFlushFileCache() && '.php' === strtolower(substr($filepath, -4)))
- {
- return opcache_invalidate($filepath, $force);
- }
-
- return false;
- }
-
- /**
- * First we check if opcache is enabled
- * Then we check if the opcache_invalidate function is available
- * Lastly we check if the host has restricted which scripts can use opcache_invalidate using opcache.restrict_api.
- *
- * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
- * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
- * If the host has this set, check whether the path in `opcache.restrict_api` matches
- * the beginning of the path of the origin file.
- *
- * @return boolean TRUE if we can proceed to use opcache_invalidate to flush a file from the OPCache
- *
- * @since 4.0.1
- */
- public static function canFlushFileCache()
- {
- if (isset(static::$canFlushFileCache))
- {
- return static::$canFlushFileCache;
- }
-
- if (ini_get('opcache.enable')
- && function_exists('opcache_invalidate')
- && (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0)
- )
- {
- static::$canFlushFileCache = true;
- }
- else
- {
- static::$canFlushFileCache = false;
- }
-
- return static::$canFlushFileCache;
- }
-
- /**
- * Delete a file or array of files
- *
- * @param mixed $file The file name or an array of file names
- *
- * @return boolean True on success
- *
- * @since 1.7.0
- */
- public static function delete($file)
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- if (\is_array($file))
- {
- $files = $file;
- }
- else
- {
- $files[] = $file;
- }
-
- // Do NOT use ftp if it is not enabled
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
- }
-
- foreach ($files as $file)
- {
- $file = Path::clean($file);
-
- if (!is_file($file))
- {
- continue;
- }
-
- /**
- * Try making the file writable first. If it's read-only, it can't be deleted
- * on Windows, even if the parent folder is writable
- */
- @chmod($file, 0777);
-
- /**
- * Invalidate the OPCache for the file before actually deleting it
- * @see https://github.com/joomla/joomla-cms/pull/32915#issuecomment-812865635
- * @see https://www.php.net/manual/en/function.opcache-invalidate.php#116372
- */
- self::invalidateFileCache($file);
-
- /**
- * In case of restricted permissions we delete it one way or the other
- * as long as the owner is either the webserver or the ftp
- */
- if (@unlink($file))
- {
- // Do nothing
- }
- elseif ($FTPOptions['enabled'] == 1)
- {
- $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
-
- if (!$ftp->delete($file))
- {
- // FTP connector throws an error
-
- return false;
- }
- }
- else
- {
- $filename = basename($file);
- Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');
-
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * Moves a file
- *
- * @param string $src The path to the source file
- * @param string $dest The path to the destination file
- * @param string $path An optional base path to prefix to the file names
- * @param boolean $useStreams True to use streams
- *
- * @return boolean True on success
- *
- * @since 1.7.0
- */
- public static function move($src, $dest, $path = '', $useStreams = false)
- {
- if ($path)
- {
- $src = Path::clean($path . '/' . $src);
- $dest = Path::clean($path . '/' . $dest);
- }
-
- // Check src path
- if (!is_readable($src))
- {
- Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- if (!$stream->move($src, $dest))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
-
- return false;
- }
-
- self::invalidateFileCache($dest);
-
- return true;
- }
- else
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- // Invalidate the compiled OPCache of the old file so it's no longer used.
- self::invalidateFileCache($src);
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path for the FTP account
- $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
- $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
-
- // Use FTP rename to simulate move
- if (!$ftp->rename($src, $dest))
- {
- Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
-
- return false;
- }
- }
- else
- {
- if (!@ rename($src, $dest))
- {
- Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
-
- return false;
- }
- }
-
- self::invalidateFileCache($dest);
-
- return true;
- }
- }
-
- /**
- * Write contents to a file
- *
- * @param string $file The full file path
- * @param string $buffer The buffer to write
- * @param boolean $useStreams Use streams
- *
- * @return boolean True on success
- *
- * @since 1.7.0
- */
- public static function write($file, $buffer, $useStreams = false)
- {
- @set_time_limit(ini_get('max_execution_time'));
-
- // If the destination directory doesn't exist we need to create it
- if (!file_exists(\dirname($file)))
- {
- if (Folder::create(\dirname($file)) == false)
- {
- return false;
- }
- }
-
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- // Beef up the chunk size to a meg
- $stream->set('chunksize', (1024 * 1024));
-
- if (!$stream->writeFile($file, $buffer))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
-
- return false;
- }
-
- self::invalidateFileCache($file);
-
- return true;
- }
- else
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path for the FTP account and use FTP write buffer to file
- $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
- $ret = $ftp->write($file, $buffer);
- }
- else
- {
- $file = Path::clean($file);
- $ret = \is_int(file_put_contents($file, $buffer));
- }
-
- self::invalidateFileCache($file);
-
- return $ret;
- }
- }
-
- /**
- * Append contents to a file
- *
- * @param string $file The full file path
- * @param string $buffer The buffer to write
- * @param boolean $useStreams Use streams
- *
- * @return boolean True on success
- *
- * @since 3.6.0
- */
- public static function append($file, $buffer, $useStreams = false)
- {
- @set_time_limit(ini_get('max_execution_time'));
-
- // If the file doesn't exist, just write instead of append
- if (!file_exists($file))
- {
- return self::write($file, $buffer, $useStreams);
- }
-
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- // Beef up the chunk size to a meg
- $stream->set('chunksize', (1024 * 1024));
-
- if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close())
- {
- self::invalidateFileCache($file);
-
- return true;
- }
-
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
-
- return false;
- }
- else
- {
- // Initialise variables.
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path for the FTP account and use FTP write buffer to file
- $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
- $ret = $ftp->append($file, $buffer);
- }
- else
- {
- $file = Path::clean($file);
- $ret = \is_int(file_put_contents($file, $buffer, FILE_APPEND));
- }
-
- self::invalidateFileCache($file);
-
- return $ret;
- }
- }
-
- /**
- * Moves an uploaded file to a destination folder
- *
- * @param string $src The name of the php (temporary) uploaded file
- * @param string $dest The path (including filename) to move the uploaded file to
- * @param boolean $useStreams True to use streams
- * @param boolean $allowUnsafe Allow the upload of unsafe files
- * @param array $safeFileOptions Options to InputFilter::isSafeFile
- *
- * @return boolean True on success
- *
- * @since 1.7.0
- */
- public static function upload($src, $dest, $useStreams = false, $allowUnsafe = false, $safeFileOptions = array())
- {
- if (!$allowUnsafe)
- {
- $descriptor = array(
- 'tmp_name' => $src,
- 'name' => basename($dest),
- 'type' => '',
- 'error' => '',
- 'size' => '',
- );
-
- $isSafe = InputFilter::isSafeFile($descriptor, $safeFileOptions);
-
- if (!$isSafe)
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');
-
- return false;
- }
- }
-
- // Ensure that the path is valid and clean
- $dest = Path::clean($dest);
-
- // Create the destination directory if it does not exist
- $baseDir = \dirname($dest);
-
- if (!file_exists($baseDir))
- {
- Folder::create($baseDir);
- }
-
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- if (!$stream->upload($src, $dest))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
-
- return false;
- }
-
- return true;
- }
- else
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
- $ret = false;
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path for the FTP account
- $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
-
- // Copy the file to the destination directory
- if (is_uploaded_file($src) && $ftp->store($src, $dest))
- {
- self::invalidateFileCache($src);
- unlink($src);
- $ret = true;
- }
- else
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
- }
- }
- else
- {
- self::invalidateFileCache($src);
-
- if (is_writable($baseDir) && move_uploaded_file($src, $dest))
- {
- // Short circuit to prevent file permission errors
- if (Path::setPermissions($dest))
- {
- $ret = true;
- }
- else
- {
- Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
- }
- }
- else
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
- }
- }
-
- self::invalidateFileCache($dest);
-
- return $ret;
- }
- }
-
- /**
- * Wrapper for the standard file_exists function
- *
- * @param string $file File path
- *
- * @return boolean True if path is a file
- *
- * @since 1.7.0
- */
- public static function exists($file)
- {
- return is_file(Path::clean($file));
- }
+ /**
+ * @var boolean true if OPCache enabled, and we have permission to invalidate files
+ * @since 4.0.1
+ */
+ protected static $canFlushFileCache;
+
+ /**
+ * Gets the extension of a file name
+ *
+ * @param string $file The file name
+ *
+ * @return string The file extension
+ *
+ * @since 1.7.0
+ */
+ public static function getExt($file)
+ {
+ // String manipulation should be faster than pathinfo() on newer PHP versions.
+ $dot = strrpos($file, '.');
+
+ if ($dot === false) {
+ return '';
+ }
+
+ $ext = substr($file, $dot + 1);
+
+ // Extension cannot contain slashes.
+ if (strpos($ext, '/') !== false || (DIRECTORY_SEPARATOR === '\\' && strpos($ext, '\\') !== false)) {
+ return '';
+ }
+
+ return $ext;
+ }
+
+ /**
+ * Strips the last extension off of a file name
+ *
+ * @param string $file The file name
+ *
+ * @return string The file name without the extension
+ *
+ * @since 1.7.0
+ */
+ public static function stripExt($file)
+ {
+ return preg_replace('#\.[^.]*$#', '', $file);
+ }
+
+ /**
+ * Makes file name safe to use
+ *
+ * @param string $file The name of the file [not full path]
+ *
+ * @return string The sanitised string
+ *
+ * @since 1.7.0
+ */
+ public static function makeSafe($file)
+ {
+ // Remove any trailing dots, as those aren't ever valid file names.
+ $file = rtrim($file, '.');
+
+ // Try transliterating the file name using the native php function
+ if (function_exists('transliterator_transliterate') && function_exists('iconv')) {
+ // Using iconv to ignore characters that can't be transliterated
+ $file = iconv("UTF-8", "ASCII//TRANSLIT//IGNORE", transliterator_transliterate('Any-Latin; Latin-ASCII', $file));
+ }
+
+ $regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');
+
+ return trim(preg_replace($regex, '', $file));
+ }
+
+ /**
+ * Copies a file
+ *
+ * @param string $src The path to the source file
+ * @param string $dest The path to the destination file
+ * @param string $path An optional base path to prefix to the file names
+ * @param boolean $useStreams True to use streams
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function copy($src, $dest, $path = null, $useStreams = false)
+ {
+ // Prepend a base path if it exists
+ if ($path) {
+ $src = Path::clean($path . '/' . $src);
+ $dest = Path::clean($path . '/' . $dest);
+ }
+
+ // Check src path
+ if (!is_readable($src)) {
+ Log::add(Text::sprintf('LIB_FILESYSTEM_ERROR_JFILE_FIND_COPY', __METHOD__, $src), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ if (!$stream->copy($src, $dest)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FILE_STREAMS', __METHOD__, $src, $dest, $stream->getError()), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ self::invalidateFileCache($dest);
+
+ return true;
+ } else {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // If the parent folder doesn't exist we must create it
+ if (!file_exists(\dirname($dest))) {
+ Folder::create(\dirname($dest));
+ }
+
+ // Translate the destination path for the FTP account
+ $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
+
+ if (!$ftp->store($src, $dest)) {
+ // FTP connector throws an error
+ return false;
+ }
+
+ $ret = true;
+ } else {
+ if (!@ copy($src, $dest)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ $ret = true;
+ }
+
+ self::invalidateFileCache($dest);
+
+ return $ret;
+ }
+ }
+
+ /**
+ * Invalidate opcache for a newly written/deleted file immediately, if opcache* functions exist and if this was a PHP file.
+ *
+ * @param string $filepath The path to the file just written to, to flush from opcache
+ * @param boolean $force If set to true, the script will be invalidated regardless of whether invalidation is necessary
+ *
+ * @return boolean TRUE if the opcode cache for script was invalidated/nothing to invalidate,
+ * or FALSE if the opcode cache is disabled or other conditions returning
+ * FALSE from opcache_invalidate (like file not found).
+ *
+ * @since 4.0.1
+ */
+ public static function invalidateFileCache($filepath, $force = true)
+ {
+ if (self::canFlushFileCache() && '.php' === strtolower(substr($filepath, -4))) {
+ return opcache_invalidate($filepath, $force);
+ }
+
+ return false;
+ }
+
+ /**
+ * First we check if opcache is enabled
+ * Then we check if the opcache_invalidate function is available
+ * Lastly we check if the host has restricted which scripts can use opcache_invalidate using opcache.restrict_api.
+ *
+ * `$_SERVER['SCRIPT_FILENAME']` approximates the origin file's path, but `realpath()`
+ * is necessary because `SCRIPT_FILENAME` can be a relative path when run from CLI.
+ * If the host has this set, check whether the path in `opcache.restrict_api` matches
+ * the beginning of the path of the origin file.
+ *
+ * @return boolean TRUE if we can proceed to use opcache_invalidate to flush a file from the OPCache
+ *
+ * @since 4.0.1
+ */
+ public static function canFlushFileCache()
+ {
+ if (isset(static::$canFlushFileCache)) {
+ return static::$canFlushFileCache;
+ }
+
+ if (
+ ini_get('opcache.enable')
+ && function_exists('opcache_invalidate')
+ && (!ini_get('opcache.restrict_api') || stripos(realpath($_SERVER['SCRIPT_FILENAME']), ini_get('opcache.restrict_api')) === 0)
+ ) {
+ static::$canFlushFileCache = true;
+ } else {
+ static::$canFlushFileCache = false;
+ }
+
+ return static::$canFlushFileCache;
+ }
+
+ /**
+ * Delete a file or array of files
+ *
+ * @param mixed $file The file name or an array of file names
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function delete($file)
+ {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if (\is_array($file)) {
+ $files = $file;
+ } else {
+ $files[] = $file;
+ }
+
+ // Do NOT use ftp if it is not enabled
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+ }
+
+ foreach ($files as $file) {
+ $file = Path::clean($file);
+
+ if (!is_file($file)) {
+ continue;
+ }
+
+ /**
+ * Try making the file writable first. If it's read-only, it can't be deleted
+ * on Windows, even if the parent folder is writable
+ */
+ @chmod($file, 0777);
+
+ /**
+ * Invalidate the OPCache for the file before actually deleting it
+ * @see https://github.com/joomla/joomla-cms/pull/32915#issuecomment-812865635
+ * @see https://www.php.net/manual/en/function.opcache-invalidate.php#116372
+ */
+ self::invalidateFileCache($file);
+
+ /**
+ * In case of restricted permissions we delete it one way or the other
+ * as long as the owner is either the webserver or the ftp
+ */
+ if (@unlink($file)) {
+ // Do nothing
+ } elseif ($FTPOptions['enabled'] == 1) {
+ $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
+
+ if (!$ftp->delete($file)) {
+ // FTP connector throws an error
+
+ return false;
+ }
+ } else {
+ $filename = basename($file);
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Moves a file
+ *
+ * @param string $src The path to the source file
+ * @param string $dest The path to the destination file
+ * @param string $path An optional base path to prefix to the file names
+ * @param boolean $useStreams True to use streams
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function move($src, $dest, $path = '', $useStreams = false)
+ {
+ if ($path) {
+ $src = Path::clean($path . '/' . $src);
+ $dest = Path::clean($path . '/' . $dest);
+ }
+
+ // Check src path
+ if (!is_readable($src)) {
+ Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ if (!$stream->move($src, $dest)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ self::invalidateFileCache($dest);
+
+ return true;
+ } else {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ // Invalidate the compiled OPCache of the old file so it's no longer used.
+ self::invalidateFileCache($src);
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path for the FTP account
+ $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
+ $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
+
+ // Use FTP rename to simulate move
+ if (!$ftp->rename($src, $dest)) {
+ Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+ } else {
+ if (!@ rename($src, $dest)) {
+ Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+ }
+
+ self::invalidateFileCache($dest);
+
+ return true;
+ }
+ }
+
+ /**
+ * Write contents to a file
+ *
+ * @param string $file The full file path
+ * @param string $buffer The buffer to write
+ * @param boolean $useStreams Use streams
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function write($file, $buffer, $useStreams = false)
+ {
+ @set_time_limit(ini_get('max_execution_time'));
+
+ // If the destination directory doesn't exist we need to create it
+ if (!file_exists(\dirname($file))) {
+ if (Folder::create(\dirname($file)) == false) {
+ return false;
+ }
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ // Beef up the chunk size to a meg
+ $stream->set('chunksize', (1024 * 1024));
+
+ if (!$stream->writeFile($file, $buffer)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ self::invalidateFileCache($file);
+
+ return true;
+ } else {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path for the FTP account and use FTP write buffer to file
+ $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
+ $ret = $ftp->write($file, $buffer);
+ } else {
+ $file = Path::clean($file);
+ $ret = \is_int(file_put_contents($file, $buffer));
+ }
+
+ self::invalidateFileCache($file);
+
+ return $ret;
+ }
+ }
+
+ /**
+ * Append contents to a file
+ *
+ * @param string $file The full file path
+ * @param string $buffer The buffer to write
+ * @param boolean $useStreams Use streams
+ *
+ * @return boolean True on success
+ *
+ * @since 3.6.0
+ */
+ public static function append($file, $buffer, $useStreams = false)
+ {
+ @set_time_limit(ini_get('max_execution_time'));
+
+ // If the file doesn't exist, just write instead of append
+ if (!file_exists($file)) {
+ return self::write($file, $buffer, $useStreams);
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ // Beef up the chunk size to a meg
+ $stream->set('chunksize', (1024 * 1024));
+
+ if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close()) {
+ self::invalidateFileCache($file);
+
+ return true;
+ }
+
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', __METHOD__, $file, $stream->getError()), Log::WARNING, 'jerror');
+
+ return false;
+ } else {
+ // Initialise variables.
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path for the FTP account and use FTP write buffer to file
+ $file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
+ $ret = $ftp->append($file, $buffer);
+ } else {
+ $file = Path::clean($file);
+ $ret = \is_int(file_put_contents($file, $buffer, FILE_APPEND));
+ }
+
+ self::invalidateFileCache($file);
+
+ return $ret;
+ }
+ }
+
+ /**
+ * Moves an uploaded file to a destination folder
+ *
+ * @param string $src The name of the php (temporary) uploaded file
+ * @param string $dest The path (including filename) to move the uploaded file to
+ * @param boolean $useStreams True to use streams
+ * @param boolean $allowUnsafe Allow the upload of unsafe files
+ * @param array $safeFileOptions Options to InputFilter::isSafeFile
+ *
+ * @return boolean True on success
+ *
+ * @since 1.7.0
+ */
+ public static function upload($src, $dest, $useStreams = false, $allowUnsafe = false, $safeFileOptions = array())
+ {
+ if (!$allowUnsafe) {
+ $descriptor = array(
+ 'tmp_name' => $src,
+ 'name' => basename($dest),
+ 'type' => '',
+ 'error' => '',
+ 'size' => '',
+ );
+
+ $isSafe = InputFilter::isSafeFile($descriptor, $safeFileOptions);
+
+ if (!$isSafe) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');
+
+ return false;
+ }
+ }
+
+ // Ensure that the path is valid and clean
+ $dest = Path::clean($dest);
+
+ // Create the destination directory if it does not exist
+ $baseDir = \dirname($dest);
+
+ if (!file_exists($baseDir)) {
+ Folder::create($baseDir);
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ if (!$stream->upload($src, $dest)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_GENERIC', __METHOD__, $stream->getError()), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ return true;
+ } else {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+ $ret = false;
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path for the FTP account
+ $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
+
+ // Copy the file to the destination directory
+ if (is_uploaded_file($src) && $ftp->store($src, $dest)) {
+ self::invalidateFileCache($src);
+ unlink($src);
+ $ret = true;
+ } else {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
+ }
+ } else {
+ self::invalidateFileCache($src);
+
+ if (is_writable($baseDir) && move_uploaded_file($src, $dest)) {
+ // Short circuit to prevent file permission errors
+ if (Path::setPermissions($dest)) {
+ $ret = true;
+ } else {
+ Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
+ }
+ } else {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
+ }
+ }
+
+ self::invalidateFileCache($dest);
+
+ return $ret;
+ }
+ }
+
+ /**
+ * Wrapper for the standard file_exists function
+ *
+ * @param string $file File path
+ *
+ * @return boolean True if path is a file
+ *
+ * @since 1.7.0
+ */
+ public static function exists($file)
+ {
+ return is_file(Path::clean($file));
+ }
}
diff --git a/libraries/src/Filesystem/FilesystemHelper.php b/libraries/src/Filesystem/FilesystemHelper.php
index e1f16db52d371..a9ecbc8c8bd22 100644
--- a/libraries/src/Filesystem/FilesystemHelper.php
+++ b/libraries/src/Filesystem/FilesystemHelper.php
@@ -1,4 +1,5 @@
isFile() || $file->getExtension() !== 'php')
- {
- continue;
- }
-
- $streams[] = str_replace('stream', '', strtolower($file->getBasename('.php')));
- }
- }
-
- return $streams;
- }
-
- /**
- * Determine if a stream is a Joomla stream.
- *
- * @param string $streamname The name of a stream
- *
- * @return boolean True for a Joomla Stream
- *
- * @since 1.7.0
- */
- public static function isJoomlaStream($streamname)
- {
- return \in_array($streamname, self::getJStreams());
- }
-
- /**
- * Calculates the maximum upload file size and returns string with unit or the size in bytes
- *
- * Call it with JFilesystemHelper::fileUploadMaxSize();
- *
- * @param bool $unitOutput This parameter determines whether the return value should be a string with a unit
- *
- * @return float|string The maximum upload size of files with the appropriate unit or in bytes
- *
- * @since 3.4
- */
- public static function fileUploadMaxSize($unitOutput = true)
- {
- static $max_size = false;
- static $output_type = true;
-
- if ($max_size === false || $output_type != $unitOutput)
- {
- $max_size = self::parseSize(ini_get('post_max_size'));
- $upload_max = self::parseSize(ini_get('upload_max_filesize'));
-
- if ($upload_max > 0 && ($upload_max < $max_size || $max_size == 0))
- {
- $max_size = $upload_max;
- }
-
- if ($unitOutput == true)
- {
- $max_size = self::parseSizeUnit($max_size);
- }
-
- $output_type = $unitOutput;
- }
-
- return $max_size;
- }
-
- /**
- * Returns the size in bytes without the unit for the comparison
- *
- * @param string $size The size which is received from the PHP settings
- *
- * @return float The size in bytes without the unit
- *
- * @since 3.4
- */
- private static function parseSize($size)
- {
- $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
- $size = preg_replace('/[^0-9\.]/', '', $size);
-
- $return = round($size);
-
- if ($unit)
- {
- $return = round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
- }
-
- return $return;
- }
-
- /**
- * Creates the rounded size of the size with the appropriate unit
- *
- * @param float $maxSize The maximum size which is allowed for the uploads
- *
- * @return string String with the size and the appropriate unit
- *
- * @since 3.4
- */
- private static function parseSizeUnit($maxSize)
- {
- $base = log($maxSize) / log(1024);
- $suffixes = array('', 'k', 'M', 'G', 'T');
-
- return round(pow(1024, $base - floor($base)), 0) . $suffixes[floor($base)];
- }
+ /**
+ * Remote file size function for streams that don't support it
+ *
+ * @param string $url Link identifier
+ *
+ * @return mixed
+ *
+ * @link https://www.php.net/manual/en/function.filesize.php
+ * @since 1.7.0
+ */
+ public static function remotefsize($url)
+ {
+ $sch = parse_url($url, PHP_URL_SCHEME);
+
+ if (($sch !== 'http') && ($sch !== 'https') && ($sch !== 'ftp') && ($sch !== 'ftps')) {
+ return false;
+ }
+
+ if (($sch === 'http') || ($sch === 'https')) {
+ $headers = get_headers($url, 1);
+
+ if ((!\array_key_exists('Content-Length', $headers))) {
+ return false;
+ }
+
+ return $headers['Content-Length'];
+ }
+
+ if (($sch === 'ftp') || ($sch === 'ftps')) {
+ $server = parse_url($url, PHP_URL_HOST);
+ $port = parse_url($url, PHP_URL_PORT);
+ $path = parse_url($url, PHP_URL_PATH);
+ $user = parse_url($url, PHP_URL_USER);
+ $pass = parse_url($url, PHP_URL_PASS);
+
+ if ((!$server) || (!$path)) {
+ return false;
+ }
+
+ if (!$port) {
+ $port = 21;
+ }
+
+ if (!$user) {
+ $user = 'anonymous';
+ }
+
+ if (!$pass) {
+ $pass = '';
+ }
+
+ switch ($sch) {
+ case 'ftp':
+ $ftpid = ftp_connect($server, $port);
+ break;
+
+ case 'ftps':
+ $ftpid = ftp_ssl_connect($server, $port);
+ break;
+ }
+
+ if (!$ftpid) {
+ return false;
+ }
+
+ $login = ftp_login($ftpid, $user, $pass);
+
+ if (!$login) {
+ return false;
+ }
+
+ $ftpsize = ftp_size($ftpid, $path);
+ ftp_close($ftpid);
+
+ if ($ftpsize == -1) {
+ return false;
+ }
+
+ return $ftpsize;
+ }
+ }
+
+ /**
+ * Quick FTP chmod
+ *
+ * @param string $url Link identifier
+ * @param integer $mode The new permissions, given as an octal value.
+ *
+ * @return mixed
+ *
+ * @link https://www.php.net/manual/en/function.ftp-chmod.php
+ * @since 1.7.0
+ */
+ public static function ftpChmod($url, $mode)
+ {
+ $sch = parse_url($url, PHP_URL_SCHEME);
+
+ if (($sch !== 'ftp') && ($sch !== 'ftps')) {
+ return false;
+ }
+
+ $server = parse_url($url, PHP_URL_HOST);
+ $port = parse_url($url, PHP_URL_PORT);
+ $path = parse_url($url, PHP_URL_PATH);
+ $user = parse_url($url, PHP_URL_USER);
+ $pass = parse_url($url, PHP_URL_PASS);
+
+ if ((!$server) || (!$path)) {
+ return false;
+ }
+
+ if (!$port) {
+ $port = 21;
+ }
+
+ if (!$user) {
+ $user = 'anonymous';
+ }
+
+ if (!$pass) {
+ $pass = '';
+ }
+
+ switch ($sch) {
+ case 'ftp':
+ $ftpid = ftp_connect($server, $port);
+ break;
+
+ case 'ftps':
+ $ftpid = ftp_ssl_connect($server, $port);
+ break;
+ }
+
+ if (!$ftpid) {
+ return false;
+ }
+
+ $login = ftp_login($ftpid, $user, $pass);
+
+ if (!$login) {
+ return false;
+ }
+
+ $res = ftp_chmod($ftpid, $mode, $path);
+ ftp_close($ftpid);
+
+ return $res;
+ }
+
+ /**
+ * Modes that require a write operation
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public static function getWriteModes()
+ {
+ return array('w', 'w+', 'a', 'a+', 'r+', 'x', 'x+');
+ }
+
+ /**
+ * Stream and Filter Support Operations
+ *
+ * Returns the supported streams, in addition to direct file access
+ * Also includes Joomla! streams as well as PHP streams
+ *
+ * @return array Streams
+ *
+ * @since 1.7.0
+ */
+ public static function getSupported()
+ {
+ // Really quite cool what php can do with arrays when you let it...
+ static $streams;
+
+ if (!$streams) {
+ $streams = array_merge(stream_get_wrappers(), self::getJStreams());
+ }
+
+ return $streams;
+ }
+
+ /**
+ * Returns a list of transports
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public static function getTransports()
+ {
+ // Is this overkill?
+ return stream_get_transports();
+ }
+
+ /**
+ * Returns a list of filters
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public static function getFilters()
+ {
+ // Note: This will look like the getSupported() function with J! filters.
+ // @todo: add user space filter loading like user space stream loading
+ return stream_get_filters();
+ }
+
+ /**
+ * Returns a list of J! streams
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ public static function getJStreams()
+ {
+ static $streams = array();
+
+ if (!$streams) {
+ $files = new \DirectoryIterator(__DIR__ . '/Streams');
+
+ /** @var $file \DirectoryIterator */
+ foreach ($files as $file) {
+ // Only load for php files.
+ if (!$file->isFile() || $file->getExtension() !== 'php') {
+ continue;
+ }
+
+ $streams[] = str_replace('stream', '', strtolower($file->getBasename('.php')));
+ }
+ }
+
+ return $streams;
+ }
+
+ /**
+ * Determine if a stream is a Joomla stream.
+ *
+ * @param string $streamname The name of a stream
+ *
+ * @return boolean True for a Joomla Stream
+ *
+ * @since 1.7.0
+ */
+ public static function isJoomlaStream($streamname)
+ {
+ return \in_array($streamname, self::getJStreams());
+ }
+
+ /**
+ * Calculates the maximum upload file size and returns string with unit or the size in bytes
+ *
+ * Call it with JFilesystemHelper::fileUploadMaxSize();
+ *
+ * @param bool $unitOutput This parameter determines whether the return value should be a string with a unit
+ *
+ * @return float|string The maximum upload size of files with the appropriate unit or in bytes
+ *
+ * @since 3.4
+ */
+ public static function fileUploadMaxSize($unitOutput = true)
+ {
+ static $max_size = false;
+ static $output_type = true;
+
+ if ($max_size === false || $output_type != $unitOutput) {
+ $max_size = self::parseSize(ini_get('post_max_size'));
+ $upload_max = self::parseSize(ini_get('upload_max_filesize'));
+
+ if ($upload_max > 0 && ($upload_max < $max_size || $max_size == 0)) {
+ $max_size = $upload_max;
+ }
+
+ if ($unitOutput == true) {
+ $max_size = self::parseSizeUnit($max_size);
+ }
+
+ $output_type = $unitOutput;
+ }
+
+ return $max_size;
+ }
+
+ /**
+ * Returns the size in bytes without the unit for the comparison
+ *
+ * @param string $size The size which is received from the PHP settings
+ *
+ * @return float The size in bytes without the unit
+ *
+ * @since 3.4
+ */
+ private static function parseSize($size)
+ {
+ $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
+ $size = preg_replace('/[^0-9\.]/', '', $size);
+
+ $return = round($size);
+
+ if ($unit) {
+ $return = round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
+ }
+
+ return $return;
+ }
+
+ /**
+ * Creates the rounded size of the size with the appropriate unit
+ *
+ * @param float $maxSize The maximum size which is allowed for the uploads
+ *
+ * @return string String with the size and the appropriate unit
+ *
+ * @since 3.4
+ */
+ private static function parseSizeUnit($maxSize)
+ {
+ $base = log($maxSize) / log(1024);
+ $suffixes = array('', 'k', 'M', 'G', 'T');
+
+ return round(pow(1024, $base - floor($base)), 0) . $suffixes[floor($base)];
+ }
}
diff --git a/libraries/src/Filesystem/Folder.php b/libraries/src/Filesystem/Folder.php
index 86da3ba25f78a..998aacdf84329 100644
--- a/libraries/src/Filesystem/Folder.php
+++ b/libraries/src/Filesystem/Folder.php
@@ -1,4 +1,5 @@
store($sfid, $dfid))
- {
- throw new \RuntimeException('Copy file failed', -1);
- }
- break;
- }
- }
- }
- else
- {
- if (!($dh = @opendir($src)))
- {
- throw new \RuntimeException('Cannot open source folder', -1);
- }
-
- // Walk through the directory copying files and recursing into folders.
- while (($file = readdir($dh)) !== false)
- {
- $sfid = $src . '/' . $file;
- $dfid = $dest . '/' . $file;
-
- switch (filetype($sfid))
- {
- case 'dir':
- if ($file != '.' && $file != '..')
- {
- $ret = self::copy($sfid, $dfid, null, $force, $useStreams);
-
- if ($ret !== true)
- {
- return $ret;
- }
- }
- break;
-
- case 'file':
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- if (!$stream->copy($sfid, $dfid))
- {
- throw new \RuntimeException(
- sprintf(
- "Cannot copy file: %s",
- Path::removeRoot($stream->getError())
- ),
- -1
- );
- }
- }
- else
- {
- if (!@copy($sfid, $dfid))
- {
- throw new \RuntimeException('Copy file failed', -1);
- }
- }
- break;
- }
- }
- }
-
- return true;
- }
-
- /**
- * Create a folder -- and all necessary parent folders.
- *
- * @param string $path A path to create from the base path.
- * @param integer $mode Directory permissions to set for folders created. 0755 by default.
- *
- * @return boolean True if successful.
- *
- * @since 1.7.0
- */
- public static function create($path = '', $mode = 0755)
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
- static $nested = 0;
-
- // Check to make sure the path valid and clean
- $path = Path::clean($path);
-
- // Check if parent dir exists
- $parent = \dirname($path);
-
- if (!self::exists($parent))
- {
- // Prevent infinite loops!
- $nested++;
-
- if (($nested > 20) || ($parent == $path))
- {
- Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING, 'jerror');
- $nested--;
-
- return false;
- }
-
- // Create the parent directory
- if (self::create($parent, $mode) !== true)
- {
- // Folder::create throws an error
- $nested--;
-
- return false;
- }
-
- // OK, parent directory has been created
- $nested--;
- }
-
- // Check if dir already exists
- if (self::exists($path))
- {
- return true;
- }
-
- // Check for safe mode
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path to FTP path
- $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
- $ret = $ftp->mkdir($path);
- $ftp->chmod($path, $mode);
- }
- else
- {
- // We need to get and explode the open_basedir paths
- $obd = ini_get('open_basedir');
-
- // If open_basedir is set we need to get the open_basedir that the path is in
- if ($obd != null)
- {
- if (IS_WIN)
- {
- $obdSeparator = ';';
- }
- else
- {
- $obdSeparator = ':';
- }
-
- // Create the array of open_basedir paths
- $obdArray = explode($obdSeparator, $obd);
- $inBaseDir = false;
-
- // Iterate through open_basedir paths looking for a match
- foreach ($obdArray as $test)
- {
- $test = Path::clean($test);
-
- if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0)
- {
- $inBaseDir = true;
- break;
- }
- }
-
- if ($inBaseDir == false)
- {
- // Return false for JFolder::create because the path to be created is not in open_basedir
- Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING, 'jerror');
-
- return false;
- }
- }
-
- // First set umask
- $origmask = @umask(0);
-
- // Create the path
- if (!$ret = @mkdir($path, $mode))
- {
- @umask($origmask);
- Log::add(
- __METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path, Log::WARNING, 'jerror'
- );
-
- return false;
- }
-
- // Reset umask
- @umask($origmask);
- }
-
- return $ret;
- }
-
- /**
- * Delete a folder.
- *
- * @param string $path The path to the folder to delete.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public static function delete($path)
- {
- @set_time_limit(ini_get('max_execution_time'));
-
- // Sanity check
- if (!$path)
- {
- // Bad programmer! Bad Bad programmer!
- Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');
-
- return false;
- }
-
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- // Check to make sure the path valid and clean
- $path = Path::clean($path);
-
- // Is this really a folder?
- if (!is_dir($path))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
-
- return false;
- }
-
- // Remove all the files in folder if they exist; disable all filtering
- $files = self::files($path, '.', false, true, array(), array());
-
- if (!empty($files))
- {
- if (File::delete($files) !== true)
- {
- // File::delete throws an error
- return false;
- }
- }
-
- // Remove sub-folders of folder; disable all filtering
- $folders = self::folders($path, '.', false, true, array(), array());
-
- foreach ($folders as $folder)
- {
- if (is_link($folder))
- {
- // Don't descend into linked directories, just delete the link.
- if (File::delete($folder) !== true)
- {
- // File::delete throws an error
- return false;
- }
- }
- elseif (self::delete($folder) !== true)
- {
- // Folder::delete throws an error
- return false;
- }
- }
-
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
- }
-
- // In case of restricted permissions we zap it one way or the other
- // as long as the owner is either the webserver or the ftp.
- if (@rmdir($path))
- {
- $ret = true;
- }
- elseif ($FTPOptions['enabled'] == 1)
- {
- // Translate path and delete
- $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
-
- // FTP connector throws an error
- $ret = $ftp->delete($path);
- }
- else
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
- $ret = false;
- }
-
- return $ret;
- }
-
- /**
- * Moves a folder.
- *
- * @param string $src The path to the source folder.
- * @param string $dest The path to the destination folder.
- * @param string $path An optional base path to prefix to the file names.
- * @param boolean $useStreams Optionally use streams.
- *
- * @return mixed Error message on false or boolean true on success.
- *
- * @since 1.7.0
- */
- public static function move($src, $dest, $path = '', $useStreams = false)
- {
- $FTPOptions = ClientHelper::getCredentials('ftp');
-
- if ($path)
- {
- $src = Path::clean($path . '/' . $src);
- $dest = Path::clean($path . '/' . $dest);
- }
-
- if (!self::exists($src))
- {
- return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
- }
-
- if (self::exists($dest))
- {
- return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
- }
-
- if ($useStreams)
- {
- $stream = Factory::getStream();
-
- if (!$stream->move($src, $dest))
- {
- return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
- }
-
- $ret = true;
- }
- else
- {
- if ($FTPOptions['enabled'] == 1)
- {
- // Connect the FTP client
- $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
-
- // Translate path for the FTP account
- $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
- $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
-
- // Use FTP rename to simulate move
- if (!$ftp->rename($src, $dest))
- {
- return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
- }
-
- $ret = true;
- }
- else
- {
- if (!@rename($src, $dest))
- {
- return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
- }
-
- $ret = true;
- }
- }
-
- return $ret;
- }
-
- /**
- * Wrapper for the standard file_exists function
- *
- * @param string $path Folder name relative to installation dir
- *
- * @return boolean True if path is a folder
- *
- * @since 1.7.0
- */
- public static function exists($path)
- {
- return is_dir(Path::clean($path));
- }
-
- /**
- * Utility function to read the files in a folder.
- *
- * @param string $path The path of the folder to read.
- * @param string $filter A filter for file names.
- * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
- * @param boolean $full True to return the full path to the file.
- * @param array $exclude Array with names of files which should not be shown in the result.
- * @param array $excludeFilter Array of filter to exclude
- * @param boolean $naturalSort False for asort, true for natsort
- *
- * @return array|boolean Files in the given folder.
- *
- * @since 1.7.0
- */
- public static function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
- $excludeFilter = array('^\..*', '.*~'), $naturalSort = false
- )
- {
- // Check to make sure the path valid and clean
- $path = Path::clean($path);
-
- // Is the path a folder?
- if (!is_dir($path))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
-
- return false;
- }
-
- // Compute the excludefilter string
- if (\count($excludeFilter))
- {
- $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
- }
- else
- {
- $excludeFilterString = '';
- }
-
- // Get the files
- $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true);
-
- // Sort the files based on either natural or alpha method
- if ($naturalSort)
- {
- natsort($arr);
- }
- else
- {
- asort($arr);
- }
-
- return array_values($arr);
- }
-
- /**
- * Utility function to read the folders in a folder.
- *
- * @param string $path The path of the folder to read.
- * @param string $filter A filter for folder names.
- * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
- * @param boolean $full True to return the full path to the folders.
- * @param array $exclude Array with names of folders which should not be shown in the result.
- * @param array $excludeFilter Array with regular expressions matching folders which should not be shown in the result.
- *
- * @return array Folders in the given folder.
- *
- * @since 1.7.0
- */
- public static function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
- $excludeFilter = array('^\..*')
- )
- {
- // Check to make sure the path valid and clean
- $path = Path::clean($path);
-
- // Is the path a folder?
- if (!is_dir($path))
- {
- Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
-
- return false;
- }
-
- // Compute the excludefilter string
- if (\count($excludeFilter))
- {
- $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
- }
- else
- {
- $excludeFilterString = '';
- }
-
- // Get the folders
- $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false);
-
- // Sort the folders
- asort($arr);
-
- return array_values($arr);
- }
-
- /**
- * Function to read the files/folders in a folder.
- *
- * @param string $path The path of the folder to read.
- * @param string $filter A filter for file names.
- * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
- * @param boolean $full True to return the full path to the file.
- * @param array $exclude Array with names of files which should not be shown in the result.
- * @param string $excludeFilterString Regexp of files to exclude
- * @param boolean $findFiles True to read the files, false to read the folders
- *
- * @return array Files.
- *
- * @since 1.7.0
- */
- protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles)
- {
- @set_time_limit(ini_get('max_execution_time'));
-
- $arr = array();
-
- // Read the source directory
- if (!($handle = @opendir($path)))
- {
- return $arr;
- }
-
- while (($file = readdir($handle)) !== false)
- {
- if ($file != '.' && $file != '..' && !\in_array($file, $exclude)
- && (empty($excludeFilterString) || !preg_match($excludeFilterString, $file)))
- {
- // Compute the fullpath
- $fullpath = $path . '/' . $file;
-
- // Compute the isDir flag
- $isDir = is_dir($fullpath);
-
- if (($isDir xor $findFiles) && preg_match("/$filter/", $file))
- {
- // (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
- if ($full)
- {
- // Full path is requested
- $arr[] = $fullpath;
- }
- else
- {
- // Filename is requested
- $arr[] = $file;
- }
- }
-
- if ($isDir && $recurse)
- {
- // Search recursively
- if (\is_int($recurse))
- {
- // Until depth 0 is reached
- $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findFiles));
- }
- else
- {
- $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles));
- }
- }
- }
- }
-
- closedir($handle);
-
- return $arr;
- }
-
- /**
- * Lists folder in format suitable for tree display.
- *
- * @param string $path The path of the folder to read.
- * @param string $filter A filter for folder names.
- * @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
- * @param integer $level The current level, optional.
- * @param integer $parent Unique identifier of the parent folder, if any.
- *
- * @return array Folders in the given folder.
- *
- * @since 1.7.0
- */
- public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
- {
- $dirs = array();
-
- if ($level == 0)
- {
- $GLOBALS['_JFolder_folder_tree_index'] = 0;
- }
-
- if ($level < $maxLevel)
- {
- $folders = self::folders($path, $filter);
-
- // First path, index foldernames
- foreach ($folders as $name)
- {
- $id = ++$GLOBALS['_JFolder_folder_tree_index'];
- $fullName = Path::clean($path . '/' . $name);
- $dirs[] = array(
- 'id' => $id,
- 'parent' => $parent,
- 'name' => $name,
- 'fullname' => $fullName,
- 'relname' => str_replace(JPATH_ROOT, '', $fullName),
- );
- $dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
- $dirs = array_merge($dirs, $dirs2);
- }
- }
-
- return $dirs;
- }
-
- /**
- * Makes path name safe to use.
- *
- * @param string $path The full path to sanitise.
- *
- * @return string The sanitised string.
- *
- * @since 1.7.0
- */
- public static function makeSafe($path)
- {
- $regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');
-
- return preg_replace($regex, '', $path);
- }
+ /**
+ * Copy a folder.
+ *
+ * @param string $src The path to the source folder.
+ * @param string $dest The path to the destination folder.
+ * @param string $path An optional base path to prefix to the file names.
+ * @param boolean $force Force copy.
+ * @param boolean $useStreams Optionally force folder/file overwrites.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @throws \RuntimeException
+ */
+ public static function copy($src, $dest, $path = '', $force = false, $useStreams = false)
+ {
+ @set_time_limit(ini_get('max_execution_time'));
+
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if ($path) {
+ $src = Path::clean($path . '/' . $src);
+ $dest = Path::clean($path . '/' . $dest);
+ }
+
+ // Eliminate trailing directory separators, if any
+ $src = rtrim($src, DIRECTORY_SEPARATOR);
+ $dest = rtrim($dest, DIRECTORY_SEPARATOR);
+
+ if (!self::exists($src)) {
+ throw new \RuntimeException('Source folder not found', -1);
+ }
+
+ if (self::exists($dest) && !$force) {
+ throw new \RuntimeException('Destination folder already exists', -1);
+ }
+
+ // Make sure the destination exists
+ if (!self::create($dest)) {
+ throw new \RuntimeException('Cannot create destination folder', -1);
+ }
+
+ // If we're using ftp and don't have streams enabled
+ if ($FTPOptions['enabled'] == 1 && !$useStreams) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ if (!($dh = @opendir($src))) {
+ throw new \RuntimeException('Cannot open source folder', -1);
+ }
+
+ // Walk through the directory copying files and recursing into folders.
+ while (($file = readdir($dh)) !== false) {
+ $sfid = $src . '/' . $file;
+ $dfid = $dest . '/' . $file;
+
+ switch (filetype($sfid)) {
+ case 'dir':
+ if ($file != '.' && $file != '..') {
+ $ret = self::copy($sfid, $dfid, null, $force);
+
+ if ($ret !== true) {
+ return $ret;
+ }
+ }
+ break;
+
+ case 'file':
+ // Translate path for the FTP account
+ $dfid = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/');
+
+ if (!$ftp->store($sfid, $dfid)) {
+ throw new \RuntimeException('Copy file failed', -1);
+ }
+ break;
+ }
+ }
+ } else {
+ if (!($dh = @opendir($src))) {
+ throw new \RuntimeException('Cannot open source folder', -1);
+ }
+
+ // Walk through the directory copying files and recursing into folders.
+ while (($file = readdir($dh)) !== false) {
+ $sfid = $src . '/' . $file;
+ $dfid = $dest . '/' . $file;
+
+ switch (filetype($sfid)) {
+ case 'dir':
+ if ($file != '.' && $file != '..') {
+ $ret = self::copy($sfid, $dfid, null, $force, $useStreams);
+
+ if ($ret !== true) {
+ return $ret;
+ }
+ }
+ break;
+
+ case 'file':
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ if (!$stream->copy($sfid, $dfid)) {
+ throw new \RuntimeException(
+ sprintf(
+ "Cannot copy file: %s",
+ Path::removeRoot($stream->getError())
+ ),
+ -1
+ );
+ }
+ } else {
+ if (!@copy($sfid, $dfid)) {
+ throw new \RuntimeException('Copy file failed', -1);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a folder -- and all necessary parent folders.
+ *
+ * @param string $path A path to create from the base path.
+ * @param integer $mode Directory permissions to set for folders created. 0755 by default.
+ *
+ * @return boolean True if successful.
+ *
+ * @since 1.7.0
+ */
+ public static function create($path = '', $mode = 0755)
+ {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+ static $nested = 0;
+
+ // Check to make sure the path valid and clean
+ $path = Path::clean($path);
+
+ // Check if parent dir exists
+ $parent = \dirname($path);
+
+ if (!self::exists($parent)) {
+ // Prevent infinite loops!
+ $nested++;
+
+ if (($nested > 20) || ($parent == $path)) {
+ Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING, 'jerror');
+ $nested--;
+
+ return false;
+ }
+
+ // Create the parent directory
+ if (self::create($parent, $mode) !== true) {
+ // Folder::create throws an error
+ $nested--;
+
+ return false;
+ }
+
+ // OK, parent directory has been created
+ $nested--;
+ }
+
+ // Check if dir already exists
+ if (self::exists($path)) {
+ return true;
+ }
+
+ // Check for safe mode
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path to FTP path
+ $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
+ $ret = $ftp->mkdir($path);
+ $ftp->chmod($path, $mode);
+ } else {
+ // We need to get and explode the open_basedir paths
+ $obd = ini_get('open_basedir');
+
+ // If open_basedir is set we need to get the open_basedir that the path is in
+ if ($obd != null) {
+ if (IS_WIN) {
+ $obdSeparator = ';';
+ } else {
+ $obdSeparator = ':';
+ }
+
+ // Create the array of open_basedir paths
+ $obdArray = explode($obdSeparator, $obd);
+ $inBaseDir = false;
+
+ // Iterate through open_basedir paths looking for a match
+ foreach ($obdArray as $test) {
+ $test = Path::clean($test);
+
+ if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0) {
+ $inBaseDir = true;
+ break;
+ }
+ }
+
+ if ($inBaseDir == false) {
+ // Return false for JFolder::create because the path to be created is not in open_basedir
+ Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+ }
+
+ // First set umask
+ $origmask = @umask(0);
+
+ // Create the path
+ if (!$ret = @mkdir($path, $mode)) {
+ @umask($origmask);
+ Log::add(
+ __METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path,
+ Log::WARNING,
+ 'jerror'
+ );
+
+ return false;
+ }
+
+ // Reset umask
+ @umask($origmask);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Delete a folder.
+ *
+ * @param string $path The path to the folder to delete.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public static function delete($path)
+ {
+ @set_time_limit(ini_get('max_execution_time'));
+
+ // Sanity check
+ if (!$path) {
+ // Bad programmer! Bad Bad programmer!
+ Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ // Check to make sure the path valid and clean
+ $path = Path::clean($path);
+
+ // Is this really a folder?
+ if (!is_dir($path)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ // Remove all the files in folder if they exist; disable all filtering
+ $files = self::files($path, '.', false, true, array(), array());
+
+ if (!empty($files)) {
+ if (File::delete($files) !== true) {
+ // File::delete throws an error
+ return false;
+ }
+ }
+
+ // Remove sub-folders of folder; disable all filtering
+ $folders = self::folders($path, '.', false, true, array(), array());
+
+ foreach ($folders as $folder) {
+ if (is_link($folder)) {
+ // Don't descend into linked directories, just delete the link.
+ if (File::delete($folder) !== true) {
+ // File::delete throws an error
+ return false;
+ }
+ } elseif (self::delete($folder) !== true) {
+ // Folder::delete throws an error
+ return false;
+ }
+ }
+
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+ }
+
+ // In case of restricted permissions we zap it one way or the other
+ // as long as the owner is either the webserver or the ftp.
+ if (@rmdir($path)) {
+ $ret = true;
+ } elseif ($FTPOptions['enabled'] == 1) {
+ // Translate path and delete
+ $path = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
+
+ // FTP connector throws an error
+ $ret = $ftp->delete($path);
+ } else {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
+ $ret = false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Moves a folder.
+ *
+ * @param string $src The path to the source folder.
+ * @param string $dest The path to the destination folder.
+ * @param string $path An optional base path to prefix to the file names.
+ * @param boolean $useStreams Optionally use streams.
+ *
+ * @return mixed Error message on false or boolean true on success.
+ *
+ * @since 1.7.0
+ */
+ public static function move($src, $dest, $path = '', $useStreams = false)
+ {
+ $FTPOptions = ClientHelper::getCredentials('ftp');
+
+ if ($path) {
+ $src = Path::clean($path . '/' . $src);
+ $dest = Path::clean($path . '/' . $dest);
+ }
+
+ if (!self::exists($src)) {
+ return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
+ }
+
+ if (self::exists($dest)) {
+ return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
+ }
+
+ if ($useStreams) {
+ $stream = Factory::getStream();
+
+ if (!$stream->move($src, $dest)) {
+ return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
+ }
+
+ $ret = true;
+ } else {
+ if ($FTPOptions['enabled'] == 1) {
+ // Connect the FTP client
+ $ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
+
+ // Translate path for the FTP account
+ $src = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
+ $dest = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
+
+ // Use FTP rename to simulate move
+ if (!$ftp->rename($src, $dest)) {
+ return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
+ }
+
+ $ret = true;
+ } else {
+ if (!@rename($src, $dest)) {
+ return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
+ }
+
+ $ret = true;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Wrapper for the standard file_exists function
+ *
+ * @param string $path Folder name relative to installation dir
+ *
+ * @return boolean True if path is a folder
+ *
+ * @since 1.7.0
+ */
+ public static function exists($path)
+ {
+ return is_dir(Path::clean($path));
+ }
+
+ /**
+ * Utility function to read the files in a folder.
+ *
+ * @param string $path The path of the folder to read.
+ * @param string $filter A filter for file names.
+ * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
+ * @param boolean $full True to return the full path to the file.
+ * @param array $exclude Array with names of files which should not be shown in the result.
+ * @param array $excludeFilter Array of filter to exclude
+ * @param boolean $naturalSort False for asort, true for natsort
+ *
+ * @return array|boolean Files in the given folder.
+ *
+ * @since 1.7.0
+ */
+ public static function files(
+ $path,
+ $filter = '.',
+ $recurse = false,
+ $full = false,
+ $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
+ $excludeFilter = array('^\..*', '.*~'),
+ $naturalSort = false
+ ) {
+ // Check to make sure the path valid and clean
+ $path = Path::clean($path);
+
+ // Is the path a folder?
+ if (!is_dir($path)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ // Compute the excludefilter string
+ if (\count($excludeFilter)) {
+ $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
+ } else {
+ $excludeFilterString = '';
+ }
+
+ // Get the files
+ $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true);
+
+ // Sort the files based on either natural or alpha method
+ if ($naturalSort) {
+ natsort($arr);
+ } else {
+ asort($arr);
+ }
+
+ return array_values($arr);
+ }
+
+ /**
+ * Utility function to read the folders in a folder.
+ *
+ * @param string $path The path of the folder to read.
+ * @param string $filter A filter for folder names.
+ * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
+ * @param boolean $full True to return the full path to the folders.
+ * @param array $exclude Array with names of folders which should not be shown in the result.
+ * @param array $excludeFilter Array with regular expressions matching folders which should not be shown in the result.
+ *
+ * @return array Folders in the given folder.
+ *
+ * @since 1.7.0
+ */
+ public static function folders(
+ $path,
+ $filter = '.',
+ $recurse = false,
+ $full = false,
+ $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
+ $excludeFilter = array('^\..*')
+ ) {
+ // Check to make sure the path valid and clean
+ $path = Path::clean($path);
+
+ // Is the path a folder?
+ if (!is_dir($path)) {
+ Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', __METHOD__, $path), Log::WARNING, 'jerror');
+
+ return false;
+ }
+
+ // Compute the excludefilter string
+ if (\count($excludeFilter)) {
+ $excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
+ } else {
+ $excludeFilterString = '';
+ }
+
+ // Get the folders
+ $arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false);
+
+ // Sort the folders
+ asort($arr);
+
+ return array_values($arr);
+ }
+
+ /**
+ * Function to read the files/folders in a folder.
+ *
+ * @param string $path The path of the folder to read.
+ * @param string $filter A filter for file names.
+ * @param mixed $recurse True to recursively search into sub-folders, or an integer to specify the maximum depth.
+ * @param boolean $full True to return the full path to the file.
+ * @param array $exclude Array with names of files which should not be shown in the result.
+ * @param string $excludeFilterString Regexp of files to exclude
+ * @param boolean $findFiles True to read the files, false to read the folders
+ *
+ * @return array Files.
+ *
+ * @since 1.7.0
+ */
+ protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles)
+ {
+ @set_time_limit(ini_get('max_execution_time'));
+
+ $arr = array();
+
+ // Read the source directory
+ if (!($handle = @opendir($path))) {
+ return $arr;
+ }
+
+ while (($file = readdir($handle)) !== false) {
+ if (
+ $file != '.' && $file != '..' && !\in_array($file, $exclude)
+ && (empty($excludeFilterString) || !preg_match($excludeFilterString, $file))
+ ) {
+ // Compute the fullpath
+ $fullpath = $path . '/' . $file;
+
+ // Compute the isDir flag
+ $isDir = is_dir($fullpath);
+
+ if (($isDir xor $findFiles) && preg_match("/$filter/", $file)) {
+ // (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
+ if ($full) {
+ // Full path is requested
+ $arr[] = $fullpath;
+ } else {
+ // Filename is requested
+ $arr[] = $file;
+ }
+ }
+
+ if ($isDir && $recurse) {
+ // Search recursively
+ if (\is_int($recurse)) {
+ // Until depth 0 is reached
+ $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findFiles));
+ } else {
+ $arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findFiles));
+ }
+ }
+ }
+ }
+
+ closedir($handle);
+
+ return $arr;
+ }
+
+ /**
+ * Lists folder in format suitable for tree display.
+ *
+ * @param string $path The path of the folder to read.
+ * @param string $filter A filter for folder names.
+ * @param integer $maxLevel The maximum number of levels to recursively read, defaults to three.
+ * @param integer $level The current level, optional.
+ * @param integer $parent Unique identifier of the parent folder, if any.
+ *
+ * @return array Folders in the given folder.
+ *
+ * @since 1.7.0
+ */
+ public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
+ {
+ $dirs = array();
+
+ if ($level == 0) {
+ $GLOBALS['_JFolder_folder_tree_index'] = 0;
+ }
+
+ if ($level < $maxLevel) {
+ $folders = self::folders($path, $filter);
+
+ // First path, index foldernames
+ foreach ($folders as $name) {
+ $id = ++$GLOBALS['_JFolder_folder_tree_index'];
+ $fullName = Path::clean($path . '/' . $name);
+ $dirs[] = array(
+ 'id' => $id,
+ 'parent' => $parent,
+ 'name' => $name,
+ 'fullname' => $fullName,
+ 'relname' => str_replace(JPATH_ROOT, '', $fullName),
+ );
+ $dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
+ $dirs = array_merge($dirs, $dirs2);
+ }
+ }
+
+ return $dirs;
+ }
+
+ /**
+ * Makes path name safe to use.
+ *
+ * @param string $path The full path to sanitise.
+ *
+ * @return string The sanitised string.
+ *
+ * @since 1.7.0
+ */
+ public static function makeSafe($path)
+ {
+ $regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');
+
+ return preg_replace($regex, '', $path);
+ }
}
diff --git a/libraries/src/Filesystem/Patcher.php b/libraries/src/Filesystem/Patcher.php
index d2b9819b08010..d2a141398ceeb 100644
--- a/libraries/src/Filesystem/Patcher.php
+++ b/libraries/src/Filesystem/Patcher.php
@@ -1,4 +1,5 @@
sources = array();
- $this->destinations = array();
- $this->removals = array();
- $this->patches = array();
-
- return $this;
- }
-
- /**
- * Apply the patches
- *
- * @return integer The number of files patched
- *
- * @since 3.0.0
- * @throws \RuntimeException
- */
- public function apply()
- {
- foreach ($this->patches as $patch)
- {
- // Separate the input into lines
- $lines = self::splitLines($patch['udiff']);
-
- // Loop for each header
- while (self::findHeader($lines, $src, $dst))
- {
- $done = false;
-
- $regex = '#^([^/]*/)*#';
-
- if ($patch['strip'] !== null)
- {
- $regex = '#^([^/]*/){' . (int) $patch['strip'] . '}#';
- }
-
- $src = $patch['root'] . preg_replace($regex, '', $src);
- $dst = $patch['root'] . preg_replace($regex, '', $dst);
-
- // Loop for each hunk of differences
- while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size))
- {
- $done = true;
-
- // Apply the hunk of differences
- $this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size);
- }
-
- // If no modifications were found, throw an exception
- if (!$done)
- {
- throw new \RuntimeException('Invalid Diff');
- }
- }
- }
-
- // Initialize the counter
- $done = 0;
-
- // Patch each destination file
- foreach ($this->destinations as $file => $content)
- {
- $buffer = implode("\n", $content);
-
- if (File::write($file, $buffer))
- {
- if (isset($this->sources[$file]))
- {
- $this->sources[$file] = $content;
- }
-
- $done++;
- }
- }
-
- // Remove each removed file
- foreach ($this->removals as $file)
- {
- if (File::delete($file))
- {
- if (isset($this->sources[$file]))
- {
- unset($this->sources[$file]);
- }
-
- $done++;
- }
- }
-
- // Clear the destinations cache
- $this->destinations = array();
-
- // Clear the removals
- $this->removals = array();
-
- // Clear the patches
- $this->patches = array();
-
- return $done;
- }
-
- /**
- * Add a unified diff file to the patcher
- *
- * @param string $filename Path to the unified diff file
- * @param string $root The files root path
- * @param integer $strip The number of '/' to strip
- *
- * @return Patcher $this for chaining
- *
- * @since 3.0.0
- */
- public function addFile($filename, $root = JPATH_BASE, $strip = 0)
- {
- return $this->add(file_get_contents($filename), $root, $strip);
- }
-
- /**
- * Add a unified diff string to the patcher
- *
- * @param string $udiff Unified diff input string
- * @param string $root The files root path
- * @param integer $strip The number of '/' to strip
- *
- * @return Patcher $this for chaining
- *
- * @since 3.0.0
- */
- public function add($udiff, $root = JPATH_BASE, $strip = 0)
- {
- $this->patches[] = array(
- 'udiff' => $udiff,
- 'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '',
- 'strip' => $strip,
- );
-
- return $this;
- }
-
- /**
- * Separate CR or CRLF lines
- *
- * @param string $data Input string
- *
- * @return array The lines of the inputdestination file
- *
- * @since 3.0.0
- */
- protected static function splitLines($data)
- {
- return preg_split(self::SPLIT, $data);
- }
-
- /**
- * Find the diff header
- *
- * The internal array pointer of $lines is on the next line after the finding
- *
- * @param array $lines The udiff array of lines
- * @param string $src The source file
- * @param string $dst The destination file
- *
- * @return boolean TRUE in case of success, FALSE in case of failure
- *
- * @since 3.0.0
- * @throws \RuntimeException
- */
- protected static function findHeader(&$lines, &$src, &$dst)
- {
- // Get the current line
- $line = current($lines);
-
- // Search for the header
- while ($line !== false && !preg_match(self::SRC_FILE, $line, $m))
- {
- $line = next($lines);
- }
-
- if ($line === false)
- {
- // No header found, return false
- return false;
- }
-
- // Set the source file
- $src = $m[1];
-
- // Advance to the next line
- $line = next($lines);
-
- if ($line === false)
- {
- throw new \RuntimeException('Unexpected EOF');
- }
-
- // Search the destination file
- if (!preg_match(self::DST_FILE, $line, $m))
- {
- throw new \RuntimeException('Invalid Diff file');
- }
-
- // Set the destination file
- $dst = $m[1];
-
- // Advance to the next line
- if (next($lines) === false)
- {
- throw new \RuntimeException('Unexpected EOF');
- }
-
- return true;
- }
-
- /**
- * Find the next hunk of difference
- *
- * The internal array pointer of $lines is on the next line after the finding
- *
- * @param array $lines The udiff array of lines
- * @param string $srcLine The beginning of the patch for the source file
- * @param string $srcSize The size of the patch for the source file
- * @param string $dstLine The beginning of the patch for the destination file
- * @param string $dstSize The size of the patch for the destination file
- *
- * @return boolean TRUE in case of success, false in case of failure
- *
- * @since 3.0.0
- * @throws \RuntimeException
- */
- protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize)
- {
- $line = current($lines);
-
- if (preg_match(self::HUNK, $line, $m))
- {
- $srcLine = (int) $m[1];
-
- $srcSize = 1;
-
- if ($m[3] !== '')
- {
- $srcSize = (int) $m[3];
- }
-
- $dstLine = (int) $m[4];
-
- $dstSize = 1;
-
- if ($m[6] !== '')
- {
- $dstSize = (int) $m[6];
- }
-
- if (next($lines) === false)
- {
- throw new \RuntimeException('Unexpected EOF');
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Apply the patch
- *
- * @param array $lines The udiff array of lines
- * @param string $src The source file
- * @param string $dst The destination file
- * @param string $srcLine The beginning of the patch for the source file
- * @param string $srcSize The size of the patch for the source file
- * @param string $dstLine The beginning of the patch for the destination file
- * @param string $dstSize The size of the patch for the destination file
- *
- * @return void
- *
- * @since 3.0.0
- * @throws \RuntimeException
- */
- protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize)
- {
- $srcLine--;
- $dstLine--;
- $line = current($lines);
-
- // Source lines (old file)
- $source = array();
-
- // New lines (new file)
- $destin = array();
- $src_left = $srcSize;
- $dst_left = $dstSize;
-
- do
- {
- if (!isset($line[0]))
- {
- $source[] = '';
- $destin[] = '';
- $src_left--;
- $dst_left--;
- }
- elseif ($line[0] == '-')
- {
- if ($src_left == 0)
- {
- throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE', key($lines)));
- }
-
- $source[] = substr($line, 1);
- $src_left--;
- }
- elseif ($line[0] == '+')
- {
- if ($dst_left == 0)
- {
- throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE', key($lines)));
- }
-
- $destin[] = substr($line, 1);
- $dst_left--;
- }
- elseif ($line != '\\ No newline at end of file')
- {
- $line = substr($line, 1);
- $source[] = $line;
- $destin[] = $line;
- $src_left--;
- $dst_left--;
- }
-
- if ($src_left == 0 && $dst_left == 0)
- {
- // Now apply the patch, finally!
- if ($srcSize > 0)
- {
- $src_lines = & $this->getSource($src);
-
- if (!isset($src_lines))
- {
- throw new \RuntimeException(
- Text::sprintf(
- 'JLIB_FILESYSTEM_PATCHER_UNEXISTING_SOURCE',
- Path::removeRoot($src)
- )
- );
- }
- }
-
- if ($dstSize > 0)
- {
- if ($srcSize > 0)
- {
- $dst_lines = & $this->getDestination($dst, $src);
- $src_bottom = $srcLine + \count($source);
-
- for ($l = $srcLine;$l < $src_bottom;$l++)
- {
- if ($src_lines[$l] != $source[$l - $srcLine])
- {
- throw new \RuntimeException(
- Text::sprintf(
- 'JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY',
- Path::removeRoot($src),
- $l
- )
- );
- }
- }
-
- array_splice($dst_lines, $dstLine, \count($source), $destin);
- }
- else
- {
- $this->destinations[$dst] = $destin;
- }
- }
- else
- {
- $this->removals[] = $src;
- }
-
- next($lines);
-
- return;
- }
-
- $line = next($lines);
- }
-
- while ($line !== false);
- throw new \RuntimeException('Unexpected EOF');
- }
-
- /**
- * Get the lines of a source file
- *
- * @param string $src The path of a file
- *
- * @return array The lines of the source file
- *
- * @since 3.0.0
- */
- protected function &getSource($src)
- {
- if (!isset($this->sources[$src]))
- {
- $this->sources[$src] = null;
-
- if (is_readable($src))
- {
- $this->sources[$src] = self::splitLines(file_get_contents($src));
- }
- }
-
- return $this->sources[$src];
- }
-
- /**
- * Get the lines of a destination file
- *
- * @param string $dst The path of a destination file
- * @param string $src The path of a source file
- *
- * @return array The lines of the destination file
- *
- * @since 3.0.0
- */
- protected function &getDestination($dst, $src)
- {
- if (!isset($this->destinations[$dst]))
- {
- $this->destinations[$dst] = $this->getSource($src);
- }
-
- return $this->destinations[$dst];
- }
+ /**
+ * Regular expression for searching source files
+ */
+ public const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
+
+ /**
+ * Regular expression for searching destination files
+ */
+ public const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';
+
+ /**
+ * Regular expression for searching hunks of differences
+ */
+ public const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';
+
+ /**
+ * Regular expression for splitting lines
+ */
+ public const SPLIT = '/(\r\n)|(\r)|(\n)/';
+
+ /**
+ * @var array sources files
+ * @since 3.0.0
+ */
+ protected $sources = array();
+
+ /**
+ * @var array destination files
+ * @since 3.0.0
+ */
+ protected $destinations = array();
+
+ /**
+ * @var array removal files
+ * @since 3.0.0
+ */
+ protected $removals = array();
+
+ /**
+ * @var array patches
+ * @since 3.0.0
+ */
+ protected $patches = array();
+
+ /**
+ * @var array instance of this class
+ * @since 3.0.0
+ */
+ protected static $instance;
+
+ /**
+ * Constructor
+ *
+ * The constructor is protected to force the use of FilesystemPatcher::getInstance()
+ *
+ * @since 3.0.0
+ */
+ protected function __construct()
+ {
+ }
+
+ /**
+ * Method to get a patcher
+ *
+ * @return Patcher an instance of the patcher
+ *
+ * @since 3.0.0
+ */
+ public static function getInstance()
+ {
+ if (!isset(static::$instance)) {
+ static::$instance = new static();
+ }
+
+ return static::$instance;
+ }
+
+ /**
+ * Reset the patcher
+ *
+ * @return Patcher This object for chaining
+ *
+ * @since 3.0.0
+ */
+ public function reset()
+ {
+ $this->sources = array();
+ $this->destinations = array();
+ $this->removals = array();
+ $this->patches = array();
+
+ return $this;
+ }
+
+ /**
+ * Apply the patches
+ *
+ * @return integer The number of files patched
+ *
+ * @since 3.0.0
+ * @throws \RuntimeException
+ */
+ public function apply()
+ {
+ foreach ($this->patches as $patch) {
+ // Separate the input into lines
+ $lines = self::splitLines($patch['udiff']);
+
+ // Loop for each header
+ while (self::findHeader($lines, $src, $dst)) {
+ $done = false;
+
+ $regex = '#^([^/]*/)*#';
+
+ if ($patch['strip'] !== null) {
+ $regex = '#^([^/]*/){' . (int) $patch['strip'] . '}#';
+ }
+
+ $src = $patch['root'] . preg_replace($regex, '', $src);
+ $dst = $patch['root'] . preg_replace($regex, '', $dst);
+
+ // Loop for each hunk of differences
+ while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size)) {
+ $done = true;
+
+ // Apply the hunk of differences
+ $this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size);
+ }
+
+ // If no modifications were found, throw an exception
+ if (!$done) {
+ throw new \RuntimeException('Invalid Diff');
+ }
+ }
+ }
+
+ // Initialize the counter
+ $done = 0;
+
+ // Patch each destination file
+ foreach ($this->destinations as $file => $content) {
+ $buffer = implode("\n", $content);
+
+ if (File::write($file, $buffer)) {
+ if (isset($this->sources[$file])) {
+ $this->sources[$file] = $content;
+ }
+
+ $done++;
+ }
+ }
+
+ // Remove each removed file
+ foreach ($this->removals as $file) {
+ if (File::delete($file)) {
+ if (isset($this->sources[$file])) {
+ unset($this->sources[$file]);
+ }
+
+ $done++;
+ }
+ }
+
+ // Clear the destinations cache
+ $this->destinations = array();
+
+ // Clear the removals
+ $this->removals = array();
+
+ // Clear the patches
+ $this->patches = array();
+
+ return $done;
+ }
+
+ /**
+ * Add a unified diff file to the patcher
+ *
+ * @param string $filename Path to the unified diff file
+ * @param string $root The files root path
+ * @param integer $strip The number of '/' to strip
+ *
+ * @return Patcher $this for chaining
+ *
+ * @since 3.0.0
+ */
+ public function addFile($filename, $root = JPATH_BASE, $strip = 0)
+ {
+ return $this->add(file_get_contents($filename), $root, $strip);
+ }
+
+ /**
+ * Add a unified diff string to the patcher
+ *
+ * @param string $udiff Unified diff input string
+ * @param string $root The files root path
+ * @param integer $strip The number of '/' to strip
+ *
+ * @return Patcher $this for chaining
+ *
+ * @since 3.0.0
+ */
+ public function add($udiff, $root = JPATH_BASE, $strip = 0)
+ {
+ $this->patches[] = array(
+ 'udiff' => $udiff,
+ 'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '',
+ 'strip' => $strip,
+ );
+
+ return $this;
+ }
+
+ /**
+ * Separate CR or CRLF lines
+ *
+ * @param string $data Input string
+ *
+ * @return array The lines of the inputdestination file
+ *
+ * @since 3.0.0
+ */
+ protected static function splitLines($data)
+ {
+ return preg_split(self::SPLIT, $data);
+ }
+
+ /**
+ * Find the diff header
+ *
+ * The internal array pointer of $lines is on the next line after the finding
+ *
+ * @param array $lines The udiff array of lines
+ * @param string $src The source file
+ * @param string $dst The destination file
+ *
+ * @return boolean TRUE in case of success, FALSE in case of failure
+ *
+ * @since 3.0.0
+ * @throws \RuntimeException
+ */
+ protected static function findHeader(&$lines, &$src, &$dst)
+ {
+ // Get the current line
+ $line = current($lines);
+
+ // Search for the header
+ while ($line !== false && !preg_match(self::SRC_FILE, $line, $m)) {
+ $line = next($lines);
+ }
+
+ if ($line === false) {
+ // No header found, return false
+ return false;
+ }
+
+ // Set the source file
+ $src = $m[1];
+
+ // Advance to the next line
+ $line = next($lines);
+
+ if ($line === false) {
+ throw new \RuntimeException('Unexpected EOF');
+ }
+
+ // Search the destination file
+ if (!preg_match(self::DST_FILE, $line, $m)) {
+ throw new \RuntimeException('Invalid Diff file');
+ }
+
+ // Set the destination file
+ $dst = $m[1];
+
+ // Advance to the next line
+ if (next($lines) === false) {
+ throw new \RuntimeException('Unexpected EOF');
+ }
+
+ return true;
+ }
+
+ /**
+ * Find the next hunk of difference
+ *
+ * The internal array pointer of $lines is on the next line after the finding
+ *
+ * @param array $lines The udiff array of lines
+ * @param string $srcLine The beginning of the patch for the source file
+ * @param string $srcSize The size of the patch for the source file
+ * @param string $dstLine The beginning of the patch for the destination file
+ * @param string $dstSize The size of the patch for the destination file
+ *
+ * @return boolean TRUE in case of success, false in case of failure
+ *
+ * @since 3.0.0
+ * @throws \RuntimeException
+ */
+ protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize)
+ {
+ $line = current($lines);
+
+ if (preg_match(self::HUNK, $line, $m)) {
+ $srcLine = (int) $m[1];
+
+ $srcSize = 1;
+
+ if ($m[3] !== '') {
+ $srcSize = (int) $m[3];
+ }
+
+ $dstLine = (int) $m[4];
+
+ $dstSize = 1;
+
+ if ($m[6] !== '') {
+ $dstSize = (int) $m[6];
+ }
+
+ if (next($lines) === false) {
+ throw new \RuntimeException('Unexpected EOF');
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Apply the patch
+ *
+ * @param array $lines The udiff array of lines
+ * @param string $src The source file
+ * @param string $dst The destination file
+ * @param string $srcLine The beginning of the patch for the source file
+ * @param string $srcSize The size of the patch for the source file
+ * @param string $dstLine The beginning of the patch for the destination file
+ * @param string $dstSize The size of the patch for the destination file
+ *
+ * @return void
+ *
+ * @since 3.0.0
+ * @throws \RuntimeException
+ */
+ protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize)
+ {
+ $srcLine--;
+ $dstLine--;
+ $line = current($lines);
+
+ // Source lines (old file)
+ $source = array();
+
+ // New lines (new file)
+ $destin = array();
+ $src_left = $srcSize;
+ $dst_left = $dstSize;
+
+ do {
+ if (!isset($line[0])) {
+ $source[] = '';
+ $destin[] = '';
+ $src_left--;
+ $dst_left--;
+ } elseif ($line[0] == '-') {
+ if ($src_left == 0) {
+ throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE', key($lines)));
+ }
+
+ $source[] = substr($line, 1);
+ $src_left--;
+ } elseif ($line[0] == '+') {
+ if ($dst_left == 0) {
+ throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE', key($lines)));
+ }
+
+ $destin[] = substr($line, 1);
+ $dst_left--;
+ } elseif ($line != '\\ No newline at end of file') {
+ $line = substr($line, 1);
+ $source[] = $line;
+ $destin[] = $line;
+ $src_left--;
+ $dst_left--;
+ }
+
+ if ($src_left == 0 && $dst_left == 0) {
+ // Now apply the patch, finally!
+ if ($srcSize > 0) {
+ $src_lines = & $this->getSource($src);
+
+ if (!isset($src_lines)) {
+ throw new \RuntimeException(
+ Text::sprintf(
+ 'JLIB_FILESYSTEM_PATCHER_UNEXISTING_SOURCE',
+ Path::removeRoot($src)
+ )
+ );
+ }
+ }
+
+ if ($dstSize > 0) {
+ if ($srcSize > 0) {
+ $dst_lines = & $this->getDestination($dst, $src);
+ $src_bottom = $srcLine + \count($source);
+
+ for ($l = $srcLine; $l < $src_bottom; $l++) {
+ if ($src_lines[$l] != $source[$l - $srcLine]) {
+ throw new \RuntimeException(
+ Text::sprintf(
+ 'JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY',
+ Path::removeRoot($src),
+ $l
+ )
+ );
+ }
+ }
+
+ array_splice($dst_lines, $dstLine, \count($source), $destin);
+ } else {
+ $this->destinations[$dst] = $destin;
+ }
+ } else {
+ $this->removals[] = $src;
+ }
+
+ next($lines);
+
+ return;
+ }
+
+ $line = next($lines);
+ } while ($line !== false);
+ throw new \RuntimeException('Unexpected EOF');
+ }
+
+ /**
+ * Get the lines of a source file
+ *
+ * @param string $src The path of a file
+ *
+ * @return array The lines of the source file
+ *
+ * @since 3.0.0
+ */
+ protected function &getSource($src)
+ {
+ if (!isset($this->sources[$src])) {
+ $this->sources[$src] = null;
+
+ if (is_readable($src)) {
+ $this->sources[$src] = self::splitLines(file_get_contents($src));
+ }
+ }
+
+ return $this->sources[$src];
+ }
+
+ /**
+ * Get the lines of a destination file
+ *
+ * @param string $dst The path of a destination file
+ * @param string $src The path of a source file
+ *
+ * @return array The lines of the destination file
+ *
+ * @since 3.0.0
+ */
+ protected function &getDestination($dst, $src)
+ {
+ if (!isset($this->destinations[$dst])) {
+ $this->destinations[$dst] = $this->getSource($src);
+ }
+
+ return $this->destinations[$dst];
+ }
}
diff --git a/libraries/src/Filesystem/Path.php b/libraries/src/Filesystem/Path.php
index 564a3e612dfb8..2dbb5b86dac0d 100644
--- a/libraries/src/Filesystem/Path.php
+++ b/libraries/src/Filesystem/Path.php
@@ -1,4 +1,5 @@
'[ROOT]',
- $makePattern(sys_get_temp_dir()) => '[TMP]',
- ];
-
- return preg_replace(array_keys($replacements), array_values($replacements), $message);
- }
+ /**
+ * Checks if a path's permissions can be changed.
+ *
+ * @param string $path Path to check.
+ *
+ * @return boolean True if path can have mode changed.
+ *
+ * @since 1.7.0
+ */
+ public static function canChmod($path)
+ {
+ $perms = fileperms($path);
+
+ if ($perms !== false) {
+ if (@chmod($path, $perms ^ 0001)) {
+ @chmod($path, $perms);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Chmods files and directories recursively to given permissions.
+ *
+ * @param string $path Root path to begin changing mode [without trailing slash].
+ * @param string $filemode Octal representation of the value to change file mode to [null = no change].
+ * @param string $foldermode Octal representation of the value to change folder mode to [null = no change].
+ *
+ * @return boolean True if successful [one fail means the whole operation failed].
+ *
+ * @since 1.7.0
+ */
+ public static function setPermissions($path, $filemode = '0644', $foldermode = '0755')
+ {
+ // Initialise return value
+ $ret = true;
+
+ if (is_dir($path)) {
+ $dh = opendir($path);
+
+ while ($file = readdir($dh)) {
+ if ($file != '.' && $file != '..') {
+ $fullpath = $path . '/' . $file;
+
+ if (is_dir($fullpath)) {
+ if (!self::setPermissions($fullpath, $filemode, $foldermode)) {
+ $ret = false;
+ }
+ } else {
+ if (isset($filemode)) {
+ if (!@ chmod($fullpath, octdec($filemode))) {
+ $ret = false;
+ }
+ }
+ }
+ }
+ }
+
+ closedir($dh);
+
+ if (isset($foldermode)) {
+ if (!@ chmod($path, octdec($foldermode))) {
+ $ret = false;
+ }
+ }
+ } else {
+ if (isset($filemode)) {
+ $ret = @ chmod($path, octdec($filemode));
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get the permissions of the file/folder at a given path.
+ *
+ * @param string $path The path of a file/folder.
+ *
+ * @return string Filesystem permissions.
+ *
+ * @since 1.7.0
+ */
+ public static function getPermissions($path)
+ {
+ $path = self::clean($path);
+ $mode = @ decoct(@ fileperms($path) & 0777);
+
+ if (\strlen($mode) < 3) {
+ return '---------';
+ }
+
+ $parsed_mode = '';
+
+ for ($i = 0; $i < 3; $i++) {
+ // Read
+ $parsed_mode .= ($mode[$i] & 04) ? 'r' : '-';
+
+ // Write
+ $parsed_mode .= ($mode[$i] & 02) ? 'w' : '-';
+
+ // Execute
+ $parsed_mode .= ($mode[$i] & 01) ? 'x' : '-';
+ }
+
+ return $parsed_mode;
+ }
+
+ /**
+ * Checks for snooping outside of the file system root.
+ *
+ * @param string $path A file system path to check.
+ *
+ * @return string A cleaned version of the path or exit on error.
+ *
+ * @throws \Exception
+ * @since 1.7.0
+ */
+ public static function check($path)
+ {
+ if (strpos($path, '..') !== false) {
+ // Don't translate
+ throw new \Exception(
+ sprintf(
+ '%s() - Use of relative paths not permitted',
+ __METHOD__
+ )
+ );
+ }
+
+ $path = self::clean($path);
+
+ if ((JPATH_ROOT != '') && strpos($path, self::clean(JPATH_ROOT)) !== 0) {
+ throw new \Exception(
+ sprintf(
+ '%1$s() - Snooping out of bounds @ %2$s',
+ __METHOD__,
+ self::removeRoot($path)
+ )
+ );
+ }
+
+ return $path;
+ }
+
+ /**
+ * Function to strip additional / or \ in a path name.
+ *
+ * @param string $path The path to clean.
+ * @param string $ds Directory separator (optional).
+ *
+ * @return string The cleaned path.
+ *
+ * @throws \UnexpectedValueException
+ * @since 1.7.0
+ */
+ public static function clean($path, $ds = DIRECTORY_SEPARATOR)
+ {
+ if (!\is_string($path) && !empty($path)) {
+ throw new \UnexpectedValueException(
+ sprintf(
+ '%s() - $path is not a string',
+ __METHOD__
+ )
+ );
+ }
+
+ $path = trim($path);
+
+ if (empty($path)) {
+ $path = JPATH_ROOT;
+ } elseif (($ds === '\\') && substr($path, 0, 2) === '\\\\') {
+ // Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
+ // If dealing with a UNC path don't forget to prepend the path with a backslash.
+ $path = "\\" . preg_replace('#[/\\\\]+#', $ds, $path);
+ } else {
+ $path = preg_replace('#[/\\\\]+#', $ds, $path);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Method to determine if script owns the path.
+ *
+ * @param string $path Path to check ownership.
+ *
+ * @return boolean True if the php script owns the path passed.
+ *
+ * @since 1.7.0
+ */
+ public static function isOwner($path)
+ {
+ $tmp = md5(Crypt::genRandomBytes());
+ $ssp = ini_get('session.save_path');
+ $jtp = JPATH_SITE . '/tmp';
+
+ // Try to find a writable directory
+ $dir = false;
+
+ foreach ([$jtp, $ssp, '/tmp'] as $currentDir) {
+ if (is_writable($currentDir)) {
+ $dir = $currentDir;
+
+ break;
+ }
+ }
+
+ if ($dir) {
+ $test = $dir . '/' . $tmp;
+
+ // Create the test file
+ $blank = '';
+ File::write($test, $blank, false);
+
+ // Test ownership
+ $return = (fileowner($test) == fileowner($path));
+
+ // Delete the test file
+ File::delete($test);
+
+ return $return;
+ }
+
+ return false;
+ }
+
+ /**
+ * Searches the directory paths for a given file.
+ *
+ * @param mixed $paths An path string or array of path strings to search in
+ * @param string $file The file name to look for.
+ *
+ * @return mixed The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
+ *
+ * @since 1.7.0
+ */
+ public static function find($paths, $file)
+ {
+ // Force to array
+ if (!\is_array($paths) && !($paths instanceof \Iterator)) {
+ settype($paths, 'array');
+ }
+
+ // Start looping through the path set
+ foreach ($paths as $path) {
+ // Get the path to the file
+ $fullname = $path . '/' . $file;
+
+ // Is the path based on a stream?
+ if (strpos($path, '://') === false) {
+ // Not a stream, so do a realpath() to avoid directory
+ // traversal attempts on the local file system.
+
+ // Needed for substr() later
+ $path = realpath($path);
+ $fullname = realpath($fullname);
+ }
+
+ /*
+ * The substr() check added to make sure that the realpath()
+ * results in a directory registered so that
+ * non-registered directories are not accessible via directory
+ * traversal attempts.
+ */
+ if (file_exists($fullname) && substr($fullname, 0, \strlen($path)) === $path) {
+ return $fullname;
+ }
+ }
+
+ // Could not find the file in the set of paths
+ return false;
+ }
+
+ /**
+ * Resolves /./, /../ and multiple / in a string and returns the resulting absolute path, inspired by Flysystem
+ * Removes trailing slashes
+ *
+ * @param string $path A path to resolve
+ *
+ * @return string The resolved path
+ *
+ * @since 3.9.25
+ */
+ public static function resolve($path)
+ {
+ $path = static::clean($path);
+
+ // Save start character for absolute path
+ $startCharacter = ($path[0] === DIRECTORY_SEPARATOR) ? DIRECTORY_SEPARATOR : '';
+
+ $parts = [];
+
+ foreach (explode(DIRECTORY_SEPARATOR, $path) as $part) {
+ switch ($part) {
+ case '':
+ case '.':
+ break;
+
+ case '..':
+ if (empty($parts)) {
+ throw new \Exception('Path is outside of the defined root');
+ }
+
+ array_pop($parts);
+ break;
+
+ default:
+ $parts[] = $part;
+ break;
+ }
+ }
+
+ return $startCharacter . implode(DIRECTORY_SEPARATOR, $parts);
+ }
+
+ /**
+ * Remove all references to root directory path and the system tmp path from a message
+ *
+ * @param string $message The message to be cleaned
+ * @param string $rootDirectory Optional root directory, defaults to JPATH_ROOT
+ *
+ * @return string
+ *
+ * @since 3.10.7
+ */
+ public static function removeRoot($message, $rootDirectory = null)
+ {
+ if (empty($rootDirectory)) {
+ $rootDirectory = JPATH_ROOT;
+ }
+
+ $makePattern = static function ($dir) {
+ return '~' . str_replace('~', '\\~', preg_replace('~[/\\\\]+~', '[/\\\\\\\\]+', $dir)) . '~';
+ };
+
+ $replacements = [
+ $makePattern(static::clean($rootDirectory)) => '[ROOT]',
+ $makePattern(sys_get_temp_dir()) => '[TMP]',
+ ];
+
+ return preg_replace(array_keys($replacements), array_values($replacements), $message);
+ }
}
diff --git a/libraries/src/Filesystem/Stream.php b/libraries/src/Filesystem/Stream.php
index 7c4629a8b81cd..a70dca49b6dfc 100644
--- a/libraries/src/Filesystem/Stream.php
+++ b/libraries/src/Filesystem/Stream.php
@@ -1,4 +1,5 @@
writeprefix = $writeprefix;
- $this->readprefix = $readprefix;
- $this->contextOptions = $context;
- $this->_buildContext();
- }
-
- /**
- * Destructor
- *
- * @since 1.7.0
- */
- public function __destruct()
- {
- // Attempt to close on destruction if there is a file handle
- if ($this->fh)
- {
- @$this->close();
- }
- }
-
- /**
- * Generic File Operations
- *
- * Open a stream with some lazy loading smarts
- *
- * @param string $filename Filename
- * @param string $mode Mode string to use
- * @param boolean $useIncludePath Use the PHP include path
- * @param resource $context Context to use when opening
- * @param boolean $usePrefix Use a prefix to open the file
- * @param boolean $relative Filename is a relative path (if false, strips JPATH_ROOT to make it relative)
- * @param boolean $detectProcessingMode Detect the processing method for the file and use the appropriate function
- * to handle output automatically
- *
- * @return boolean
- *
- * @since 1.7.0
- */
- public function open($filename, $mode = 'r', $useIncludePath = false, $context = null,
- $usePrefix = false, $relative = false, $detectProcessingMode = false
- )
- {
- $filename = $this->_getFilename($filename, $mode, $usePrefix, $relative);
-
- if (!$filename)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
-
- return false;
- }
-
- $this->filename = $filename;
- $this->openmode = $mode;
-
- $url = parse_url($filename);
- $retval = false;
-
- if (isset($url['scheme']))
- {
- // If we're dealing with a Joomla! stream, load it
- if (FilesystemHelper::isJoomlaStream($url['scheme']))
- {
- require_once __DIR__ . '/streams/' . $url['scheme'] . '.php';
- }
-
- // We have a scheme! force the method to be f
- $this->processingmethod = 'f';
- }
- elseif ($detectProcessingMode)
- {
- $ext = strtolower(File::getExt($this->filename));
-
- switch ($ext)
- {
- case 'tgz':
- case 'gz':
- case 'gzip':
- $this->processingmethod = 'gz';
- break;
-
- case 'tbz2':
- case 'bz2':
- case 'bzip2':
- $this->processingmethod = 'bz';
- break;
-
- default:
- $this->processingmethod = 'f';
- break;
- }
- }
-
- // Capture PHP errors
- $php_errormsg = 'Error Unknown whilst opening a file';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- // Decide which context to use:
- switch ($this->processingmethod)
- {
- // Gzip doesn't support contexts or streams
- case 'gz':
- $this->fh = gzopen($filename, $mode, $useIncludePath);
- break;
-
- // Bzip2 is much like gzip except it doesn't use the include path
- case 'bz':
- $this->fh = bzopen($filename, $mode);
- break;
-
- // Fopen can handle streams
- case 'f':
- default:
- // One supplied at open; overrides everything
- if ($context)
- {
- $this->fh = fopen($filename, $mode, $useIncludePath, $context);
- }
- // One provided at initialisation
- elseif ($this->context)
- {
- $this->fh = fopen($filename, $mode, $useIncludePath, $this->context);
- }
- // No context; all defaults
- else
- {
- $this->fh = fopen($filename, $mode, $useIncludePath);
- }
-
- break;
- }
-
- if (!$this->fh)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $retval = true;
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Attempt to close a file handle
- *
- * Will return false if it failed and true on success
- * If the file is not open the system will return true, this function destroys the file handle as well
- *
- * @return boolean
- *
- * @since 1.7.0
- */
- public function close()
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return true;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = 'Error Unknown';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- switch ($this->processingmethod)
- {
- case 'gz':
- $res = gzclose($this->fh);
- break;
-
- case 'bz':
- $res = bzclose($this->fh);
- break;
-
- case 'f':
- default:
- $res = fclose($this->fh);
- break;
- }
-
- if (!$res)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- // Reset this
- $this->fh = null;
- $retval = true;
- }
-
- // If we wrote, chmod the file after it's closed
- if ($this->openmode[0] == 'w')
- {
- $this->chmod();
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Work out if we're at the end of the file for a stream
- *
- * @return boolean
- *
- * @since 1.7.0
- */
- public function eof()
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- switch ($this->processingmethod)
- {
- case 'gz':
- $res = gzeof($this->fh);
- break;
-
- case 'bz':
- case 'f':
- default:
- $res = feof($this->fh);
- break;
- }
-
- if ($php_errormsg)
- {
- $this->setError($php_errormsg);
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $res;
- }
-
- /**
- * Retrieve the file size of the path
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function filesize()
- {
- if (!$this->filename)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $res = @filesize($this->filename);
-
- if (!$res)
- {
- $tmp_error = '';
-
- if ($php_errormsg)
- {
- // Something went wrong.
- // Store the error in case we need it.
- $tmp_error = $php_errormsg;
- }
-
- $res = FilesystemHelper::remotefsize($this->filename);
-
- if (!$res)
- {
- if ($tmp_error)
- {
- // Use the php_errormsg from before
- $this->setError($tmp_error);
- }
- else
- {
- // Error but nothing from php? How strange! Create our own
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_SIZE'));
- }
- }
- else
- {
- $this->filesize = $res;
- $retval = $res;
- }
- }
- else
- {
- $this->filesize = $res;
- $retval = $res;
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Get a line from the stream source.
- *
- * @param integer $length The number of bytes (optional) to read.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function gets($length = 0)
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = 'Error Unknown';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- switch ($this->processingmethod)
- {
- case 'gz':
- $res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);
- break;
-
- case 'bz':
- case 'f':
- default:
- $res = $length ? fgets($this->fh, $length) : fgets($this->fh);
- break;
- }
-
- if (!$res)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $retval = $res;
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Read a file
- *
- * Handles user space streams appropriately otherwise any read will return 8192
- *
- * @param integer $length Length of data to read
- *
- * @return mixed
- *
- * @link https://www.php.net/manual/en/function.fread.php
- * @since 1.7.0
- */
- public function read($length = 0)
- {
- if (!$this->filesize && !$length)
- {
- // Get the filesize
- $this->filesize();
-
- if (!$this->filesize)
- {
- // Set it to the biggest and then wait until eof
- $length = -1;
- }
- else
- {
- $length = $this->filesize;
- }
- }
-
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = 'Error Unknown';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $remaining = $length;
-
- do
- {
- // Do chunked reads where relevant
- switch ($this->processingmethod)
- {
- case 'bz':
- $res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize);
- break;
-
- case 'gz':
- $res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize);
- break;
-
- case 'f':
- default:
- $res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize);
- break;
- }
-
- if (!$res)
- {
- $this->setError($php_errormsg);
-
- // Jump from the loop
- $remaining = 0;
- }
- else
- {
- if (!$retval)
- {
- $retval = '';
- }
-
- $retval .= $res;
-
- if (!$this->eof())
- {
- $len = \strlen($res);
- $remaining -= $len;
- }
- else
- {
- // If it's the end of the file then we've nothing left to read; reset remaining and len
- $remaining = 0;
- $length = \strlen($retval);
- }
- }
- }
- while ($remaining || !$length);
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Seek the file
- *
- * Note: the return value is different to that of fseek
- *
- * @param integer $offset Offset to use when seeking.
- * @param integer $whence Seek mode to use.
- *
- * @return boolean True on success, false on failure
- *
- * @link https://www.php.net/manual/en/function.fseek.php
- * @since 1.7.0
- */
- public function seek($offset, $whence = SEEK_SET)
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- switch ($this->processingmethod)
- {
- case 'gz':
- $res = gzseek($this->fh, $offset, $whence);
- break;
-
- case 'bz':
- case 'f':
- default:
- $res = fseek($this->fh, $offset, $whence);
- break;
- }
-
- // Seek, interestingly, returns 0 on success or -1 on failure.
- if ($res == -1)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $retval = true;
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Returns the current position of the file read/write pointer.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function tell()
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- switch ($this->processingmethod)
- {
- case 'gz':
- $res = gztell($this->fh);
- break;
-
- case 'bz':
- case 'f':
- default:
- $res = ftell($this->fh);
- break;
- }
-
- // May return 0 so check if it's really false
- if ($res === false)
- {
- $this->setError($php_errormsg);
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $res;
- }
-
- /**
- * File write
- *
- * Whilst this function accepts a reference, the underlying fwrite
- * will do a copy! This will roughly double the memory allocation for
- * any write you do. Specifying chunked will get around this by only
- * writing in specific chunk sizes. This defaults to 8192 which is a
- * sane number to use most of the time (change the default with
- * JStream::set('chunksize', newsize);)
- * Note: This doesn't support gzip/bzip2 writing like reading does
- *
- * @param string $string Reference to the string to write.
- * @param integer $length Length of the string to write.
- * @param integer $chunk Size of chunks to write in.
- *
- * @return boolean
- *
- * @link https://www.php.net/manual/en/function.fwrite.php
- * @since 1.7.0
- */
- public function write(&$string, $length = 0, $chunk = 0)
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- // If the length isn't set, set it to the length of the string.
- if (!$length)
- {
- $length = \strlen($string);
- }
-
- // If the chunk isn't set, set it to the default.
- if (!$chunk)
- {
- $chunk = $this->chunksize;
- }
-
- $retval = true;
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $remaining = $length;
- $start = 0;
-
- do
- {
- // If the amount remaining is greater than the chunk size, then use the chunk
- $amount = ($remaining > $chunk) ? $chunk : $remaining;
- $res = fwrite($this->fh, substr($string, $start), $amount);
-
- // Returns false on error or the number of bytes written
- if ($res === false)
- {
- // Returned error
- $this->setError($php_errormsg);
- $retval = false;
- $remaining = 0;
- }
- elseif ($res === 0)
- {
- // Wrote nothing?
- $remaining = 0;
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_NO_DATA_WRITTEN'));
- }
- else
- {
- // Wrote something
- $start += $amount;
- $remaining -= $res;
- }
- }
- while ($remaining);
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Chmod wrapper
- *
- * @param string $filename File name.
- * @param mixed $mode Mode to use.
- *
- * @return boolean
- *
- * @since 1.7.0
- */
- public function chmod($filename = '', $mode = 0)
- {
- if (!$filename)
- {
- if (!isset($this->filename) || !$this->filename)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
-
- return false;
- }
-
- $filename = $this->filename;
- }
-
- // If no mode is set use the default
- if (!$mode)
- {
- $mode = $this->filemode;
- }
-
- $retval = false;
-
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $sch = parse_url($filename, PHP_URL_SCHEME);
-
- // Scheme specific options; ftp's chmod support is fun.
- switch ($sch)
- {
- case 'ftp':
- case 'ftps':
- $res = FilesystemHelper::ftpChmod($filename, $mode);
- break;
-
- default:
- $res = chmod($filename, $mode);
- break;
- }
-
- // Seek, interestingly, returns 0 on success or -1 on failure
- if (!$res)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $retval = true;
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
-
- // Return the result
- return $retval;
- }
-
- /**
- * Get the stream metadata
- *
- * @return array|boolean header/metadata
- *
- * @link https://www.php.net/manual/en/function.stream-get-meta-data.php
- * @since 1.7.0
- */
- public function get_meta_data()
- {
- if (!$this->fh)
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
-
- return false;
- }
-
- return stream_get_meta_data($this->fh);
- }
-
- /**
- * Stream contexts
- * Builds the context from the array
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function _buildContext()
- {
- // According to the manual this always works!
- if (\count($this->contextOptions))
- {
- $this->context = @stream_context_create($this->contextOptions);
- }
- else
- {
- $this->context = null;
- }
- }
-
- /**
- * Updates the context to the array
- *
- * Format is the same as the options for stream_context_create
- *
- * @param array $context Options to create the context with
- *
- * @return void
- *
- * @link https://www.php.net/stream_context_create
- * @since 1.7.0
- */
- public function setContextOptions($context)
- {
- $this->contextOptions = $context;
- $this->_buildContext();
- }
-
- /**
- * Adds a particular options to the context
- *
- * @param string $wrapper The wrapper to use
- * @param string $name The option to set
- * @param string $value The value of the option
- *
- * @return void
- *
- * @link https://www.php.net/stream_context_create Stream Context Creation
- * @link https://www.php.net/manual/en/context.php Context Options for various streams
- * @since 1.7.0
- */
- public function addContextEntry($wrapper, $name, $value)
- {
- $this->contextOptions[$wrapper][$name] = $value;
- $this->_buildContext();
- }
-
- /**
- * Deletes a particular setting from a context
- *
- * @param string $wrapper The wrapper to use
- * @param string $name The option to unset
- *
- * @return void
- *
- * @link https://www.php.net/stream_context_create
- * @since 1.7.0
- */
- public function deleteContextEntry($wrapper, $name)
- {
- // Check whether the wrapper is set
- if (isset($this->contextOptions[$wrapper]))
- {
- // Check that entry is set for that wrapper
- if (isset($this->contextOptions[$wrapper][$name]))
- {
- // Unset the item
- unset($this->contextOptions[$wrapper][$name]);
-
- // Check that there are still items there
- if (!\count($this->contextOptions[$wrapper]))
- {
- // Clean up an empty wrapper context option
- unset($this->contextOptions[$wrapper]);
- }
- }
- }
-
- // Rebuild the context and apply it to the stream
- $this->_buildContext();
- }
-
- /**
- * Applies the current context to the stream
- *
- * Use this to change the values of the context after you've opened a stream
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function applyContextToStream()
- {
- $retval = false;
-
- if ($this->fh)
- {
- // Capture PHP errors
- $php_errormsg = 'Unknown error setting context option';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $retval = @stream_context_set_option($this->fh, $this->contextOptions);
-
- if (!$retval)
- {
- $this->setError($php_errormsg);
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
- }
-
- return $retval;
- }
-
- /**
- * Stream filters
- * Append a filter to the chain
- *
- * @param string $filterName The key name of the filter.
- * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ.
- * @param array $params An array of params for the stream_filter_append call.
- *
- * @return mixed
- *
- * @link https://www.php.net/manual/en/function.stream-filter-append.php
- * @since 1.7.0
- */
- public function appendFilter($filterName, $readWrite = STREAM_FILTER_READ, $params = array())
- {
- $res = false;
-
- if ($this->fh)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- $res = @stream_filter_append($this->fh, $filterName, $readWrite, $params);
-
- if (!$res && $php_errormsg)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $this->filters[] = &$res;
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
- }
-
- return $res;
- }
-
- /**
- * Prepend a filter to the chain
- *
- * @param string $filterName The key name of the filter.
- * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ.
- * @param array $params An array of params for the stream_filter_prepend call.
- *
- * @return mixed
- *
- * @link https://www.php.net/manual/en/function.stream-filter-prepend.php
- * @since 1.7.0
- */
- public function prependFilter($filterName, $readWrite = STREAM_FILTER_READ, $params = array())
- {
- $res = false;
-
- if ($this->fh)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
- $res = @stream_filter_prepend($this->fh, $filterName, $readWrite, $params);
-
- if (!$res && $php_errormsg)
- {
- // Set the error msg
- $this->setError($php_errormsg);
- }
- else
- {
- array_unshift($res, '');
- $res[0] = &$this->filters;
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
- }
-
- return $res;
- }
-
- /**
- * Remove a filter, either by resource (handed out from the append or prepend function)
- * or via getting the filter list)
- *
- * @param resource $resource The resource.
- * @param boolean $byindex The index of the filter.
- *
- * @return boolean Result of operation
- *
- * @since 1.7.0
- */
- public function removeFilter(&$resource, $byindex = false)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- if ($byindex)
- {
- $res = stream_filter_remove($this->filters[$resource]);
- }
- else
- {
- $res = stream_filter_remove($resource);
- }
-
- if ($res && $php_errormsg)
- {
- $this->setError($php_errormsg);
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
-
- return $res;
- }
-
- /**
- * Copy a file from src to dest
- *
- * @param string $src The file path to copy from.
- * @param string $dest The file path to copy to.
- * @param resource $context A valid context resource (optional) created with stream_context_create.
- * @param boolean $usePrefix Controls the use of a prefix (optional).
- * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function copy($src, $dest, $context = null, $usePrefix = true, $relative = false)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- $chmodDest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
-
- // Since we're going to open the file directly we need to get the filename.
- // We need to use the same prefix so force everything to write.
- $src = $this->_getFilename($src, 'w', $usePrefix, $relative);
- $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
-
- if ($context)
- {
- // Use the provided context
- $res = @copy($src, $dest, $context);
- }
- elseif ($this->context)
- {
- // Use the objects context
- $res = @copy($src, $dest, $this->context);
- }
- else
- {
- // Don't use any context
- $res = @copy($src, $dest);
- }
-
- if (!$res && $php_errormsg)
- {
- $this->setError($php_errormsg);
- }
- else
- {
- $this->chmod($chmodDest);
- }
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- return $res;
- }
-
- /**
- * Moves a file
- *
- * @param string $src The file path to move from.
- * @param string $dest The file path to move to.
- * @param resource $context A valid context resource (optional) created with stream_context_create.
- * @param boolean $usePrefix Controls the use of a prefix (optional).
- * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function move($src, $dest, $context = null, $usePrefix = true, $relative = false)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- $src = $this->_getFilename($src, 'w', $usePrefix, $relative);
- $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
-
- if ($context)
- {
- // Use the provided context
- $res = @rename($src, $dest, $context);
- }
- elseif ($this->context)
- {
- // Use the object's context
- $res = @rename($src, $dest, $this->context);
- }
- else
- {
- // Don't use any context
- $res = @rename($src, $dest);
- }
-
- if (!$res && $php_errormsg)
- {
- $this->setError($php_errormsg());
- }
-
- $this->chmod($dest);
-
- // Restore error tracking to what it was before
- ini_set('track_errors', $track_errors);
-
- return $res;
- }
-
- /**
- * Delete a file
- *
- * @param string $filename The file path to delete.
- * @param resource $context A valid context resource (optional) created with stream_context_create.
- * @param boolean $usePrefix Controls the use of a prefix (optional).
- * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function delete($filename, $context = null, $usePrefix = true, $relative = false)
- {
- // Capture PHP errors
- $php_errormsg = '';
- $track_errors = ini_get('track_errors');
- ini_set('track_errors', true);
-
- $filename = $this->_getFilename($filename, 'w', $usePrefix, $relative);
-
- if ($context)
- {
- // Use the provided context
- $res = @unlink($filename, $context);
- }
- elseif ($this->context)
- {
- // Use the object's context
- $res = @unlink($filename, $this->context);
- }
- else
- {
- // Don't use any context
- $res = @unlink($filename);
- }
-
- if (!$res && $php_errormsg)
- {
- $this->setError($php_errormsg());
- }
-
- // Restore error tracking to what it was before.
- ini_set('track_errors', $track_errors);
-
- return $res;
- }
-
- /**
- * Upload a file
- *
- * @param string $src The file path to copy from (usually a temp folder).
- * @param string $dest The file path to copy to.
- * @param resource $context A valid context resource (optional) created with stream_context_create.
- * @param boolean $usePrefix Controls the use of a prefix (optional).
- * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
- *
- * @return mixed
- *
- * @since 1.7.0
- */
- public function upload($src, $dest, $context = null, $usePrefix = true, $relative = false)
- {
- if (is_uploaded_file($src))
- {
- // Make sure it's an uploaded file
- return $this->copy($src, $dest, $context, $usePrefix, $relative);
- }
- else
- {
- $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_NOT_UPLOADED_FILE'));
-
- return false;
- }
- }
-
- /**
- * Writes a chunk of data to a file.
- *
- * @param string $filename The file name.
- * @param string $buffer The data to write to the file.
- *
- * @return boolean
- *
- * @since 1.7.0
- */
- public function writeFile($filename, &$buffer)
- {
- if ($this->open($filename, 'w'))
- {
- $result = $this->write($buffer);
- $this->chmod();
- $this->close();
-
- return $result;
- }
-
- return false;
- }
-
- /**
- * Determine the appropriate 'filename' of a file
- *
- * @param string $filename Original filename of the file
- * @param string $mode Mode string to retrieve the filename
- * @param boolean $usePrefix Controls the use of a prefix
- * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
- *
- * @return string
- *
- * @since 1.7.0
- */
- public function _getFilename($filename, $mode, $usePrefix, $relative)
- {
- if ($usePrefix)
- {
- // Get rid of binary or t, should be at the end of the string
- $tmode = trim($mode, 'btf123456789');
-
- // Check if it's a write mode then add the appropriate prefix
- // Get rid of JPATH_ROOT (legacy compat) along the way
- if (\in_array($tmode, FilesystemHelper::getWriteModes()))
- {
- if (!$relative && $this->writeprefix)
- {
- $filename = str_replace(JPATH_ROOT, '', $filename);
- }
-
- $filename = $this->writeprefix . $filename;
- }
- else
- {
- if (!$relative && $this->readprefix)
- {
- $filename = str_replace(JPATH_ROOT, '', $filename);
- }
-
- $filename = $this->readprefix . $filename;
- }
- }
-
- return $filename;
- }
-
- /**
- * Return the internal file handle
- *
- * @return resource File handler
- *
- * @since 1.7.0
- */
- public function getFileHandle()
- {
- return $this->fh;
- }
+ /**
+ * File Mode
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ protected $filemode = 0644;
+
+ /**
+ * Directory Mode
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ protected $dirmode = 0755;
+
+ /**
+ * Default Chunk Size
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ protected $chunksize = 8192;
+
+ /**
+ * Filename
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $filename;
+
+ /**
+ * Prefix of the connection for writing
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $writeprefix;
+
+ /**
+ * Prefix of the connection for reading
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $readprefix;
+
+ /**
+ * Read Processing method
+ * @var string gz, bz, f
+ * If a scheme is detected, fopen will be defaulted
+ * To use compression with a network stream use a filter
+ * @since 1.7.0
+ */
+ protected $processingmethod = 'f';
+
+ /**
+ * Filters applied to the current stream
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $filters = array();
+
+ /**
+ * File Handle
+ *
+ * @var resource
+ * @since 3.0.0
+ */
+ protected $fh;
+
+ /**
+ * File size
+ *
+ * @var integer
+ * @since 3.0.0
+ */
+ protected $filesize;
+
+ /**
+ * Context to use when opening the connection
+ *
+ * @var resource
+ * @since 3.0.0
+ */
+ protected $context = null;
+
+ /**
+ * Context options; used to rebuild the context
+ *
+ * @var array
+ * @since 3.0.0
+ */
+ protected $contextOptions;
+
+ /**
+ * The mode under which the file was opened
+ *
+ * @var string
+ * @since 3.0.0
+ */
+ protected $openmode;
+
+ /**
+ * Constructor
+ *
+ * @param string $writeprefix Prefix of the stream (optional). Unlike the JPATH_*, this has a final path separator!
+ * @param string $readprefix The read prefix (optional).
+ * @param array $context The context options (optional).
+ *
+ * @since 1.7.0
+ */
+ public function __construct($writeprefix = '', $readprefix = '', $context = array())
+ {
+ $this->writeprefix = $writeprefix;
+ $this->readprefix = $readprefix;
+ $this->contextOptions = $context;
+ $this->_buildContext();
+ }
+
+ /**
+ * Destructor
+ *
+ * @since 1.7.0
+ */
+ public function __destruct()
+ {
+ // Attempt to close on destruction if there is a file handle
+ if ($this->fh) {
+ @$this->close();
+ }
+ }
+
+ /**
+ * Generic File Operations
+ *
+ * Open a stream with some lazy loading smarts
+ *
+ * @param string $filename Filename
+ * @param string $mode Mode string to use
+ * @param boolean $useIncludePath Use the PHP include path
+ * @param resource $context Context to use when opening
+ * @param boolean $usePrefix Use a prefix to open the file
+ * @param boolean $relative Filename is a relative path (if false, strips JPATH_ROOT to make it relative)
+ * @param boolean $detectProcessingMode Detect the processing method for the file and use the appropriate function
+ * to handle output automatically
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function open(
+ $filename,
+ $mode = 'r',
+ $useIncludePath = false,
+ $context = null,
+ $usePrefix = false,
+ $relative = false,
+ $detectProcessingMode = false
+ ) {
+ $filename = $this->_getFilename($filename, $mode, $usePrefix, $relative);
+
+ if (!$filename) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
+
+ return false;
+ }
+
+ $this->filename = $filename;
+ $this->openmode = $mode;
+
+ $url = parse_url($filename);
+ $retval = false;
+
+ if (isset($url['scheme'])) {
+ // If we're dealing with a Joomla! stream, load it
+ if (FilesystemHelper::isJoomlaStream($url['scheme'])) {
+ require_once __DIR__ . '/streams/' . $url['scheme'] . '.php';
+ }
+
+ // We have a scheme! force the method to be f
+ $this->processingmethod = 'f';
+ } elseif ($detectProcessingMode) {
+ $ext = strtolower(File::getExt($this->filename));
+
+ switch ($ext) {
+ case 'tgz':
+ case 'gz':
+ case 'gzip':
+ $this->processingmethod = 'gz';
+ break;
+
+ case 'tbz2':
+ case 'bz2':
+ case 'bzip2':
+ $this->processingmethod = 'bz';
+ break;
+
+ default:
+ $this->processingmethod = 'f';
+ break;
+ }
+ }
+
+ // Capture PHP errors
+ $php_errormsg = 'Error Unknown whilst opening a file';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ // Decide which context to use:
+ switch ($this->processingmethod) {
+ // Gzip doesn't support contexts or streams
+ case 'gz':
+ $this->fh = gzopen($filename, $mode, $useIncludePath);
+ break;
+
+ // Bzip2 is much like gzip except it doesn't use the include path
+ case 'bz':
+ $this->fh = bzopen($filename, $mode);
+ break;
+
+ // Fopen can handle streams
+ case 'f':
+ default:
+ // One supplied at open; overrides everything
+ if ($context) {
+ $this->fh = fopen($filename, $mode, $useIncludePath, $context);
+ } elseif ($this->context) {
+ // One provided at initialisation
+ $this->fh = fopen($filename, $mode, $useIncludePath, $this->context);
+ } else {
+ // No context; all defaults
+ $this->fh = fopen($filename, $mode, $useIncludePath);
+ }
+
+ break;
+ }
+
+ if (!$this->fh) {
+ $this->setError($php_errormsg);
+ } else {
+ $retval = true;
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Attempt to close a file handle
+ *
+ * Will return false if it failed and true on success
+ * If the file is not open the system will return true, this function destroys the file handle as well
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function close()
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return true;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = 'Error Unknown';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ switch ($this->processingmethod) {
+ case 'gz':
+ $res = gzclose($this->fh);
+ break;
+
+ case 'bz':
+ $res = bzclose($this->fh);
+ break;
+
+ case 'f':
+ default:
+ $res = fclose($this->fh);
+ break;
+ }
+
+ if (!$res) {
+ $this->setError($php_errormsg);
+ } else {
+ // Reset this
+ $this->fh = null;
+ $retval = true;
+ }
+
+ // If we wrote, chmod the file after it's closed
+ if ($this->openmode[0] == 'w') {
+ $this->chmod();
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Work out if we're at the end of the file for a stream
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function eof()
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ switch ($this->processingmethod) {
+ case 'gz':
+ $res = gzeof($this->fh);
+ break;
+
+ case 'bz':
+ case 'f':
+ default:
+ $res = feof($this->fh);
+ break;
+ }
+
+ if ($php_errormsg) {
+ $this->setError($php_errormsg);
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $res;
+ }
+
+ /**
+ * Retrieve the file size of the path
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function filesize()
+ {
+ if (!$this->filename) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $res = @filesize($this->filename);
+
+ if (!$res) {
+ $tmp_error = '';
+
+ if ($php_errormsg) {
+ // Something went wrong.
+ // Store the error in case we need it.
+ $tmp_error = $php_errormsg;
+ }
+
+ $res = FilesystemHelper::remotefsize($this->filename);
+
+ if (!$res) {
+ if ($tmp_error) {
+ // Use the php_errormsg from before
+ $this->setError($tmp_error);
+ } else {
+ // Error but nothing from php? How strange! Create our own
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_SIZE'));
+ }
+ } else {
+ $this->filesize = $res;
+ $retval = $res;
+ }
+ } else {
+ $this->filesize = $res;
+ $retval = $res;
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Get a line from the stream source.
+ *
+ * @param integer $length The number of bytes (optional) to read.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function gets($length = 0)
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = 'Error Unknown';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ switch ($this->processingmethod) {
+ case 'gz':
+ $res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);
+ break;
+
+ case 'bz':
+ case 'f':
+ default:
+ $res = $length ? fgets($this->fh, $length) : fgets($this->fh);
+ break;
+ }
+
+ if (!$res) {
+ $this->setError($php_errormsg);
+ } else {
+ $retval = $res;
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Read a file
+ *
+ * Handles user space streams appropriately otherwise any read will return 8192
+ *
+ * @param integer $length Length of data to read
+ *
+ * @return mixed
+ *
+ * @link https://www.php.net/manual/en/function.fread.php
+ * @since 1.7.0
+ */
+ public function read($length = 0)
+ {
+ if (!$this->filesize && !$length) {
+ // Get the filesize
+ $this->filesize();
+
+ if (!$this->filesize) {
+ // Set it to the biggest and then wait until eof
+ $length = -1;
+ } else {
+ $length = $this->filesize;
+ }
+ }
+
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = 'Error Unknown';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $remaining = $length;
+
+ do {
+ // Do chunked reads where relevant
+ switch ($this->processingmethod) {
+ case 'bz':
+ $res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize);
+ break;
+
+ case 'gz':
+ $res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize);
+ break;
+
+ case 'f':
+ default:
+ $res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize);
+ break;
+ }
+
+ if (!$res) {
+ $this->setError($php_errormsg);
+
+ // Jump from the loop
+ $remaining = 0;
+ } else {
+ if (!$retval) {
+ $retval = '';
+ }
+
+ $retval .= $res;
+
+ if (!$this->eof()) {
+ $len = \strlen($res);
+ $remaining -= $len;
+ } else {
+ // If it's the end of the file then we've nothing left to read; reset remaining and len
+ $remaining = 0;
+ $length = \strlen($retval);
+ }
+ }
+ } while ($remaining || !$length);
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Seek the file
+ *
+ * Note: the return value is different to that of fseek
+ *
+ * @param integer $offset Offset to use when seeking.
+ * @param integer $whence Seek mode to use.
+ *
+ * @return boolean True on success, false on failure
+ *
+ * @link https://www.php.net/manual/en/function.fseek.php
+ * @since 1.7.0
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ switch ($this->processingmethod) {
+ case 'gz':
+ $res = gzseek($this->fh, $offset, $whence);
+ break;
+
+ case 'bz':
+ case 'f':
+ default:
+ $res = fseek($this->fh, $offset, $whence);
+ break;
+ }
+
+ // Seek, interestingly, returns 0 on success or -1 on failure.
+ if ($res == -1) {
+ $this->setError($php_errormsg);
+ } else {
+ $retval = true;
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Returns the current position of the file read/write pointer.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function tell()
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ switch ($this->processingmethod) {
+ case 'gz':
+ $res = gztell($this->fh);
+ break;
+
+ case 'bz':
+ case 'f':
+ default:
+ $res = ftell($this->fh);
+ break;
+ }
+
+ // May return 0 so check if it's really false
+ if ($res === false) {
+ $this->setError($php_errormsg);
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $res;
+ }
+
+ /**
+ * File write
+ *
+ * Whilst this function accepts a reference, the underlying fwrite
+ * will do a copy! This will roughly double the memory allocation for
+ * any write you do. Specifying chunked will get around this by only
+ * writing in specific chunk sizes. This defaults to 8192 which is a
+ * sane number to use most of the time (change the default with
+ * JStream::set('chunksize', newsize);)
+ * Note: This doesn't support gzip/bzip2 writing like reading does
+ *
+ * @param string $string Reference to the string to write.
+ * @param integer $length Length of the string to write.
+ * @param integer $chunk Size of chunks to write in.
+ *
+ * @return boolean
+ *
+ * @link https://www.php.net/manual/en/function.fwrite.php
+ * @since 1.7.0
+ */
+ public function write(&$string, $length = 0, $chunk = 0)
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ // If the length isn't set, set it to the length of the string.
+ if (!$length) {
+ $length = \strlen($string);
+ }
+
+ // If the chunk isn't set, set it to the default.
+ if (!$chunk) {
+ $chunk = $this->chunksize;
+ }
+
+ $retval = true;
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $remaining = $length;
+ $start = 0;
+
+ do {
+ // If the amount remaining is greater than the chunk size, then use the chunk
+ $amount = ($remaining > $chunk) ? $chunk : $remaining;
+ $res = fwrite($this->fh, substr($string, $start), $amount);
+
+ // Returns false on error or the number of bytes written
+ if ($res === false) {
+ // Returned error
+ $this->setError($php_errormsg);
+ $retval = false;
+ $remaining = 0;
+ } elseif ($res === 0) {
+ // Wrote nothing?
+ $remaining = 0;
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_NO_DATA_WRITTEN'));
+ } else {
+ // Wrote something
+ $start += $amount;
+ $remaining -= $res;
+ }
+ } while ($remaining);
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Chmod wrapper
+ *
+ * @param string $filename File name.
+ * @param mixed $mode Mode to use.
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function chmod($filename = '', $mode = 0)
+ {
+ if (!$filename) {
+ if (!isset($this->filename) || !$this->filename) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));
+
+ return false;
+ }
+
+ $filename = $this->filename;
+ }
+
+ // If no mode is set use the default
+ if (!$mode) {
+ $mode = $this->filemode;
+ }
+
+ $retval = false;
+
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $sch = parse_url($filename, PHP_URL_SCHEME);
+
+ // Scheme specific options; ftp's chmod support is fun.
+ switch ($sch) {
+ case 'ftp':
+ case 'ftps':
+ $res = FilesystemHelper::ftpChmod($filename, $mode);
+ break;
+
+ default:
+ $res = chmod($filename, $mode);
+ break;
+ }
+
+ // Seek, interestingly, returns 0 on success or -1 on failure
+ if (!$res) {
+ $this->setError($php_errormsg);
+ } else {
+ $retval = true;
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
+ // Return the result
+ return $retval;
+ }
+
+ /**
+ * Get the stream metadata
+ *
+ * @return array|boolean header/metadata
+ *
+ * @link https://www.php.net/manual/en/function.stream-get-meta-data.php
+ * @since 1.7.0
+ */
+ public function get_meta_data()
+ {
+ if (!$this->fh) {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));
+
+ return false;
+ }
+
+ return stream_get_meta_data($this->fh);
+ }
+
+ /**
+ * Stream contexts
+ * Builds the context from the array
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function _buildContext()
+ {
+ // According to the manual this always works!
+ if (\count($this->contextOptions)) {
+ $this->context = @stream_context_create($this->contextOptions);
+ } else {
+ $this->context = null;
+ }
+ }
+
+ /**
+ * Updates the context to the array
+ *
+ * Format is the same as the options for stream_context_create
+ *
+ * @param array $context Options to create the context with
+ *
+ * @return void
+ *
+ * @link https://www.php.net/stream_context_create
+ * @since 1.7.0
+ */
+ public function setContextOptions($context)
+ {
+ $this->contextOptions = $context;
+ $this->_buildContext();
+ }
+
+ /**
+ * Adds a particular options to the context
+ *
+ * @param string $wrapper The wrapper to use
+ * @param string $name The option to set
+ * @param string $value The value of the option
+ *
+ * @return void
+ *
+ * @link https://www.php.net/stream_context_create Stream Context Creation
+ * @link https://www.php.net/manual/en/context.php Context Options for various streams
+ * @since 1.7.0
+ */
+ public function addContextEntry($wrapper, $name, $value)
+ {
+ $this->contextOptions[$wrapper][$name] = $value;
+ $this->_buildContext();
+ }
+
+ /**
+ * Deletes a particular setting from a context
+ *
+ * @param string $wrapper The wrapper to use
+ * @param string $name The option to unset
+ *
+ * @return void
+ *
+ * @link https://www.php.net/stream_context_create
+ * @since 1.7.0
+ */
+ public function deleteContextEntry($wrapper, $name)
+ {
+ // Check whether the wrapper is set
+ if (isset($this->contextOptions[$wrapper])) {
+ // Check that entry is set for that wrapper
+ if (isset($this->contextOptions[$wrapper][$name])) {
+ // Unset the item
+ unset($this->contextOptions[$wrapper][$name]);
+
+ // Check that there are still items there
+ if (!\count($this->contextOptions[$wrapper])) {
+ // Clean up an empty wrapper context option
+ unset($this->contextOptions[$wrapper]);
+ }
+ }
+ }
+
+ // Rebuild the context and apply it to the stream
+ $this->_buildContext();
+ }
+
+ /**
+ * Applies the current context to the stream
+ *
+ * Use this to change the values of the context after you've opened a stream
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function applyContextToStream()
+ {
+ $retval = false;
+
+ if ($this->fh) {
+ // Capture PHP errors
+ $php_errormsg = 'Unknown error setting context option';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $retval = @stream_context_set_option($this->fh, $this->contextOptions);
+
+ if (!$retval) {
+ $this->setError($php_errormsg);
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Stream filters
+ * Append a filter to the chain
+ *
+ * @param string $filterName The key name of the filter.
+ * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ.
+ * @param array $params An array of params for the stream_filter_append call.
+ *
+ * @return mixed
+ *
+ * @link https://www.php.net/manual/en/function.stream-filter-append.php
+ * @since 1.7.0
+ */
+ public function appendFilter($filterName, $readWrite = STREAM_FILTER_READ, $params = array())
+ {
+ $res = false;
+
+ if ($this->fh) {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ $res = @stream_filter_append($this->fh, $filterName, $readWrite, $params);
+
+ if (!$res && $php_errormsg) {
+ $this->setError($php_errormsg);
+ } else {
+ $this->filters[] = &$res;
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+ }
+
+ return $res;
+ }
+
+ /**
+ * Prepend a filter to the chain
+ *
+ * @param string $filterName The key name of the filter.
+ * @param integer $readWrite Optional. Defaults to STREAM_FILTER_READ.
+ * @param array $params An array of params for the stream_filter_prepend call.
+ *
+ * @return mixed
+ *
+ * @link https://www.php.net/manual/en/function.stream-filter-prepend.php
+ * @since 1.7.0
+ */
+ public function prependFilter($filterName, $readWrite = STREAM_FILTER_READ, $params = array())
+ {
+ $res = false;
+
+ if ($this->fh) {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+ $res = @stream_filter_prepend($this->fh, $filterName, $readWrite, $params);
+
+ if (!$res && $php_errormsg) {
+ // Set the error msg
+ $this->setError($php_errormsg);
+ } else {
+ array_unshift($res, '');
+ $res[0] = &$this->filters;
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+ }
+
+ return $res;
+ }
+
+ /**
+ * Remove a filter, either by resource (handed out from the append or prepend function)
+ * or via getting the filter list)
+ *
+ * @param resource $resource The resource.
+ * @param boolean $byindex The index of the filter.
+ *
+ * @return boolean Result of operation
+ *
+ * @since 1.7.0
+ */
+ public function removeFilter(&$resource, $byindex = false)
+ {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ if ($byindex) {
+ $res = stream_filter_remove($this->filters[$resource]);
+ } else {
+ $res = stream_filter_remove($resource);
+ }
+
+ if ($res && $php_errormsg) {
+ $this->setError($php_errormsg);
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
+ return $res;
+ }
+
+ /**
+ * Copy a file from src to dest
+ *
+ * @param string $src The file path to copy from.
+ * @param string $dest The file path to copy to.
+ * @param resource $context A valid context resource (optional) created with stream_context_create.
+ * @param boolean $usePrefix Controls the use of a prefix (optional).
+ * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function copy($src, $dest, $context = null, $usePrefix = true, $relative = false)
+ {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ $chmodDest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
+
+ // Since we're going to open the file directly we need to get the filename.
+ // We need to use the same prefix so force everything to write.
+ $src = $this->_getFilename($src, 'w', $usePrefix, $relative);
+ $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
+
+ if ($context) {
+ // Use the provided context
+ $res = @copy($src, $dest, $context);
+ } elseif ($this->context) {
+ // Use the objects context
+ $res = @copy($src, $dest, $this->context);
+ } else {
+ // Don't use any context
+ $res = @copy($src, $dest);
+ }
+
+ if (!$res && $php_errormsg) {
+ $this->setError($php_errormsg);
+ } else {
+ $this->chmod($chmodDest);
+ }
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ return $res;
+ }
+
+ /**
+ * Moves a file
+ *
+ * @param string $src The file path to move from.
+ * @param string $dest The file path to move to.
+ * @param resource $context A valid context resource (optional) created with stream_context_create.
+ * @param boolean $usePrefix Controls the use of a prefix (optional).
+ * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function move($src, $dest, $context = null, $usePrefix = true, $relative = false)
+ {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ $src = $this->_getFilename($src, 'w', $usePrefix, $relative);
+ $dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);
+
+ if ($context) {
+ // Use the provided context
+ $res = @rename($src, $dest, $context);
+ } elseif ($this->context) {
+ // Use the object's context
+ $res = @rename($src, $dest, $this->context);
+ } else {
+ // Don't use any context
+ $res = @rename($src, $dest);
+ }
+
+ if (!$res && $php_errormsg) {
+ $this->setError($php_errormsg());
+ }
+
+ $this->chmod($dest);
+
+ // Restore error tracking to what it was before
+ ini_set('track_errors', $track_errors);
+
+ return $res;
+ }
+
+ /**
+ * Delete a file
+ *
+ * @param string $filename The file path to delete.
+ * @param resource $context A valid context resource (optional) created with stream_context_create.
+ * @param boolean $usePrefix Controls the use of a prefix (optional).
+ * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function delete($filename, $context = null, $usePrefix = true, $relative = false)
+ {
+ // Capture PHP errors
+ $php_errormsg = '';
+ $track_errors = ini_get('track_errors');
+ ini_set('track_errors', true);
+
+ $filename = $this->_getFilename($filename, 'w', $usePrefix, $relative);
+
+ if ($context) {
+ // Use the provided context
+ $res = @unlink($filename, $context);
+ } elseif ($this->context) {
+ // Use the object's context
+ $res = @unlink($filename, $this->context);
+ } else {
+ // Don't use any context
+ $res = @unlink($filename);
+ }
+
+ if (!$res && $php_errormsg) {
+ $this->setError($php_errormsg());
+ }
+
+ // Restore error tracking to what it was before.
+ ini_set('track_errors', $track_errors);
+
+ return $res;
+ }
+
+ /**
+ * Upload a file
+ *
+ * @param string $src The file path to copy from (usually a temp folder).
+ * @param string $dest The file path to copy to.
+ * @param resource $context A valid context resource (optional) created with stream_context_create.
+ * @param boolean $usePrefix Controls the use of a prefix (optional).
+ * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
+ *
+ * @return mixed
+ *
+ * @since 1.7.0
+ */
+ public function upload($src, $dest, $context = null, $usePrefix = true, $relative = false)
+ {
+ if (is_uploaded_file($src)) {
+ // Make sure it's an uploaded file
+ return $this->copy($src, $dest, $context, $usePrefix, $relative);
+ } else {
+ $this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_NOT_UPLOADED_FILE'));
+
+ return false;
+ }
+ }
+
+ /**
+ * Writes a chunk of data to a file.
+ *
+ * @param string $filename The file name.
+ * @param string $buffer The data to write to the file.
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function writeFile($filename, &$buffer)
+ {
+ if ($this->open($filename, 'w')) {
+ $result = $this->write($buffer);
+ $this->chmod();
+ $this->close();
+
+ return $result;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine the appropriate 'filename' of a file
+ *
+ * @param string $filename Original filename of the file
+ * @param string $mode Mode string to retrieve the filename
+ * @param boolean $usePrefix Controls the use of a prefix
+ * @param boolean $relative Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ */
+ public function _getFilename($filename, $mode, $usePrefix, $relative)
+ {
+ if ($usePrefix) {
+ // Get rid of binary or t, should be at the end of the string
+ $tmode = trim($mode, 'btf123456789');
+
+ // Check if it's a write mode then add the appropriate prefix
+ // Get rid of JPATH_ROOT (legacy compat) along the way
+ if (\in_array($tmode, FilesystemHelper::getWriteModes())) {
+ if (!$relative && $this->writeprefix) {
+ $filename = str_replace(JPATH_ROOT, '', $filename);
+ }
+
+ $filename = $this->writeprefix . $filename;
+ } else {
+ if (!$relative && $this->readprefix) {
+ $filename = str_replace(JPATH_ROOT, '', $filename);
+ }
+
+ $filename = $this->readprefix . $filename;
+ }
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Return the internal file handle
+ *
+ * @return resource File handler
+ *
+ * @since 1.7.0
+ */
+ public function getFileHandle()
+ {
+ return $this->fh;
+ }
}
diff --git a/libraries/src/Filesystem/Streams/StreamString.php b/libraries/src/Filesystem/Streams/StreamString.php
index d64e8c7c924b8..8eda226ebed2f 100644
--- a/libraries/src/Filesystem/Streams/StreamString.php
+++ b/libraries/src/Filesystem/Streams/StreamString.php
@@ -1,4 +1,5 @@
currentString = &StringController::getRef(str_replace('string://', '', $path));
+ /**
+ * Method to open a file or URL.
+ *
+ * @param string $path The stream path.
+ * @param string $mode Not used.
+ * @param integer $options Not used.
+ * @param string $openedPath Not used.
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ */
+ public function stream_open($path, $mode, $options, &$openedPath)
+ {
+ $this->currentString = &StringController::getRef(str_replace('string://', '', $path));
- if ($this->currentString)
- {
- $this->len = \strlen($this->currentString);
- $this->pos = 0;
- $this->stat = $this->url_stat($path, 0);
+ if ($this->currentString) {
+ $this->len = \strlen($this->currentString);
+ $this->pos = 0;
+ $this->stat = $this->url_stat($path, 0);
- return true;
- }
- else
- {
- return false;
- }
- }
+ return true;
+ } else {
+ return false;
+ }
+ }
- /**
- * Method to retrieve information from a file resource
- *
- * @return array
- *
- * @link https://www.php.net/manual/en/streamwrapper.stream-stat.php
- * @since 1.7.0
- */
- public function stream_stat()
- {
- return $this->stat;
- }
+ /**
+ * Method to retrieve information from a file resource
+ *
+ * @return array
+ *
+ * @link https://www.php.net/manual/en/streamwrapper.stream-stat.php
+ * @since 1.7.0
+ */
+ public function stream_stat()
+ {
+ return $this->stat;
+ }
- /**
- * Method to retrieve information about a file.
- *
- * @param string $path File path or URL to stat
- * @param integer $flags Additional flags set by the streams API
- *
- * @return array
- *
- * @link https://www.php.net/manual/en/streamwrapper.url-stat.php
- * @since 1.7.0
- */
- public function url_stat($path, $flags = 0)
- {
- $now = time();
- $string = &StringController::getRef(str_replace('string://', '', $path));
- $stat = array(
- 'dev' => 0,
- 'ino' => 0,
- 'mode' => 0,
- 'nlink' => 1,
- 'uid' => 0,
- 'gid' => 0,
- 'rdev' => 0,
- 'size' => \strlen($string),
- 'atime' => $now,
- 'mtime' => $now,
- 'ctime' => $now,
- 'blksize' => '512',
- 'blocks' => ceil(\strlen($string) / 512),
- );
+ /**
+ * Method to retrieve information about a file.
+ *
+ * @param string $path File path or URL to stat
+ * @param integer $flags Additional flags set by the streams API
+ *
+ * @return array
+ *
+ * @link https://www.php.net/manual/en/streamwrapper.url-stat.php
+ * @since 1.7.0
+ */
+ public function url_stat($path, $flags = 0)
+ {
+ $now = time();
+ $string = &StringController::getRef(str_replace('string://', '', $path));
+ $stat = array(
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 1,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => \strlen($string),
+ 'atime' => $now,
+ 'mtime' => $now,
+ 'ctime' => $now,
+ 'blksize' => '512',
+ 'blocks' => ceil(\strlen($string) / 512),
+ );
- return $stat;
- }
+ return $stat;
+ }
- /**
- * Method to read a given number of bytes starting at the current position
- * and moving to the end of the string defined by the current position plus the
- * given number.
- *
- * @param integer $count Bytes of data from the current position should be returned.
- *
- * @return string
- *
- * @since 1.7.0
- *
- * @link https://www.php.net/manual/en/streamwrapper.stream-read.php
- */
- public function stream_read($count)
- {
- $result = substr($this->currentString, $this->pos, $count);
- $this->pos += $count;
+ /**
+ * Method to read a given number of bytes starting at the current position
+ * and moving to the end of the string defined by the current position plus the
+ * given number.
+ *
+ * @param integer $count Bytes of data from the current position should be returned.
+ *
+ * @return string
+ *
+ * @since 1.7.0
+ *
+ * @link https://www.php.net/manual/en/streamwrapper.stream-read.php
+ */
+ public function stream_read($count)
+ {
+ $result = substr($this->currentString, $this->pos, $count);
+ $this->pos += $count;
- return $result;
- }
+ return $result;
+ }
- /**
- * Stream write, always returning false.
- *
- * @param string $data The data to write.
- *
- * @return boolean
- *
- * @since 1.7.0
- * @note Updating the string is not supported.
- */
- public function stream_write($data)
- {
- // We don't support updating the string.
- return false;
- }
+ /**
+ * Stream write, always returning false.
+ *
+ * @param string $data The data to write.
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ * @note Updating the string is not supported.
+ */
+ public function stream_write($data)
+ {
+ // We don't support updating the string.
+ return false;
+ }
- /**
- * Method to get the current position
- *
- * @return integer The position
- *
- * @since 1.7.0
- */
- public function stream_tell()
- {
- return $this->pos;
- }
+ /**
+ * Method to get the current position
+ *
+ * @return integer The position
+ *
+ * @since 1.7.0
+ */
+ public function stream_tell()
+ {
+ return $this->pos;
+ }
- /**
- * End of field check
- *
- * @return boolean True if at end of field.
- *
- * @since 1.7.0
- */
- public function stream_eof()
- {
- if ($this->pos > $this->len)
- {
- return true;
- }
+ /**
+ * End of field check
+ *
+ * @return boolean True if at end of field.
+ *
+ * @since 1.7.0
+ */
+ public function stream_eof()
+ {
+ if ($this->pos > $this->len) {
+ return true;
+ }
- return false;
- }
+ return false;
+ }
- /**
- * Stream offset
- *
- * @param integer $offset The starting offset.
- * @param integer $whence SEEK_SET, SEEK_CUR, SEEK_END
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function stream_seek($offset, $whence)
- {
- // $whence: SEEK_SET, SEEK_CUR, SEEK_END
- if ($offset > $this->len)
- {
- // We can't seek beyond our len.
- return false;
- }
+ /**
+ * Stream offset
+ *
+ * @param integer $offset The starting offset.
+ * @param integer $whence SEEK_SET, SEEK_CUR, SEEK_END
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function stream_seek($offset, $whence)
+ {
+ // $whence: SEEK_SET, SEEK_CUR, SEEK_END
+ if ($offset > $this->len) {
+ // We can't seek beyond our len.
+ return false;
+ }
- switch ($whence)
- {
- case SEEK_SET:
- $this->pos = $offset;
- break;
+ switch ($whence) {
+ case SEEK_SET:
+ $this->pos = $offset;
+ break;
- case SEEK_CUR:
- if (($this->pos + $offset) < $this->len)
- {
- $this->pos += $offset;
- }
- else
- {
- return false;
- }
- break;
+ case SEEK_CUR:
+ if (($this->pos + $offset) < $this->len) {
+ $this->pos += $offset;
+ } else {
+ return false;
+ }
+ break;
- case SEEK_END:
- $this->pos = $this->len - $offset;
- break;
- }
+ case SEEK_END:
+ $this->pos = $this->len - $offset;
+ break;
+ }
- return true;
- }
+ return true;
+ }
- /**
- * Stream flush, always returns true.
- *
- * @return boolean
- *
- * @since 1.7.0
- * @note Data storage is not supported
- */
- public function stream_flush()
- {
- // We don't store data.
- return true;
- }
+ /**
+ * Stream flush, always returns true.
+ *
+ * @return boolean
+ *
+ * @since 1.7.0
+ * @note Data storage is not supported
+ */
+ public function stream_flush()
+ {
+ // We don't store data.
+ return true;
+ }
}
stream_wrapper_register('string', '\\Joomla\\CMS\\Filesystem\\Streams\\StreamString') or die('StreamString Wrapper Registration Failed');
diff --git a/libraries/src/Filesystem/Support/StringController.php b/libraries/src/Filesystem/Support/StringController.php
index e2edb9d8b1b40..cfe4b02294c1e 100644
--- a/libraries/src/Filesystem/Support/StringController.php
+++ b/libraries/src/Filesystem/Support/StringController.php
@@ -1,4 +1,5 @@
stripUSC = $stripUSC;
- }
-
- /**
- * Returns an input filter object, only creating it if it doesn't already exist.
- *
- * @param array $tagsArray List of user-defined tags
- * @param array $attrArray List of user-defined attributes
- * @param integer $tagsMethod The constant static::ONLY_ALLOW_DEFINED_TAGS or static::BLOCK_DEFINED_TAGS
- * @param integer $attrMethod The constant static::ONLY_ALLOW_DEFINED_ATTRIBUTES or static::BLOCK_DEFINED_ATTRIBUTES
- * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1
- * @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0
- *
- * @return InputFilter The InputFilter object.
- *
- * @since 1.7.0
- */
- public static function getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = 0)
- {
- $sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));
-
- if (empty(self::$instances[$sig]))
- {
- self::$instances[$sig] = new InputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto, $stripUSC);
- }
-
- return self::$instances[$sig];
- }
-
- /**
- * Method to be called by another php script. Processes for XSS and
- * specified bad code.
- *
- * @param mixed $source Input string/array-of-string to be 'cleaned'
- * @param string $type The return type for the variable:
- * INT: An integer, or an array of integers,
- * UINT: An unsigned integer, or an array of unsigned integers,
- * FLOAT: A floating point number, or an array of floating point numbers,
- * BOOLEAN: A boolean value,
- * WORD: A string containing A-Z or underscores only (not case sensitive),
- * ALNUM: A string containing A-Z or 0-9 only (not case sensitive),
- * CMD: A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
- * BASE64: A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
- * STRING: A fully decoded and sanitised string (default),
- * HTML: A sanitised string,
- * ARRAY: An array,
- * PATH: A sanitised file path, or an array of sanitised file paths,
- * TRIM: A string trimmed from normal, non-breaking and multibyte spaces
- * USERNAME: Do not use (use an application specific filter),
- * RAW: The raw string is returned with no filtering,
- * unknown: An unknown filter will act like STRING. If the input is an array it will return an
- * array of fully decoded and sanitised strings.
- *
- * @return mixed 'Cleaned' version of input parameter
- *
- * @since 1.7.0
- */
- public function clean($source, $type = 'string')
- {
- // Strip Unicode Supplementary Characters when requested to do so
- if ($this->stripUSC)
- {
- // Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xE2\xAF\x91", $source) but it'd be slower.
- $source = $this->stripUSC($source);
- }
-
- return parent::clean($source, $type);
- }
-
- /**
- * Function to punyencode utf8 mail when saving content
- *
- * @param string $text The strings to encode
- *
- * @return string The punyencoded mail
- *
- * @since 3.5
- */
- public function emailToPunycode($text)
- {
- $pattern = '/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';
-
- if (preg_match_all($pattern, $text, $matches))
- {
- foreach ($matches[0] as $match)
- {
- $match = (string) str_replace(array('?', '"'), '', $match);
- $text = (string) str_replace($match, PunycodeHelper::emailToPunycode($match), $text);
- }
- }
-
- return $text;
- }
-
- /**
- * Checks an uploaded for suspicious naming and potential PHP contents which could indicate a hacking attempt.
- *
- * The options you can define are:
- * null_byte Prevent files with a null byte in their name (buffer overflow attack)
- * forbidden_extensions Do not allow these strings anywhere in the file's extension
- * php_tag_in_content Do not allow ` true,
-
- // Forbidden string in extension (e.g. php matched .php, .xxx.php, .php.xxx and so on)
- 'forbidden_extensions' => self::FORBIDDEN_FILE_EXTENSIONS,
-
- // true,
-
- // tag in file contents
- 'shorttag_in_content' => true,
-
- // __HALT_COMPILER()
- 'phar_stub_in_content' => true,
-
- // Which file extensions to scan for short tags
- 'shorttag_extensions' => array(
- 'inc', 'phps', 'class', 'php3', 'php4', 'php5', 'php6', 'php7', 'php8', 'txt', 'dat', 'tpl', 'tmpl',
- ),
-
- // Forbidden extensions anywhere in the content
- 'fobidden_ext_in_content' => true,
-
- // Which file extensions to scan for .php in the content
- 'php_ext_content_extensions' => array('zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa'),
- );
-
- $options = array_merge($defaultOptions, $options);
-
- // Make sure we can scan nested file descriptors
- $descriptors = $file;
-
- if (isset($file['name']) && isset($file['tmp_name']))
- {
- $descriptors = static::decodeFileData(
- array(
- $file['name'],
- $file['type'],
- $file['tmp_name'],
- $file['error'],
- $file['size'],
- )
- );
- }
-
- // Handle non-nested descriptors (single files)
- if (isset($descriptors['name']))
- {
- $descriptors = array($descriptors);
- }
-
- // Scan all descriptors detected
- foreach ($descriptors as $fileDescriptor)
- {
- if (!isset($fileDescriptor['name']))
- {
- // This is a nested descriptor. We have to recurse.
- if (!static::isSafeFile($fileDescriptor, $options))
- {
- return false;
- }
-
- continue;
- }
-
- $tempNames = $fileDescriptor['tmp_name'];
- $intendedNames = $fileDescriptor['name'];
-
- if (!\is_array($tempNames))
- {
- $tempNames = array($tempNames);
- }
-
- if (!\is_array($intendedNames))
- {
- $intendedNames = array($intendedNames);
- }
-
- $len = \count($tempNames);
-
- for ($i = 0; $i < $len; $i++)
- {
- $tempName = array_shift($tempNames);
- $intendedName = array_shift($intendedNames);
-
- // 1. Null byte check
- if ($options['null_byte'])
- {
- if (strstr($intendedName, "\x00"))
- {
- return false;
- }
- }
-
- // 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]], .xxx[.yyy[.zzz[...]]].php)
- if (!empty($options['forbidden_extensions']))
- {
- $explodedName = explode('.', $intendedName);
- $explodedName = array_reverse($explodedName);
- array_pop($explodedName);
- $explodedName = array_map('strtolower', $explodedName);
-
- /*
- * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
- * be set, i.e. they should have unique values.
- */
- foreach ($options['forbidden_extensions'] as $ext)
- {
- if (\in_array($ext, $explodedName))
- {
- return false;
- }
- }
- }
-
- // 3. File contents scanner (PHP tag in file contents)
- if ($options['php_tag_in_content']
- || $options['shorttag_in_content'] || $options['phar_stub_in_content']
- || ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions'])))
- {
- $fp = strlen($tempName) ? @fopen($tempName, 'r') : false;
-
- if ($fp !== false)
- {
- $data = '';
-
- while (!feof($fp))
- {
- $data .= @fread($fp, 131072);
-
- if ($options['php_tag_in_content'] && stripos($data, ' $v)
- {
- $result[$k] = static::decodeFileData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
- }
-
- return $result;
- }
-
- return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
- }
-
- /**
- * Try to convert to plaintext
- *
- * @param string $source The source string.
- *
- * @return string Plaintext string
- *
- * @since 3.5
- */
- protected function decode($source)
- {
- static $ttr;
-
- if (!\is_array($ttr))
- {
- // Entity decode
- $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'ISO-8859-1');
-
- foreach ($trans_tbl as $k => $v)
- {
- $ttr[$v] = utf8_encode($k);
- }
- }
-
- $source = strtr($source, $ttr);
-
- // Convert decimal
- $source = preg_replace_callback(
- '/(\d+);/m',
- function ($m) {
- return utf8_encode(\chr($m[1]));
- },
- $source
- );
-
- // Convert hex
- $source = preg_replace_callback(
- '/([a-f0-9]+);/mi',
- function ($m) {
- return utf8_encode(\chr('0x' . $m[1]));
- },
- $source
- );
-
- return $source;
- }
-
- /**
- * Recursively strip Unicode Supplementary Characters from the source. Not: objects cannot be filtered.
- *
- * @param mixed $source The data to filter
- *
- * @return mixed The filtered result
- *
- * @since 3.5
- */
- protected function stripUSC($source)
- {
- if (\is_object($source))
- {
- return $source;
- }
-
- if (\is_array($source))
- {
- $filteredArray = array();
-
- foreach ($source as $k => $v)
- {
- $filteredArray[$k] = $this->stripUSC($v);
- }
-
- return $filteredArray;
- }
-
- return preg_replace('/[\xF0-\xF7].../s', "\xE2\xAF\x91", $source);
- }
+ /**
+ * An array containing a list of extensions for files that are typically
+ * executable directly in the webserver context, potentially resulting in code executions
+ *
+ * @since 4.0.0
+ */
+ public const FORBIDDEN_FILE_EXTENSIONS = [
+ 'php', 'phps', 'pht', 'phtml', 'php3', 'php4', 'php5', 'php6', 'php7', 'asp',
+ 'php8', 'phar', 'inc', 'pl', 'cgi', 'fcgi', 'java', 'jar', 'py', 'aspx'
+ ];
+
+ /**
+ * A flag for Unicode Supplementary Characters (4-byte Unicode character) stripping.
+ *
+ * @var integer
+ * @since 3.5
+ */
+ private $stripUSC = 0;
+
+ /**
+ * A container for InputFilter instances.
+ *
+ * @var InputFilter[]
+ * @since 4.0.0
+ */
+ protected static $instances = array();
+ /**
+ * Constructor for inputFilter class. Only first parameter is required.
+ *
+ * @param array $tagsArray List of user-defined tags
+ * @param array $attrArray List of user-defined attributes
+ * @param integer $tagsMethod The constant static::ONLY_ALLOW_DEFINED_TAGS or static::BLOCK_DEFINED_TAGS
+ * @param integer $attrMethod The constant static::ONLY_ALLOW_DEFINED_ATTRIBUTES or static::BLOCK_DEFINED_ATTRIBUTES
+ * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1
+ * @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0
+ *
+ * @since 1.7.0
+ */
+ public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = 0)
+ {
+ parent::__construct($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
+
+ // Assign member variables
+ $this->stripUSC = $stripUSC;
+ }
+
+ /**
+ * Returns an input filter object, only creating it if it doesn't already exist.
+ *
+ * @param array $tagsArray List of user-defined tags
+ * @param array $attrArray List of user-defined attributes
+ * @param integer $tagsMethod The constant static::ONLY_ALLOW_DEFINED_TAGS or static::BLOCK_DEFINED_TAGS
+ * @param integer $attrMethod The constant static::ONLY_ALLOW_DEFINED_ATTRIBUTES or static::BLOCK_DEFINED_ATTRIBUTES
+ * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blocked tags/attributes = 1
+ * @param integer $stripUSC Strip 4-byte unicode characters = 1, no strip = 0
+ *
+ * @return InputFilter The InputFilter object.
+ *
+ * @since 1.7.0
+ */
+ public static function getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = 0)
+ {
+ $sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));
+
+ if (empty(self::$instances[$sig])) {
+ self::$instances[$sig] = new InputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto, $stripUSC);
+ }
+
+ return self::$instances[$sig];
+ }
+
+ /**
+ * Method to be called by another php script. Processes for XSS and
+ * specified bad code.
+ *
+ * @param mixed $source Input string/array-of-string to be 'cleaned'
+ * @param string $type The return type for the variable:
+ * INT: An integer, or an array of integers,
+ * UINT: An unsigned integer, or an array of unsigned integers,
+ * FLOAT: A floating point number, or an array of floating point numbers,
+ * BOOLEAN: A boolean value,
+ * WORD: A string containing A-Z or underscores only (not case sensitive),
+ * ALNUM: A string containing A-Z or 0-9 only (not case sensitive),
+ * CMD: A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
+ * BASE64: A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
+ * STRING: A fully decoded and sanitised string (default),
+ * HTML: A sanitised string,
+ * ARRAY: An array,
+ * PATH: A sanitised file path, or an array of sanitised file paths,
+ * TRIM: A string trimmed from normal, non-breaking and multibyte spaces
+ * USERNAME: Do not use (use an application specific filter),
+ * RAW: The raw string is returned with no filtering,
+ * unknown: An unknown filter will act like STRING. If the input is an array it will return an
+ * array of fully decoded and sanitised strings.
+ *
+ * @return mixed 'Cleaned' version of input parameter
+ *
+ * @since 1.7.0
+ */
+ public function clean($source, $type = 'string')
+ {
+ // Strip Unicode Supplementary Characters when requested to do so
+ if ($this->stripUSC) {
+ // Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xE2\xAF\x91", $source) but it'd be slower.
+ $source = $this->stripUSC($source);
+ }
+
+ return parent::clean($source, $type);
+ }
+
+ /**
+ * Function to punyencode utf8 mail when saving content
+ *
+ * @param string $text The strings to encode
+ *
+ * @return string The punyencoded mail
+ *
+ * @since 3.5
+ */
+ public function emailToPunycode($text)
+ {
+ $pattern = '/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';
+
+ if (preg_match_all($pattern, $text, $matches)) {
+ foreach ($matches[0] as $match) {
+ $match = (string) str_replace(array('?', '"'), '', $match);
+ $text = (string) str_replace($match, PunycodeHelper::emailToPunycode($match), $text);
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Checks an uploaded for suspicious naming and potential PHP contents which could indicate a hacking attempt.
+ *
+ * The options you can define are:
+ * null_byte Prevent files with a null byte in their name (buffer overflow attack)
+ * forbidden_extensions Do not allow these strings anywhere in the file's extension
+ * php_tag_in_content Do not allow ` true,
+
+ // Forbidden string in extension (e.g. php matched .php, .xxx.php, .php.xxx and so on)
+ 'forbidden_extensions' => self::FORBIDDEN_FILE_EXTENSIONS,
+
+ // true,
+
+ // tag in file contents
+ 'shorttag_in_content' => true,
+
+ // __HALT_COMPILER()
+ 'phar_stub_in_content' => true,
+
+ // Which file extensions to scan for short tags
+ 'shorttag_extensions' => array(
+ 'inc', 'phps', 'class', 'php3', 'php4', 'php5', 'php6', 'php7', 'php8', 'txt', 'dat', 'tpl', 'tmpl',
+ ),
+
+ // Forbidden extensions anywhere in the content
+ 'fobidden_ext_in_content' => true,
+
+ // Which file extensions to scan for .php in the content
+ 'php_ext_content_extensions' => array('zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa'),
+ );
+
+ $options = array_merge($defaultOptions, $options);
+
+ // Make sure we can scan nested file descriptors
+ $descriptors = $file;
+
+ if (isset($file['name']) && isset($file['tmp_name'])) {
+ $descriptors = static::decodeFileData(
+ array(
+ $file['name'],
+ $file['type'],
+ $file['tmp_name'],
+ $file['error'],
+ $file['size'],
+ )
+ );
+ }
+
+ // Handle non-nested descriptors (single files)
+ if (isset($descriptors['name'])) {
+ $descriptors = array($descriptors);
+ }
+
+ // Scan all descriptors detected
+ foreach ($descriptors as $fileDescriptor) {
+ if (!isset($fileDescriptor['name'])) {
+ // This is a nested descriptor. We have to recurse.
+ if (!static::isSafeFile($fileDescriptor, $options)) {
+ return false;
+ }
+
+ continue;
+ }
+
+ $tempNames = $fileDescriptor['tmp_name'];
+ $intendedNames = $fileDescriptor['name'];
+
+ if (!\is_array($tempNames)) {
+ $tempNames = array($tempNames);
+ }
+
+ if (!\is_array($intendedNames)) {
+ $intendedNames = array($intendedNames);
+ }
+
+ $len = \count($tempNames);
+
+ for ($i = 0; $i < $len; $i++) {
+ $tempName = array_shift($tempNames);
+ $intendedName = array_shift($intendedNames);
+
+ // 1. Null byte check
+ if ($options['null_byte']) {
+ if (strstr($intendedName, "\x00")) {
+ return false;
+ }
+ }
+
+ // 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]], .xxx[.yyy[.zzz[...]]].php)
+ if (!empty($options['forbidden_extensions'])) {
+ $explodedName = explode('.', $intendedName);
+ $explodedName = array_reverse($explodedName);
+ array_pop($explodedName);
+ $explodedName = array_map('strtolower', $explodedName);
+
+ /*
+ * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
+ * be set, i.e. they should have unique values.
+ */
+ foreach ($options['forbidden_extensions'] as $ext) {
+ if (\in_array($ext, $explodedName)) {
+ return false;
+ }
+ }
+ }
+
+ // 3. File contents scanner (PHP tag in file contents)
+ if (
+ $options['php_tag_in_content']
+ || $options['shorttag_in_content'] || $options['phar_stub_in_content']
+ || ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions']))
+ ) {
+ $fp = strlen($tempName) ? @fopen($tempName, 'r') : false;
+
+ if ($fp !== false) {
+ $data = '';
+
+ while (!feof($fp)) {
+ $data .= @fread($fp, 131072);
+
+ if ($options['php_tag_in_content'] && stripos($data, ' $v) {
+ $result[$k] = static::decodeFileData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
+ }
+
+ return $result;
+ }
+
+ return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
+ }
+
+ /**
+ * Try to convert to plaintext
+ *
+ * @param string $source The source string.
+ *
+ * @return string Plaintext string
+ *
+ * @since 3.5
+ */
+ protected function decode($source)
+ {
+ static $ttr;
+
+ if (!\is_array($ttr)) {
+ // Entity decode
+ $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'ISO-8859-1');
+
+ foreach ($trans_tbl as $k => $v) {
+ $ttr[$v] = utf8_encode($k);
+ }
+ }
+
+ $source = strtr($source, $ttr);
+
+ // Convert decimal
+ $source = preg_replace_callback(
+ '/(\d+);/m',
+ function ($m) {
+ return utf8_encode(\chr($m[1]));
+ },
+ $source
+ );
+
+ // Convert hex
+ $source = preg_replace_callback(
+ '/([a-f0-9]+);/mi',
+ function ($m) {
+ return utf8_encode(\chr('0x' . $m[1]));
+ },
+ $source
+ );
+
+ return $source;
+ }
+
+ /**
+ * Recursively strip Unicode Supplementary Characters from the source. Not: objects cannot be filtered.
+ *
+ * @param mixed $source The data to filter
+ *
+ * @return mixed The filtered result
+ *
+ * @since 3.5
+ */
+ protected function stripUSC($source)
+ {
+ if (\is_object($source)) {
+ return $source;
+ }
+
+ if (\is_array($source)) {
+ $filteredArray = array();
+
+ foreach ($source as $k => $v) {
+ $filteredArray[$k] = $this->stripUSC($v);
+ }
+
+ return $filteredArray;
+ }
+
+ return preg_replace('/[\xF0-\xF7].../s', "\xE2\xAF\x91", $source);
+ }
}
diff --git a/libraries/src/Filter/OutputFilter.php b/libraries/src/Filter/OutputFilter.php
index 358825e8a2b43..c85e9ccd67bf5 100644
--- a/libraries/src/Filter/OutputFilter.php
+++ b/libraries/src/Filter/OutputFilter.php
@@ -1,4 +1,5 @@
transliterate($str);
-
- // Trim white spaces at beginning and end of alias and make lowercase
- $str = trim(StringHelper::strtolower($str));
-
- // Remove any apostrophe. We do it here to ensure it is not replaced by a '-'
- $str = str_replace("'", '', $str);
-
- // Remove any duplicate whitespace, and ensure all characters are alphanumeric
- $str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-', $str);
-
- // Trim dashes at beginning and end of alias
- $str = trim($str, '-');
-
- return $str;
- }
-
- /**
- * Callback method for replacing & with & in a string
- *
- * @param string $m String to process
- *
- * @return string Replaced string
- *
- * @since 3.5
- */
- public static function ampReplaceCallback($m)
- {
- $rx = '&(?!amp;)';
-
- return preg_replace('#' . $rx . '#', '&', $m[0]);
- }
+ /**
+ * This method processes a string and replaces all instances of & with & in links only.
+ *
+ * @param string $input String to process
+ *
+ * @return string Processed string
+ *
+ * @since 1.7.0
+ */
+ public static function linkXHTMLSafe($input)
+ {
+ $regex = 'href="([^"]*(&(amp;){0})[^"]*)*?"';
+
+ return preg_replace_callback("#$regex#i", array('\\Joomla\\CMS\\Filter\\OutputFilter', 'ampReplaceCallback'), $input);
+ }
+
+ /**
+ * This method processes a string and escapes it for use in JavaScript
+ *
+ * @param string $string String to process
+ *
+ * @return string Processed text
+ */
+ public static function stringJSSafe($string)
+ {
+ $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
+ $new_str = '';
+
+ foreach ($chars as $chr) {
+ $code = str_pad(dechex(StringHelper::ord($chr)), 4, '0', STR_PAD_LEFT);
+
+ if (strlen($code) < 5) {
+ $new_str .= '\\u' . $code;
+ } else {
+ $new_str .= '\\u{' . $code . '}';
+ }
+ }
+
+ return $new_str;
+ }
+
+ /**
+ * This method processes a string and replaces all accented UTF-8 characters by unaccented
+ * ASCII-7 "equivalents", whitespaces are replaced by hyphens and the string is lowercase.
+ *
+ * @param string $string String to process
+ * @param string $language Language to transliterate to
+ *
+ * @return string Processed string
+ *
+ * @since 1.7.0
+ */
+ public static function stringURLSafe($string, $language = '')
+ {
+ // Remove any '-' from the string since they will be used as concatenaters
+ $str = str_replace('-', ' ', $string);
+
+ // Transliterate on the language requested (fallback to current language if not specified)
+ $lang = $language == '' || $language == '*' ? Factory::getLanguage() : Language::getInstance($language);
+ $str = $lang->transliterate($str);
+
+ // Trim white spaces at beginning and end of alias and make lowercase
+ $str = trim(StringHelper::strtolower($str));
+
+ // Remove any apostrophe. We do it here to ensure it is not replaced by a '-'
+ $str = str_replace("'", '', $str);
+
+ // Remove any duplicate whitespace, and ensure all characters are alphanumeric
+ $str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-', $str);
+
+ // Trim dashes at beginning and end of alias
+ $str = trim($str, '-');
+
+ return $str;
+ }
+
+ /**
+ * Callback method for replacing & with & in a string
+ *
+ * @param string $m String to process
+ *
+ * @return string Replaced string
+ *
+ * @since 3.5
+ */
+ public static function ampReplaceCallback($m)
+ {
+ $rx = '&(?!amp;)';
+
+ return preg_replace('#' . $rx . '#', '&', $m[0]);
+ }
}
diff --git a/libraries/src/Form/Field/AccessiblemediaField.php b/libraries/src/Form/Field/AccessiblemediaField.php
index ea1f7e2804816..82e013047c670 100644
--- a/libraries/src/Form/Field/AccessiblemediaField.php
+++ b/libraries/src/Form/Field/AccessiblemediaField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 4.0.0
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'directory':
- case 'preview':
- $this->$name = (string) $value;
- break;
-
- case 'previewHeight':
- case 'previewWidth':
- $this->$name = (int) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the
tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value.
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- /**
- * When you have subforms which are not repeatable (i.e. a subform custom field with the
- * repeat attribute set to 0) you get an array here since the data comes from decoding the
- * JSON into an associative array, including the media subfield's data.
- *
- * However, this method expects an object or a string, not an array. Typecasting the array
- * to an object solves the data format discrepancy.
- */
- $value = is_array($value) ? (object) $value : $value;
-
- /**
- * If the value is not a string, it is
- * most likely within a custom field of type subform
- * and the value is a stdClass with properties
- * imagefile and alt_text. So it is fine.
- */
- if (\is_string($value))
- {
- json_decode($value);
-
- // Check if value is a valid JSON string.
- if ($value !== '' && json_last_error() !== JSON_ERROR_NONE)
- {
- /**
- * If the value is not empty and is not a valid JSON string,
- * it is most likely a custom field created in Joomla 3 and
- * the value is a string that contains the file name.
- */
- if (is_file(JPATH_ROOT . '/' . $value))
- {
- $value = '{"imagefile":"' . $value . '","alt_text":""}';
- }
- else
- {
- $value = '';
- }
- }
- }
- elseif (!is_object($value)
- || !property_exists($value, 'imagefile')
- || !property_exists($value, 'alt_text'))
- {
- return false;
- }
-
- if (!parent::setup($element, $value, $group))
- {
- return false;
- }
-
- $this->directory = (string) $this->element['directory'];
- $this->preview = (string) $this->element['preview'];
- $this->previewHeight = isset($this->element['preview_height']) ? (int) $this->element['preview_height'] : 200;
- $this->previewWidth = isset($this->element['preview_width']) ? (int) $this->element['preview_width'] : 200;
-
- $xml = <<$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'directory':
+ case 'preview':
+ $this->$name = (string) $value;
+ break;
+
+ case 'previewHeight':
+ case 'previewWidth':
+ $this->$name = (int) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value.
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ /**
+ * When you have subforms which are not repeatable (i.e. a subform custom field with the
+ * repeat attribute set to 0) you get an array here since the data comes from decoding the
+ * JSON into an associative array, including the media subfield's data.
+ *
+ * However, this method expects an object or a string, not an array. Typecasting the array
+ * to an object solves the data format discrepancy.
+ */
+ $value = is_array($value) ? (object) $value : $value;
+
+ /**
+ * If the value is not a string, it is
+ * most likely within a custom field of type subform
+ * and the value is a stdClass with properties
+ * imagefile and alt_text. So it is fine.
+ */
+ if (\is_string($value)) {
+ json_decode($value);
+
+ // Check if value is a valid JSON string.
+ if ($value !== '' && json_last_error() !== JSON_ERROR_NONE) {
+ /**
+ * If the value is not empty and is not a valid JSON string,
+ * it is most likely a custom field created in Joomla 3 and
+ * the value is a string that contains the file name.
+ */
+ if (is_file(JPATH_ROOT . '/' . $value)) {
+ $value = '{"imagefile":"' . $value . '","alt_text":""}';
+ } else {
+ $value = '';
+ }
+ }
+ } elseif (
+ !is_object($value)
+ || !property_exists($value, 'imagefile')
+ || !property_exists($value, 'alt_text')
+ ) {
+ return false;
+ }
+
+ if (!parent::setup($element, $value, $group)) {
+ return false;
+ }
+
+ $this->directory = (string) $this->element['directory'];
+ $this->preview = (string) $this->element['preview'];
+ $this->previewHeight = isset($this->element['preview_height']) ? (int) $this->element['preview_height'] : 200;
+ $this->previewWidth = isset($this->element['preview_width']) ? (int) $this->element['preview_width'] : 200;
+
+ $xml = <<
XML;
- $this->formsource = $xml;
+ $this->formsource = $xml;
- $this->layout = 'joomla.form.field.media.accessiblemedia';
+ $this->layout = 'joomla.form.field.media.accessiblemedia';
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Field/AccesslevelField.php b/libraries/src/Form/Field/AccesslevelField.php
index d3a78f884a0e7..a73ca2b11ca2a 100644
--- a/libraries/src/Form/Field/AccesslevelField.php
+++ b/libraries/src/Form/Field/AccesslevelField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- 'DISTINCT ' . $db->quoteName('type_alias', 'value'),
- $db->quoteName('type_alias', 'text'),
- ]
- )
- ->from($db->quoteName('#__contentitem_tag_map'));
- $db->setQuery($query);
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 3.6
+ */
+ protected function getOptions()
+ {
+ // Get list of tag type alias
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ 'DISTINCT ' . $db->quoteName('type_alias', 'value'),
+ $db->quoteName('type_alias', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__contentitem_tag_map'));
+ $db->setQuery($query);
- $options = $db->loadObjectList();
+ $options = $db->loadObjectList();
- $lang = Factory::getLanguage();
+ $lang = Factory::getLanguage();
- foreach ($options as $i => $item)
- {
- $parts = explode('.', $item->value);
- $extension = $parts[0];
- $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($extension, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $extension));
- $options[$i]->text = Text::_(strtoupper($extension) . '_TAGS_' . strtoupper($parts[1]));
- }
+ foreach ($options as $i => $item) {
+ $parts = explode('.', $item->value);
+ $extension = $parts[0];
+ $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($extension, Path::clean(JPATH_ADMINISTRATOR . '/components/' . $extension));
+ $options[$i]->text = Text::_(strtoupper($extension) . '_TAGS_' . strtoupper($parts[1]));
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- // Sort by language value
- usort(
- $options,
- function ($a, $b)
- {
- return strcmp($a->text, $b->text);
- }
- );
+ // Sort by language value
+ usort(
+ $options,
+ function ($a, $b) {
+ return strcmp($a->text, $b->text);
+ }
+ );
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/AuthorField.php b/libraries/src/Form/Field/AuthorField.php
index 48d0f3cb6678f..bf64a8bd0bbcb 100644
--- a/libraries/src/Form/Field/AuthorField.php
+++ b/libraries/src/Form/Field/AuthorField.php
@@ -1,4 +1,5 @@
element);
+ /**
+ * Method to get the options to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.2
+ */
+ protected function getOptions()
+ {
+ // Accepted modifiers
+ $hash = md5($this->element);
- if (!isset(static::$options[$hash]))
- {
- static::$options[$hash] = parent::getOptions();
+ if (!isset(static::$options[$hash])) {
+ static::$options[$hash] = parent::getOptions();
- $db = $this->getDatabase();
+ $db = $this->getDatabase();
- // Construct the query
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('u.id', 'value'),
- $db->quoteName('u.name', 'text'),
- ]
- )
- ->from($db->quoteName('#__users', 'u'))
- ->join('INNER', $db->quoteName('#__content', 'c'), $db->quoteName('c.created_by') . ' = ' . $db->quoteName('u.id'))
- ->group(
- [
- $db->quoteName('u.id'),
- $db->quoteName('u.name'),
- ]
- )
- ->order($db->quoteName('u.name'));
+ // Construct the query
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('u.id', 'value'),
+ $db->quoteName('u.name', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__users', 'u'))
+ ->join('INNER', $db->quoteName('#__content', 'c'), $db->quoteName('c.created_by') . ' = ' . $db->quoteName('u.id'))
+ ->group(
+ [
+ $db->quoteName('u.id'),
+ $db->quoteName('u.name'),
+ ]
+ )
+ ->order($db->quoteName('u.name'));
- // Setup the query
- $db->setQuery($query);
+ // Setup the query
+ $db->setQuery($query);
- // Return the result
- if ($options = $db->loadObjectList())
- {
- static::$options[$hash] = array_merge(static::$options[$hash], $options);
- }
- }
+ // Return the result
+ if ($options = $db->loadObjectList()) {
+ static::$options[$hash] = array_merge(static::$options[$hash], $options);
+ }
+ }
- return static::$options[$hash];
- }
+ return static::$options[$hash];
+ }
}
diff --git a/libraries/src/Form/Field/CachehandlerField.php b/libraries/src/Form/Field/CachehandlerField.php
index 3217717f9e718..dfc8612c9f142 100644
--- a/libraries/src/Form/Field/CachehandlerField.php
+++ b/libraries/src/Form/Field/CachehandlerField.php
@@ -1,4 +1,5 @@
name array.
- foreach (Cache::getStores() as $store)
- {
- $options[] = HTMLHelper::_('select.option', $store, Text::_('JLIB_FORM_VALUE_CACHE_' . $store), 'value', 'text');
- }
-
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Cachehandler';
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ // Convert to name => name array.
+ foreach (Cache::getStores() as $store) {
+ $options[] = HTMLHelper::_('select.option', $store, Text::_('JLIB_FORM_VALUE_CACHE_' . $store), 'value', 'text');
+ }
+
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/CalendarField.php b/libraries/src/Form/Field/CalendarField.php
index 8a48558d2b604..6b89d76d55513 100644
--- a/libraries/src/Form/Field/CalendarField.php
+++ b/libraries/src/Form/Field/CalendarField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'maxlength':
- case 'timeformat':
- $this->$name = (int) $value;
- break;
- case 'todaybutton':
- case 'singleheader':
- case 'weeknumbers':
- case 'showtime':
- case 'filltable':
- case 'format':
- case 'filterFormat':
- case 'filter':
- case 'minyear':
- case 'maxyear':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->maxlength = (int) $this->element['maxlength'] ? (int) $this->element['maxlength'] : 45;
- $this->format = (string) $this->element['format'] ? (string) $this->element['format'] : '%Y-%m-%d';
- $this->filter = (string) $this->element['filter'] ? (string) $this->element['filter'] : 'USER_UTC';
- $this->todaybutton = (string) $this->element['todaybutton'] ? (string) $this->element['todaybutton'] : 'true';
- $this->weeknumbers = (string) $this->element['weeknumbers'] ? (string) $this->element['weeknumbers'] : 'true';
- $this->showtime = (string) $this->element['showtime'] ? (string) $this->element['showtime'] : 'false';
- $this->filltable = (string) $this->element['filltable'] ? (string) $this->element['filltable'] : 'true';
- $this->timeformat = (int) $this->element['timeformat'] ? (int) $this->element['timeformat'] : 24;
- $this->singleheader = (string) $this->element['singleheader'] ? (string) $this->element['singleheader'] : 'false';
- $this->minyear = \strlen((string) $this->element['minyear']) ? (string) $this->element['minyear'] : null;
- $this->maxyear = \strlen((string) $this->element['maxyear']) ? (string) $this->element['maxyear'] : null;
-
- if ($this->maxyear < 0 || $this->minyear > 0)
- {
- $this->todaybutton = 'false';
- }
-
- $translateFormat = (string) $this->element['translateformat'];
-
- if ($translateFormat && $translateFormat !== 'false')
- {
- $showTime = (string) $this->element['showtime'];
-
- $lang = Factory::getLanguage();
- $debug = $lang->setDebug(false);
-
- if ($showTime && $showTime !== 'false')
- {
- $this->format = Text::_('DATE_FORMAT_CALENDAR_DATETIME');
- $this->filterFormat = Text::_('DATE_FORMAT_FILTER_DATETIME');
- }
- else
- {
- $this->format = Text::_('DATE_FORMAT_CALENDAR_DATE');
- $this->filterFormat = Text::_('DATE_FORMAT_FILTER_DATE');
- }
-
- $lang->setDebug($debug);
- }
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- $user = Factory::getApplication()->getIdentity();
-
- // If a known filter is given use it.
- switch (strtoupper($this->filter))
- {
- case 'SERVER_UTC':
- // Convert a date to UTC based on the server timezone.
- if ($this->value && $this->value != $this->getDatabase()->getNullDate())
- {
- // Get a date object based on the correct timezone.
- $date = Factory::getDate($this->value, 'UTC');
- $date->setTimezone(new \DateTimeZone(Factory::getApplication()->get('offset')));
-
- // Transform the date string.
- $this->value = $date->format('Y-m-d H:i:s', true, false);
- }
- break;
- case 'USER_UTC':
- // Convert a date to UTC based on the user timezone.
- if ($this->value && $this->value != $this->getDatabase()->getNullDate())
- {
- // Get a date object based on the correct timezone.
- $date = Factory::getDate($this->value, 'UTC');
- $date->setTimezone($user->getTimezone());
-
- // Transform the date string.
- $this->value = $date->format('Y-m-d H:i:s', true, false);
- }
- break;
- }
-
- // Format value when not nulldate ('0000-00-00 00:00:00'), otherwise blank it as it would result in 1970-01-01.
- if ($this->value && $this->value != $this->getDatabase()->getNullDate() && strtotime($this->value) !== false)
- {
- $tz = date_default_timezone_get();
- date_default_timezone_set('UTC');
-
- if ($this->filterFormat)
- {
- $date = \DateTimeImmutable::createFromFormat('U', strtotime($this->value));
- $this->value = $date->format($this->filterFormat);
- }
- else
- {
- $this->value = strftime($this->format, strtotime($this->value));
- }
-
- date_default_timezone_set($tz);
- }
- else
- {
- $this->value = '';
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
- $lang = Factory::getApplication()->getLanguage();
- $calendar = $lang->getCalendar();
- $direction = strtolower(Factory::getDocument()->getDirection());
-
- // Get the appropriate file for the current language date helper
- $helperPath = 'system/fields/calendar-locales/date/gregorian/date-helper.min.js';
-
- if ($calendar && is_dir(JPATH_ROOT . '/media/system/js/fields/calendar-locales/date/' . strtolower($calendar)))
- {
- $helperPath = 'system/fields/calendar-locales/date/' . strtolower($calendar) . '/date-helper.min.js';
- }
-
- $extraData = array(
- 'value' => $this->value,
- 'maxLength' => $this->maxlength,
- 'format' => $this->format,
- 'filter' => $this->filter,
- 'todaybutton' => ($this->todaybutton === 'true') ? 1 : 0,
- 'weeknumbers' => ($this->weeknumbers === 'true') ? 1 : 0,
- 'showtime' => ($this->showtime === 'true') ? 1 : 0,
- 'filltable' => ($this->filltable === 'true') ? 1 : 0,
- 'timeformat' => $this->timeformat,
- 'singleheader' => ($this->singleheader === 'true') ? 1 : 0,
- 'helperPath' => $helperPath,
- 'minYear' => $this->minyear,
- 'maxYear' => $this->maxyear,
- 'direction' => $direction,
- 'calendar' => $calendar,
- 'firstday' => $lang->getFirstDay(),
- 'weekend' => explode(',', $lang->getWeekEnd()),
- );
-
- return array_merge($data, $extraData);
- }
-
- /**
- * Method to filter a field value.
- *
- * @param mixed $value The optional value to use as the default for the field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param Registry $input An optional Registry object with the entire data set to filter
- * against the entire form.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter($value, $group = null, Registry $input = null)
- {
- // Make sure there is a valid SimpleXMLElement.
- if (!($this->element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
- }
-
- if ((int) $value <= 0)
- {
- return '';
- }
-
- if ($this->filterFormat)
- {
- $value = DateTime::createFromFormat($this->filterFormat, $value)->format('Y-m-d H:i:s');
- }
-
- $app = Factory::getApplication();
-
- // Get the field filter type.
- $filter = (string) $this->element['filter'];
-
- $return = $value;
-
- switch (strtoupper($filter))
- {
- // Convert a date to UTC based on the server timezone offset.
- case 'SERVER_UTC':
- // Return an SQL formatted datetime string in UTC.
- $return = Factory::getDate($value, $app->get('offset'))->toSql();
- break;
-
- // Convert a date to UTC based on the user timezone offset.
- case 'USER_UTC':
- // Get the user timezone setting defaulting to the server timezone setting.
- $offset = $app->getIdentity()->getParam('timezone', $app->get('offset'));
-
- // Return an SQL formatted datetime string in UTC.
- $return = Factory::getDate($value, $offset)->toSql();
- break;
- }
-
- return $return;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Calendar';
+
+ /**
+ * The allowable maxlength of calendar field.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $maxlength;
+
+ /**
+ * The format of date and time.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $format;
+
+ /**
+ * The format will be used to filter submitted date and time.
+ *
+ * @var string
+ * @since 4.0.1
+ */
+ protected $filterFormat;
+
+ /**
+ * The filter.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $filter;
+
+ /**
+ * The minimum year number to subtract/add from the current year
+ *
+ * @var integer
+ * @since 3.7.0
+ */
+ protected $minyear;
+
+ /**
+ * The maximum year number to subtract/add from the current year
+ *
+ * @var integer
+ * @since 3.7.0
+ */
+ protected $maxyear;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $layout = 'joomla.form.field.calendar';
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'maxlength':
+ case 'format':
+ case 'filterFormat':
+ case 'filter':
+ case 'timeformat':
+ case 'todaybutton':
+ case 'singleheader':
+ case 'weeknumbers':
+ case 'showtime':
+ case 'filltable':
+ case 'minyear':
+ case 'maxyear':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'maxlength':
+ case 'timeformat':
+ $this->$name = (int) $value;
+ break;
+ case 'todaybutton':
+ case 'singleheader':
+ case 'weeknumbers':
+ case 'showtime':
+ case 'filltable':
+ case 'format':
+ case 'filterFormat':
+ case 'filter':
+ case 'minyear':
+ case 'maxyear':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->maxlength = (int) $this->element['maxlength'] ? (int) $this->element['maxlength'] : 45;
+ $this->format = (string) $this->element['format'] ? (string) $this->element['format'] : '%Y-%m-%d';
+ $this->filter = (string) $this->element['filter'] ? (string) $this->element['filter'] : 'USER_UTC';
+ $this->todaybutton = (string) $this->element['todaybutton'] ? (string) $this->element['todaybutton'] : 'true';
+ $this->weeknumbers = (string) $this->element['weeknumbers'] ? (string) $this->element['weeknumbers'] : 'true';
+ $this->showtime = (string) $this->element['showtime'] ? (string) $this->element['showtime'] : 'false';
+ $this->filltable = (string) $this->element['filltable'] ? (string) $this->element['filltable'] : 'true';
+ $this->timeformat = (int) $this->element['timeformat'] ? (int) $this->element['timeformat'] : 24;
+ $this->singleheader = (string) $this->element['singleheader'] ? (string) $this->element['singleheader'] : 'false';
+ $this->minyear = \strlen((string) $this->element['minyear']) ? (string) $this->element['minyear'] : null;
+ $this->maxyear = \strlen((string) $this->element['maxyear']) ? (string) $this->element['maxyear'] : null;
+
+ if ($this->maxyear < 0 || $this->minyear > 0) {
+ $this->todaybutton = 'false';
+ }
+
+ $translateFormat = (string) $this->element['translateformat'];
+
+ if ($translateFormat && $translateFormat !== 'false') {
+ $showTime = (string) $this->element['showtime'];
+
+ $lang = Factory::getLanguage();
+ $debug = $lang->setDebug(false);
+
+ if ($showTime && $showTime !== 'false') {
+ $this->format = Text::_('DATE_FORMAT_CALENDAR_DATETIME');
+ $this->filterFormat = Text::_('DATE_FORMAT_FILTER_DATETIME');
+ } else {
+ $this->format = Text::_('DATE_FORMAT_CALENDAR_DATE');
+ $this->filterFormat = Text::_('DATE_FORMAT_FILTER_DATE');
+ }
+
+ $lang->setDebug($debug);
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ $user = Factory::getApplication()->getIdentity();
+
+ // If a known filter is given use it.
+ switch (strtoupper($this->filter)) {
+ case 'SERVER_UTC':
+ // Convert a date to UTC based on the server timezone.
+ if ($this->value && $this->value != $this->getDatabase()->getNullDate()) {
+ // Get a date object based on the correct timezone.
+ $date = Factory::getDate($this->value, 'UTC');
+ $date->setTimezone(new \DateTimeZone(Factory::getApplication()->get('offset')));
+
+ // Transform the date string.
+ $this->value = $date->format('Y-m-d H:i:s', true, false);
+ }
+ break;
+ case 'USER_UTC':
+ // Convert a date to UTC based on the user timezone.
+ if ($this->value && $this->value != $this->getDatabase()->getNullDate()) {
+ // Get a date object based on the correct timezone.
+ $date = Factory::getDate($this->value, 'UTC');
+ $date->setTimezone($user->getTimezone());
+
+ // Transform the date string.
+ $this->value = $date->format('Y-m-d H:i:s', true, false);
+ }
+ break;
+ }
+
+ // Format value when not nulldate ('0000-00-00 00:00:00'), otherwise blank it as it would result in 1970-01-01.
+ if ($this->value && $this->value != $this->getDatabase()->getNullDate() && strtotime($this->value) !== false) {
+ $tz = date_default_timezone_get();
+ date_default_timezone_set('UTC');
+
+ if ($this->filterFormat) {
+ $date = \DateTimeImmutable::createFromFormat('U', strtotime($this->value));
+ $this->value = $date->format($this->filterFormat);
+ } else {
+ $this->value = strftime($this->format, strtotime($this->value));
+ }
+
+ date_default_timezone_set($tz);
+ } else {
+ $this->value = '';
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+ $lang = Factory::getApplication()->getLanguage();
+ $calendar = $lang->getCalendar();
+ $direction = strtolower(Factory::getDocument()->getDirection());
+
+ // Get the appropriate file for the current language date helper
+ $helperPath = 'system/fields/calendar-locales/date/gregorian/date-helper.min.js';
+
+ if ($calendar && is_dir(JPATH_ROOT . '/media/system/js/fields/calendar-locales/date/' . strtolower($calendar))) {
+ $helperPath = 'system/fields/calendar-locales/date/' . strtolower($calendar) . '/date-helper.min.js';
+ }
+
+ $extraData = array(
+ 'value' => $this->value,
+ 'maxLength' => $this->maxlength,
+ 'format' => $this->format,
+ 'filter' => $this->filter,
+ 'todaybutton' => ($this->todaybutton === 'true') ? 1 : 0,
+ 'weeknumbers' => ($this->weeknumbers === 'true') ? 1 : 0,
+ 'showtime' => ($this->showtime === 'true') ? 1 : 0,
+ 'filltable' => ($this->filltable === 'true') ? 1 : 0,
+ 'timeformat' => $this->timeformat,
+ 'singleheader' => ($this->singleheader === 'true') ? 1 : 0,
+ 'helperPath' => $helperPath,
+ 'minYear' => $this->minyear,
+ 'maxYear' => $this->maxyear,
+ 'direction' => $direction,
+ 'calendar' => $calendar,
+ 'firstday' => $lang->getFirstDay(),
+ 'weekend' => explode(',', $lang->getWeekEnd()),
+ );
+
+ return array_merge($data, $extraData);
+ }
+
+ /**
+ * Method to filter a field value.
+ *
+ * @param mixed $value The optional value to use as the default for the field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param Registry $input An optional Registry object with the entire data set to filter
+ * against the entire form.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter($value, $group = null, Registry $input = null)
+ {
+ // Make sure there is a valid SimpleXMLElement.
+ if (!($this->element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
+ }
+
+ if ((int) $value <= 0) {
+ return '';
+ }
+
+ if ($this->filterFormat) {
+ $value = DateTime::createFromFormat($this->filterFormat, $value)->format('Y-m-d H:i:s');
+ }
+
+ $app = Factory::getApplication();
+
+ // Get the field filter type.
+ $filter = (string) $this->element['filter'];
+
+ $return = $value;
+
+ switch (strtoupper($filter)) {
+ // Convert a date to UTC based on the server timezone offset.
+ case 'SERVER_UTC':
+ // Return an SQL formatted datetime string in UTC.
+ $return = Factory::getDate($value, $app->get('offset'))->toSql();
+ break;
+
+ // Convert a date to UTC based on the user timezone offset.
+ case 'USER_UTC':
+ // Get the user timezone setting defaulting to the server timezone setting.
+ $offset = $app->getIdentity()->getParam('timezone', $app->get('offset'));
+
+ // Return an SQL formatted datetime string in UTC.
+ $return = Factory::getDate($value, $offset)->toSql();
+ break;
+ }
+
+ return $return;
+ }
}
diff --git a/libraries/src/Form/Field/CaptchaField.php b/libraries/src/Form/Field/CaptchaField.php
index 4818bb6884fb5..764a0ab627198 100644
--- a/libraries/src/Form/Field/CaptchaField.php
+++ b/libraries/src/Form/Field/CaptchaField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'plugin':
- case 'namespace':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 2.5
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- $app = Factory::getApplication();
-
- $default = $app->get('captcha');
-
- if ($app->isClient('site'))
- {
- $default = $app->getParams()->get('captcha', $default);
- }
-
- $plugin = $this->element['plugin'] ?
- (string) $this->element['plugin'] :
- $default;
-
- $this->plugin = $plugin;
-
- if ($plugin === 0 || $plugin === '0' || $plugin === '' || $plugin === null)
- {
- $this->hidden = true;
-
- return false;
- }
- else
- {
- // Force field to be required. There's no reason to have a captcha if it is not required.
- // Obs: Don't put required="required" in the xml file, you just need to have validate="captcha"
- $this->required = true;
-
- if (strpos($this->class, 'required') === false)
- {
- $this->class .= ' required';
- }
- }
-
- $this->namespace = $this->element['namespace'] ? (string) $this->element['namespace'] : $this->form->getName();
-
- try
- {
- // Get an instance of the captcha class that we are using
- $this->_captcha = Captcha::getInstance($this->plugin, array('namespace' => $this->namespace));
-
- /**
- * Give the captcha instance a possibility to react on the setup-process,
- * e.g. by altering the XML structure of the field, for example hiding the label
- * when using invisible captchas.
- */
- $this->_captcha->setupField($this, $element);
- }
- catch (\RuntimeException $e)
- {
- $this->_captcha = null;
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
-
- return false;
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input.
- *
- * @return string The field input.
- *
- * @since 2.5
- */
- protected function getInput()
- {
- if ($this->hidden || $this->_captcha == null)
- {
- return '';
- }
-
- try
- {
- return $this->_captcha->display($this->name, $this->id, $this->class);
- }
- catch (\RuntimeException $e)
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
-
- return '';
- }
+ /**
+ * The field type.
+ *
+ * @var string
+ * @since 2.5
+ */
+ protected $type = 'Captcha';
+
+ /**
+ * The captcha base instance of our type.
+ *
+ * @var Captcha
+ */
+ protected $_captcha;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'plugin':
+ case 'namespace':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'plugin':
+ case 'namespace':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 2.5
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ $app = Factory::getApplication();
+
+ $default = $app->get('captcha');
+
+ if ($app->isClient('site')) {
+ $default = $app->getParams()->get('captcha', $default);
+ }
+
+ $plugin = $this->element['plugin'] ?
+ (string) $this->element['plugin'] :
+ $default;
+
+ $this->plugin = $plugin;
+
+ if ($plugin === 0 || $plugin === '0' || $plugin === '' || $plugin === null) {
+ $this->hidden = true;
+
+ return false;
+ } else {
+ // Force field to be required. There's no reason to have a captcha if it is not required.
+ // Obs: Don't put required="required" in the xml file, you just need to have validate="captcha"
+ $this->required = true;
+
+ if (strpos($this->class, 'required') === false) {
+ $this->class .= ' required';
+ }
+ }
+
+ $this->namespace = $this->element['namespace'] ? (string) $this->element['namespace'] : $this->form->getName();
+
+ try {
+ // Get an instance of the captcha class that we are using
+ $this->_captcha = Captcha::getInstance($this->plugin, array('namespace' => $this->namespace));
+
+ /**
+ * Give the captcha instance a possibility to react on the setup-process,
+ * e.g. by altering the XML structure of the field, for example hiding the label
+ * when using invisible captchas.
+ */
+ $this->_captcha->setupField($this, $element);
+ } catch (\RuntimeException $e) {
+ $this->_captcha = null;
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input.
+ *
+ * @return string The field input.
+ *
+ * @since 2.5
+ */
+ protected function getInput()
+ {
+ if ($this->hidden || $this->_captcha == null) {
+ return '';
+ }
+
+ try {
+ return $this->_captcha->display($this->name, $this->id, $this->class);
+ } catch (\RuntimeException $e) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+
+ return '';
+ }
}
diff --git a/libraries/src/Form/Field/CategoryField.php b/libraries/src/Form/Field/CategoryField.php
index b996c23f63817..16cf5b4f0ce37 100644
--- a/libraries/src/Form/Field/CategoryField.php
+++ b/libraries/src/Form/Field/CategoryField.php
@@ -1,4 +1,5 @@
element['extension'] ? (string) $this->element['extension'] : (string) $this->element['scope'];
- $published = (string) $this->element['published'];
- $language = (string) $this->element['language'];
+ /**
+ * Method to get the field options for category
+ * Use the extension attribute in a form to specify the.specific extension for
+ * which categories should be displayed.
+ * Use the show_root attribute to specify whether to show the global category root in the list.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.6
+ */
+ protected function getOptions()
+ {
+ $options = array();
+ $extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $this->element['scope'];
+ $published = (string) $this->element['published'];
+ $language = (string) $this->element['language'];
- // Load the category options for a given extension.
- if (!empty($extension))
- {
- // Filter over published state or not depending upon if it is present.
- $filters = array();
+ // Load the category options for a given extension.
+ if (!empty($extension)) {
+ // Filter over published state or not depending upon if it is present.
+ $filters = array();
- if ($published)
- {
- $filters['filter.published'] = explode(',', $published);
- }
+ if ($published) {
+ $filters['filter.published'] = explode(',', $published);
+ }
- // Filter over language depending upon if it is present.
- if ($language)
- {
- $filters['filter.language'] = explode(',', $language);
- }
+ // Filter over language depending upon if it is present.
+ if ($language) {
+ $filters['filter.language'] = explode(',', $language);
+ }
- if ($filters === array())
- {
- $options = HTMLHelper::_('category.options', $extension);
- }
- else
- {
- $options = HTMLHelper::_('category.options', $extension, $filters);
- }
+ if ($filters === array()) {
+ $options = HTMLHelper::_('category.options', $extension);
+ } else {
+ $options = HTMLHelper::_('category.options', $extension, $filters);
+ }
- // Verify permissions. If the action attribute is set, then we scan the options.
- if ((string) $this->element['action'])
- {
- // Get the current user object.
- $user = Factory::getUser();
+ // Verify permissions. If the action attribute is set, then we scan the options.
+ if ((string) $this->element['action']) {
+ // Get the current user object.
+ $user = Factory::getUser();
- foreach ($options as $i => $option)
- {
- /*
- * To take save or create in a category you need to have create rights for that category
- * unless the item is already in that category.
- * Unset the option if the user isn't authorised for it. In this field assets are always categories.
- */
- if ($user->authorise('core.create', $extension . '.category.' . $option->value) === false)
- {
- unset($options[$i]);
- }
- }
- }
+ foreach ($options as $i => $option) {
+ /*
+ * To take save or create in a category you need to have create rights for that category
+ * unless the item is already in that category.
+ * Unset the option if the user isn't authorised for it. In this field assets are always categories.
+ */
+ if ($user->authorise('core.create', $extension . '.category.' . $option->value) === false) {
+ unset($options[$i]);
+ }
+ }
+ }
- if (isset($this->element['show_root']))
- {
- array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('JGLOBAL_ROOT')));
- }
- }
- else
- {
- Log::add(Text::_('JLIB_FORM_ERROR_FIELDS_CATEGORY_ERROR_EXTENSION_EMPTY'), Log::WARNING, 'jerror');
- }
+ if (isset($this->element['show_root'])) {
+ array_unshift($options, HTMLHelper::_('select.option', '0', Text::_('JGLOBAL_ROOT')));
+ }
+ } else {
+ Log::add(Text::_('JLIB_FORM_ERROR_FIELDS_CATEGORY_ERROR_EXTENSION_EMPTY'), Log::WARNING, 'jerror');
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/CheckboxField.php b/libraries/src/Form/Field/CheckboxField.php
index 401d42db277a0..034a17aefb936 100644
--- a/libraries/src/Form/Field/CheckboxField.php
+++ b/libraries/src/Form/Field/CheckboxField.php
@@ -1,4 +1,5 @@
checked;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- if ($name === 'checked')
- {
- $value = (string) $value;
- $this->checked = ($value === 'true' || $value == $name || $value === '1');
-
- return;
- }
-
- parent::__set($name, $value);
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- // Handle the default attribute
- $default = (string) $element['default'];
-
- if ($default)
- {
- $test = $this->form->getValue((string) $element['name'], $group);
-
- $value = ($test == $default) ? $default : null;
- }
-
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $checked = (string) $this->element['checked'];
- $this->checked = ($checked === 'true' || $checked === 'checked' || $checked === '1');
-
- empty($this->value) || $this->checked ? null : $this->checked = true;
- }
-
- return $return;
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
- $data['value'] = $this->default ?: '1';
- $data['checked'] = $this->checked || $this->value;
-
- return $data;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Checkbox';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.checkbox';
+
+ /**
+ * The checked state of checkbox field.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $checked = false;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'checked') {
+ return $this->checked;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ if ($name === 'checked') {
+ $value = (string) $value;
+ $this->checked = ($value === 'true' || $value == $name || $value === '1');
+
+ return;
+ }
+
+ parent::__set($name, $value);
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ // Handle the default attribute
+ $default = (string) $element['default'];
+
+ if ($default) {
+ $test = $this->form->getValue((string) $element['name'], $group);
+
+ $value = ($test == $default) ? $default : null;
+ }
+
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $checked = (string) $this->element['checked'];
+ $this->checked = ($checked === 'true' || $checked === 'checked' || $checked === '1');
+
+ empty($this->value) || $this->checked ? null : $this->checked = true;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+ $data['value'] = $this->default ?: '1';
+ $data['checked'] = $this->checked || $this->value;
+
+ return $data;
+ }
}
diff --git a/libraries/src/Form/Field/CheckboxesField.php b/libraries/src/Form/Field/CheckboxesField.php
index ed547c1a7c722..5dd212033c5ce 100644
--- a/libraries/src/Form/Field/CheckboxesField.php
+++ b/libraries/src/Form/Field/CheckboxesField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'checkedOptions':
- $this->checkedOptions = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to get the radio button field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- if (empty($this->layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->checkedOptions = (string) $this->element['checked'];
- }
-
- return $return;
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // True if the field has 'value' set. In other words, it has been stored, don't use the default values.
- $hasValue = (isset($this->value) && !empty($this->value));
-
- // If a value has been stored, use it. Otherwise, use the defaults.
- $checkedOptions = $hasValue ? $this->value : $this->checkedOptions;
-
- $extraData = array(
- 'checkedOptions' => \is_array($checkedOptions) ? $checkedOptions : explode(',', (string) $checkedOptions),
- 'hasValue' => $hasValue,
- 'options' => $this->getOptions(),
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Checkboxes';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $layout = 'joomla.form.field.checkboxes';
+
+ /**
+ * Flag to tell the field to always be in multiple values mode.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $forceMultiple = true;
+
+ /**
+ * The comma separated list of checked checkboxes value.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ public $checkedOptions;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'forceMultiple':
+ case 'checkedOptions':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'checkedOptions':
+ $this->checkedOptions = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to get the radio button field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->checkedOptions = (string) $this->element['checked'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // True if the field has 'value' set. In other words, it has been stored, don't use the default values.
+ $hasValue = (isset($this->value) && !empty($this->value));
+
+ // If a value has been stored, use it. Otherwise, use the defaults.
+ $checkedOptions = $hasValue ? $this->value : $this->checkedOptions;
+
+ $extraData = array(
+ 'checkedOptions' => \is_array($checkedOptions) ? $checkedOptions : explode(',', (string) $checkedOptions),
+ 'hasValue' => $hasValue,
+ 'options' => $this->getOptions(),
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/ChromestyleField.php b/libraries/src/Form/Field/ChromestyleField.php
index 25698275c592e..a3915f30e145d 100644
--- a/libraries/src/Form/Field/ChromestyleField.php
+++ b/libraries/src/Form/Field/ChromestyleField.php
@@ -1,4 +1,5 @@
clientId;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to get the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'clientId':
- $this->clientId = (int) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- // Get the client id.
- $clientId = $this->element['client_id'];
-
- if (!isset($clientId))
- {
- $clientName = $this->element['client'];
-
- if (isset($clientName))
- {
- $client = ApplicationHelper::getClientInfo($clientName, true);
- $clientId = $client->id;
- }
- }
-
- if (!isset($clientId) && $this->form instanceof Form)
- {
- $clientId = $this->form->getValue('client_id');
- }
-
- $this->clientId = (int) $clientId;
- }
-
- return $result;
- }
-
-
- /**
- * Method to get the list of template chrome style options
- * grouped by template.
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 3.0
- */
- protected function getGroups()
- {
- $groups = array();
-
- // Add Module Style Field
- $tmp = '---' . Text::_('JLIB_FORM_VALUE_FROM_TEMPLATE') . '---';
- $groups[$tmp][] = HTMLHelper::_('select.option', '0', Text::_('JLIB_FORM_VALUE_INHERITED'));
-
- $templateStyles = $this->getTemplateModuleStyles();
-
- // Create one new option object for each available style, grouped by templates
- foreach ($templateStyles as $template => $styles)
- {
- $template = ucfirst($template);
- $groups[$template] = array();
-
- foreach ($styles as $style)
- {
- $tmp = HTMLHelper::_('select.option', $template . '-' . $style, $style);
- $groups[$template][] = $tmp;
- }
- }
-
- reset($groups);
-
- return $groups;
- }
-
- /**
- * Method to get the templates module styles.
- *
- * @return array The array of styles, grouped by templates.
- *
- * @since 3.0
- */
- protected function getTemplateModuleStyles()
- {
- $moduleStyles = array();
-
- // Global Layouts
- $layouts = Folder::files(JPATH_SITE . '/layouts/chromes', '.*\.php');
-
- foreach ($layouts as &$layout)
- {
- $layout = basename($layout, '.php');
- }
-
- $moduleStyles['system'] = $layouts;
-
- $templates = $this->getTemplates();
- $path = JPATH_ADMINISTRATOR;
-
- if ($this->clientId === 0)
- {
- $path = JPATH_SITE;
- }
-
- foreach ($templates as $template)
- {
- $chromeLayoutPath = $path . '/templates/' . $template->element . '/html/layouts/chromes';
-
- if (!Folder::exists($chromeLayoutPath))
- {
- continue;
- }
-
- $layouts = Folder::files($chromeLayoutPath, '.*\.php');
-
- if ($layouts)
- {
- foreach ($layouts as &$layout)
- {
- $layout = basename($layout, '.php');
- }
-
- $moduleStyles[$template->element] = $layouts;
- }
- }
-
- return $moduleStyles;
- }
-
- /**
- * Return a list of templates
- *
- * @return array List of templates
- *
- * @since 3.2.1
- */
- protected function getTemplates()
- {
- $db = $this->getDatabase();
-
- // Get the database object and a new query object.
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select(
- [
- $db->quoteName('element'),
- $db->quoteName('name'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('client_id') . ' = :clientId',
- $db->quoteName('type') . ' = ' . $db->quote('template'),
- $db->quoteName('enabled') . ' = 1',
- ]
- )
- ->bind(':clientId', $this->clientId, ParameterType::INTEGER);
-
- // Set the query and load the templates.
- $db->setQuery($query);
-
- return $db->loadObjectList('element');
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.0
+ */
+ public $type = 'ChromeStyle';
+
+ /**
+ * The client ID.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $clientId;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'clientId') {
+ return $this->clientId;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'clientId':
+ $this->clientId = (int) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ // Get the client id.
+ $clientId = $this->element['client_id'];
+
+ if (!isset($clientId)) {
+ $clientName = $this->element['client'];
+
+ if (isset($clientName)) {
+ $client = ApplicationHelper::getClientInfo($clientName, true);
+ $clientId = $client->id;
+ }
+ }
+
+ if (!isset($clientId) && $this->form instanceof Form) {
+ $clientId = $this->form->getValue('client_id');
+ }
+
+ $this->clientId = (int) $clientId;
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Method to get the list of template chrome style options
+ * grouped by template.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 3.0
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+
+ // Add Module Style Field
+ $tmp = '---' . Text::_('JLIB_FORM_VALUE_FROM_TEMPLATE') . '---';
+ $groups[$tmp][] = HTMLHelper::_('select.option', '0', Text::_('JLIB_FORM_VALUE_INHERITED'));
+
+ $templateStyles = $this->getTemplateModuleStyles();
+
+ // Create one new option object for each available style, grouped by templates
+ foreach ($templateStyles as $template => $styles) {
+ $template = ucfirst($template);
+ $groups[$template] = array();
+
+ foreach ($styles as $style) {
+ $tmp = HTMLHelper::_('select.option', $template . '-' . $style, $style);
+ $groups[$template][] = $tmp;
+ }
+ }
+
+ reset($groups);
+
+ return $groups;
+ }
+
+ /**
+ * Method to get the templates module styles.
+ *
+ * @return array The array of styles, grouped by templates.
+ *
+ * @since 3.0
+ */
+ protected function getTemplateModuleStyles()
+ {
+ $moduleStyles = array();
+
+ // Global Layouts
+ $layouts = Folder::files(JPATH_SITE . '/layouts/chromes', '.*\.php');
+
+ foreach ($layouts as &$layout) {
+ $layout = basename($layout, '.php');
+ }
+
+ $moduleStyles['system'] = $layouts;
+
+ $templates = $this->getTemplates();
+ $path = JPATH_ADMINISTRATOR;
+
+ if ($this->clientId === 0) {
+ $path = JPATH_SITE;
+ }
+
+ foreach ($templates as $template) {
+ $chromeLayoutPath = $path . '/templates/' . $template->element . '/html/layouts/chromes';
+
+ if (!Folder::exists($chromeLayoutPath)) {
+ continue;
+ }
+
+ $layouts = Folder::files($chromeLayoutPath, '.*\.php');
+
+ if ($layouts) {
+ foreach ($layouts as &$layout) {
+ $layout = basename($layout, '.php');
+ }
+
+ $moduleStyles[$template->element] = $layouts;
+ }
+ }
+
+ return $moduleStyles;
+ }
+
+ /**
+ * Return a list of templates
+ *
+ * @return array List of templates
+ *
+ * @since 3.2.1
+ */
+ protected function getTemplates()
+ {
+ $db = $this->getDatabase();
+
+ // Get the database object and a new query object.
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select(
+ [
+ $db->quoteName('element'),
+ $db->quoteName('name'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('client_id') . ' = :clientId',
+ $db->quoteName('type') . ' = ' . $db->quote('template'),
+ $db->quoteName('enabled') . ' = 1',
+ ]
+ )
+ ->bind(':clientId', $this->clientId, ParameterType::INTEGER);
+
+ // Set the query and load the templates.
+ $db->setQuery($query);
+
+ return $db->loadObjectList('element');
+ }
}
diff --git a/libraries/src/Form/Field/ColorField.php b/libraries/src/Form/Field/ColorField.php
index d217b3d3835cd..4a709ae11a113 100644
--- a/libraries/src/Form/Field/ColorField.php
+++ b/libraries/src/Form/Field/ColorField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'colors':
- case 'control':
- case 'default':
- case 'display':
- case 'exclude':
- case 'format':
- case 'keywords':
- case 'saveFormat':
- $this->$name = (string) $value;
- break;
- case 'split':
- $this->$name = (int) $value;
- break;
- case 'preview':
- $this->$name = (boolean) $value;
- break;
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->colors = (string) $this->element['colors'];
- $this->control = isset($this->element['control']) ? (string) $this->element['control'] : 'hue';
- $this->default = (string) $this->element['default'];
- $this->display = isset($this->element['display']) ? (string) $this->element['display'] : 'hue';
- $this->format = isset($this->element['format']) ? (string) $this->element['format'] : 'hex';
- $this->keywords = (string) $this->element['keywords'];
- $this->position = isset($this->element['position']) ? (string) $this->element['position'] : 'default';
- $this->preview = isset($this->element['preview']) ? (string) $this->element['preview'] : false;
- $this->saveFormat = isset($this->element['saveFormat']) ? (string) $this->element['saveFormat'] : 'hex';
- $this->split = isset($this->element['split']) ? (int) $this->element['split'] : 3;
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.3
- */
- protected function getInput()
- {
- // Switch the layouts
- if ($this->control === 'simple' || $this->control === 'slider')
- {
- $this->layout .= '.' . $this->control;
- }
- else
- {
- $this->layout .= '.advanced';
- }
-
- // Trim the trailing line in the layout file
- return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData()
- {
- $lang = Factory::getApplication()->getLanguage();
- $data = parent::getLayoutData();
- $color = strtolower($this->value);
- $color = !$color && $color !== '0' ? '' : $color;
-
- // Position of the panel can be: right (default), left, top or bottom (default RTL is left)
- $position = ' data-position="' . (($lang->isRtl() && $this->position === 'default') ? 'left' : $this->position) . '"';
-
- if ($color === '' || \in_array($color, array('none', 'transparent')))
- {
- $color = 'none';
- }
- elseif ($color[0] !== '#' && $this->format === 'hex')
- {
- $color = '#' . $color;
- }
-
- switch ($this->control)
- {
- case 'simple':
- $controlModeData = $this->getSimpleModeLayoutData();
- break;
- case 'slider':
- $controlModeData = $this->getSliderModeLayoutData();
- break;
- case 'advanced':
- default:
- $controlModeData = $this->getAdvancedModeLayoutData($lang);
- break;
- }
-
- $extraData = array(
- 'color' => $color,
- 'format' => $this->format,
- 'keywords' => $this->keywords,
- 'position' => $position,
- 'validate' => $this->validate,
- );
-
- return array_merge($data, $extraData, $controlModeData);
- }
-
- /**
- * Method to get the data for the simple mode to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getSimpleModeLayoutData()
- {
- $colors = strtolower($this->colors);
-
- if (empty($colors))
- {
- $colors = array(
- 'none',
- '#049cdb',
- '#46a546',
- '#9d261d',
- '#ffc40d',
- '#f89406',
- '#c3325f',
- '#7a43b6',
- '#ffffff',
- '#999999',
- '#555555',
- '#000000',
- );
- }
- else
- {
- $colors = explode(',', $colors);
- }
-
- if (!$this->split)
- {
- $count = \count($colors);
-
- if ($count % 5 == 0)
- {
- $split = 5;
- }
- else
- {
- if ($count % 4 == 0)
- {
- $split = 4;
- }
- }
- }
-
- $split = $this->split ?: 3;
-
- return array(
- 'colors' => $colors,
- 'split' => $split,
- );
- }
-
- /**
- * Method to get the data for the advanced mode to be passed to the layout for rendering.
- *
- * @param object $lang The language object
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getAdvancedModeLayoutData($lang)
- {
- return array(
- 'colors' => $this->colors,
- 'control' => $this->control,
- 'lang' => $lang,
- );
- }
-
- /**
- * Method to get the data for the slider
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getSliderModeLayoutData()
- {
- return array(
- 'default' => $this->default,
- 'display' => $this->display,
- 'preview' => $this->preview,
- 'saveFormat' => $this->saveFormat,
- );
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.3
+ */
+ protected $type = 'Color';
+
+ /**
+ * The control.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $control = 'hue';
+
+ /**
+ * Default color when there is no value.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $default;
+
+ /**
+ * The type of value the slider should display: 'hue', 'saturation' or 'light'.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $display = 'hue';
+
+ /**
+ * The format.
+ *
+ * @var string
+ * @since 3.6.0
+ */
+ protected $format = 'hex';
+
+ /**
+ * The keywords (transparent,initial,inherit).
+ *
+ * @var string
+ * @since 3.6.0
+ */
+ protected $keywords = '';
+
+ /**
+ * The position.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $position = 'default';
+
+ /**
+ * The colors.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $colors;
+
+ /**
+ * Shows preview of the selected color
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $preview = false;
+
+ /**
+ * Color format to use when value gets saved
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $saveFormat = 'hex';
+
+ /**
+ * The split.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $split = 3;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $layout = 'joomla.form.field.color';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'colors':
+ case 'control':
+ case 'default':
+ case 'display':
+ case 'exclude':
+ case 'format':
+ case 'keywords':
+ case 'preview':
+ case 'saveFormat':
+ case 'split':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'colors':
+ case 'control':
+ case 'default':
+ case 'display':
+ case 'exclude':
+ case 'format':
+ case 'keywords':
+ case 'saveFormat':
+ $this->$name = (string) $value;
+ break;
+ case 'split':
+ $this->$name = (int) $value;
+ break;
+ case 'preview':
+ $this->$name = (bool) $value;
+ break;
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->colors = (string) $this->element['colors'];
+ $this->control = isset($this->element['control']) ? (string) $this->element['control'] : 'hue';
+ $this->default = (string) $this->element['default'];
+ $this->display = isset($this->element['display']) ? (string) $this->element['display'] : 'hue';
+ $this->format = isset($this->element['format']) ? (string) $this->element['format'] : 'hex';
+ $this->keywords = (string) $this->element['keywords'];
+ $this->position = isset($this->element['position']) ? (string) $this->element['position'] : 'default';
+ $this->preview = isset($this->element['preview']) ? (string) $this->element['preview'] : false;
+ $this->saveFormat = isset($this->element['saveFormat']) ? (string) $this->element['saveFormat'] : 'hex';
+ $this->split = isset($this->element['split']) ? (int) $this->element['split'] : 3;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.3
+ */
+ protected function getInput()
+ {
+ // Switch the layouts
+ if ($this->control === 'simple' || $this->control === 'slider') {
+ $this->layout .= '.' . $this->control;
+ } else {
+ $this->layout .= '.advanced';
+ }
+
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData()
+ {
+ $lang = Factory::getApplication()->getLanguage();
+ $data = parent::getLayoutData();
+ $color = strtolower($this->value);
+ $color = !$color && $color !== '0' ? '' : $color;
+
+ // Position of the panel can be: right (default), left, top or bottom (default RTL is left)
+ $position = ' data-position="' . (($lang->isRtl() && $this->position === 'default') ? 'left' : $this->position) . '"';
+
+ if ($color === '' || \in_array($color, array('none', 'transparent'))) {
+ $color = 'none';
+ } elseif ($color[0] !== '#' && $this->format === 'hex') {
+ $color = '#' . $color;
+ }
+
+ switch ($this->control) {
+ case 'simple':
+ $controlModeData = $this->getSimpleModeLayoutData();
+ break;
+ case 'slider':
+ $controlModeData = $this->getSliderModeLayoutData();
+ break;
+ case 'advanced':
+ default:
+ $controlModeData = $this->getAdvancedModeLayoutData($lang);
+ break;
+ }
+
+ $extraData = array(
+ 'color' => $color,
+ 'format' => $this->format,
+ 'keywords' => $this->keywords,
+ 'position' => $position,
+ 'validate' => $this->validate,
+ );
+
+ return array_merge($data, $extraData, $controlModeData);
+ }
+
+ /**
+ * Method to get the data for the simple mode to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getSimpleModeLayoutData()
+ {
+ $colors = strtolower($this->colors);
+
+ if (empty($colors)) {
+ $colors = array(
+ 'none',
+ '#049cdb',
+ '#46a546',
+ '#9d261d',
+ '#ffc40d',
+ '#f89406',
+ '#c3325f',
+ '#7a43b6',
+ '#ffffff',
+ '#999999',
+ '#555555',
+ '#000000',
+ );
+ } else {
+ $colors = explode(',', $colors);
+ }
+
+ if (!$this->split) {
+ $count = \count($colors);
+
+ if ($count % 5 == 0) {
+ $split = 5;
+ } else {
+ if ($count % 4 == 0) {
+ $split = 4;
+ }
+ }
+ }
+
+ $split = $this->split ?: 3;
+
+ return array(
+ 'colors' => $colors,
+ 'split' => $split,
+ );
+ }
+
+ /**
+ * Method to get the data for the advanced mode to be passed to the layout for rendering.
+ *
+ * @param object $lang The language object
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getAdvancedModeLayoutData($lang)
+ {
+ return array(
+ 'colors' => $this->colors,
+ 'control' => $this->control,
+ 'lang' => $lang,
+ );
+ }
+
+ /**
+ * Method to get the data for the slider
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getSliderModeLayoutData()
+ {
+ return array(
+ 'default' => $this->default,
+ 'display' => $this->display,
+ 'preview' => $this->preview,
+ 'saveFormat' => $this->saveFormat,
+ );
+ }
}
diff --git a/libraries/src/Form/Field/ComboField.php b/libraries/src/Form/Field/ComboField.php
index 5f345377bb2a7..4f1f6402fa3aa 100644
--- a/libraries/src/Form/Field/ComboField.php
+++ b/libraries/src/Form/Field/ComboField.php
@@ -1,4 +1,5 @@
layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
+ /**
+ * Method to get the field input markup for a combo box field.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.8.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.8.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- // Get the field options.
- $options = $this->getOptions();
+ // Get the field options.
+ $options = $this->getOptions();
- $extraData = array(
- 'options' => $options,
- );
+ $extraData = array(
+ 'options' => $options,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/ComponentlayoutField.php b/libraries/src/Form/Field/ComponentlayoutField.php
index 122e9449757d1..94bed7823198f 100644
--- a/libraries/src/Form/Field/ComponentlayoutField.php
+++ b/libraries/src/Form/Field/ComponentlayoutField.php
@@ -1,4 +1,5 @@
element['client_id'];
-
- if ($clientId === null && $this->form instanceof Form)
- {
- $clientId = $this->form->getValue('client_id');
- }
-
- $clientId = (int) $clientId;
-
- $client = ApplicationHelper::getClientInfo($clientId);
-
- // Get the extension.
- $extension = (string) $this->element['extension'];
-
- if (empty($extension) && ($this->form instanceof Form))
- {
- $extension = $this->form->getValue('extension');
- }
-
- $extension = preg_replace('#\W#', '', $extension);
-
- $template = (string) $this->element['template'];
- $template = preg_replace('#\W#', '', $template);
-
- $template_style_id = 0;
-
- if ($this->form instanceof Form)
- {
- $template_style_id = $this->form->getValue('template_style_id', null, 0);
- $template_style_id = (int) preg_replace('#\W#', '', $template_style_id);
- }
-
- $view = (string) $this->element['view'];
- $view = preg_replace('#\W#', '', $view);
-
- // If a template, extension and view are present build the options.
- if ($extension && $view && $client)
- {
- // Load language file
- $lang = Factory::getLanguage();
- $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($extension . '.sys', JPATH_ADMINISTRATOR . '/components/' . $extension);
-
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select(
- [
- $db->quoteName('e.element'),
- $db->quoteName('e.name'),
- ]
- )
- ->from($db->quoteName('#__extensions', 'e'))
- ->where(
- [
- $db->quoteName('e.client_id') . ' = :clientId',
- $db->quoteName('e.type') . ' = ' . $db->quote('template'),
- $db->quoteName('e.enabled') . ' = 1',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
-
- if ($template)
- {
- $query->where($db->quoteName('e.element') . ' = :template')
- ->bind(':template', $template);
- }
-
- if ($template_style_id)
- {
- $query->join('LEFT', $db->quoteName('#__template_styles', 's'), $db->quoteName('s.template') . ' = ' . $db->quoteName('e.element'))
- ->where($db->quoteName('s.id') . ' = :style')
- ->bind(':style', $template_style_id, ParameterType::INTEGER);
- }
-
- // Set the query and load the templates.
- $db->setQuery($query);
- $templates = $db->loadObjectList('element');
-
- // Build the search paths for component layouts.
- $component_path = Path::clean($client->path . '/components/' . $extension . '/tmpl/' . $view);
-
- // Check if the new layouts folder exists, else use the old one
- if (!is_dir($component_path))
- {
- $component_path = Path::clean($client->path . '/components/' . $extension . '/views/' . $view . '/tmpl');
- }
-
- // Prepare array of component layouts
- $component_layouts = array();
-
- // Prepare the grouped list
- $groups = array();
-
- // Add a Use Global option if useglobal="true" in XML file
- if ((string) $this->element['useglobal'] === 'true')
- {
- $groups[Text::_('JOPTION_FROM_STANDARD')]['items'][] = HTMLHelper::_('select.option', '', Text::_('JGLOBAL_USE_GLOBAL'));
- }
-
- // Add the layout options from the component path.
- if (is_dir($component_path) && ($component_layouts = Folder::files($component_path, '^[^_]*\.xml$', false, true)))
- {
- // Create the group for the component
- $groups['_'] = array();
- $groups['_']['id'] = $this->id . '__';
- $groups['_']['text'] = Text::sprintf('JOPTION_FROM_COMPONENT');
- $groups['_']['items'] = array();
-
- foreach ($component_layouts as $i => $file)
- {
- // Attempt to load the XML file.
- if (!$xml = simplexml_load_file($file))
- {
- unset($component_layouts[$i]);
-
- continue;
- }
-
- // Get the help data from the XML file if present.
- if (!$menu = $xml->xpath('layout[1]'))
- {
- unset($component_layouts[$i]);
-
- continue;
- }
-
- $menu = $menu[0];
-
- // Add an option to the component group
- $value = basename($file, '.xml');
- $component_layouts[$i] = $value;
- $text = isset($menu['option']) ? Text::_($menu['option']) : (isset($menu['title']) ? Text::_($menu['title']) : $value);
- $groups['_']['items'][] = HTMLHelper::_('select.option', '_:' . $value, $text);
- }
- }
-
- // Loop on all templates
- if ($templates)
- {
- foreach ($templates as $template)
- {
- // Load language file
- $lang->load('tpl_' . $template->element . '.sys', $client->path)
- || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
-
- $template_path = Path::clean(
- $client->path
- . '/templates/'
- . $template->element
- . '/html/'
- . $extension
- . '/'
- . $view
- );
-
- // Add the layout options from the template path.
- if (is_dir($template_path) && ($files = Folder::files($template_path, '^[^_]*\.php$', false, true)))
- {
- foreach ($files as $i => $file)
- {
- // Remove layout files that exist in the component folder
- if (\in_array(basename($file, '.php'), $component_layouts))
- {
- unset($files[$i]);
- }
- }
-
- if (\count($files))
- {
- // Create the group for the template
- $groups[$template->name] = array();
- $groups[$template->name]['id'] = $this->id . '_' . $template->element;
- $groups[$template->name]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
- $groups[$template->name]['items'] = array();
-
- foreach ($files as $file)
- {
- // Add an option to the template group
- $value = basename($file, '.php');
- $text = $lang
- ->hasKey(
- $key = strtoupper(
- 'TPL_'
- . $template->name
- . '_'
- . $extension
- . '_'
- . $view
- . '_LAYOUT_'
- . $value
- )
- )
- ? Text::_($key) : $value;
- $groups[$template->name]['items'][] = HTMLHelper::_('select.option', $template->element . ':' . $value, $text);
- }
- }
- }
- }
- }
-
- // Compute attributes for the grouped list
- $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
- $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
-
- // Prepare HTML code
- $html = array();
-
- // Compute the current selected values
- $selected = array($this->value);
-
- // Add a grouped list
- $html[] = HTMLHelper::_(
- 'select.groupedlist', $groups, $this->name,
- array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
- );
-
- return implode($html);
- }
- else
- {
- return '';
- }
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'ComponentLayout';
+
+ /**
+ * Method to get the field input for a component layout field.
+ *
+ * @return string The field input.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ // Get the client id.
+ $clientId = $this->element['client_id'];
+
+ if ($clientId === null && $this->form instanceof Form) {
+ $clientId = $this->form->getValue('client_id');
+ }
+
+ $clientId = (int) $clientId;
+
+ $client = ApplicationHelper::getClientInfo($clientId);
+
+ // Get the extension.
+ $extension = (string) $this->element['extension'];
+
+ if (empty($extension) && ($this->form instanceof Form)) {
+ $extension = $this->form->getValue('extension');
+ }
+
+ $extension = preg_replace('#\W#', '', $extension);
+
+ $template = (string) $this->element['template'];
+ $template = preg_replace('#\W#', '', $template);
+
+ $template_style_id = 0;
+
+ if ($this->form instanceof Form) {
+ $template_style_id = $this->form->getValue('template_style_id', null, 0);
+ $template_style_id = (int) preg_replace('#\W#', '', $template_style_id);
+ }
+
+ $view = (string) $this->element['view'];
+ $view = preg_replace('#\W#', '', $view);
+
+ // If a template, extension and view are present build the options.
+ if ($extension && $view && $client) {
+ // Load language file
+ $lang = Factory::getLanguage();
+ $lang->load($extension . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($extension . '.sys', JPATH_ADMINISTRATOR . '/components/' . $extension);
+
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select(
+ [
+ $db->quoteName('e.element'),
+ $db->quoteName('e.name'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions', 'e'))
+ ->where(
+ [
+ $db->quoteName('e.client_id') . ' = :clientId',
+ $db->quoteName('e.type') . ' = ' . $db->quote('template'),
+ $db->quoteName('e.enabled') . ' = 1',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+
+ if ($template) {
+ $query->where($db->quoteName('e.element') . ' = :template')
+ ->bind(':template', $template);
+ }
+
+ if ($template_style_id) {
+ $query->join('LEFT', $db->quoteName('#__template_styles', 's'), $db->quoteName('s.template') . ' = ' . $db->quoteName('e.element'))
+ ->where($db->quoteName('s.id') . ' = :style')
+ ->bind(':style', $template_style_id, ParameterType::INTEGER);
+ }
+
+ // Set the query and load the templates.
+ $db->setQuery($query);
+ $templates = $db->loadObjectList('element');
+
+ // Build the search paths for component layouts.
+ $component_path = Path::clean($client->path . '/components/' . $extension . '/tmpl/' . $view);
+
+ // Check if the new layouts folder exists, else use the old one
+ if (!is_dir($component_path)) {
+ $component_path = Path::clean($client->path . '/components/' . $extension . '/views/' . $view . '/tmpl');
+ }
+
+ // Prepare array of component layouts
+ $component_layouts = array();
+
+ // Prepare the grouped list
+ $groups = array();
+
+ // Add a Use Global option if useglobal="true" in XML file
+ if ((string) $this->element['useglobal'] === 'true') {
+ $groups[Text::_('JOPTION_FROM_STANDARD')]['items'][] = HTMLHelper::_('select.option', '', Text::_('JGLOBAL_USE_GLOBAL'));
+ }
+
+ // Add the layout options from the component path.
+ if (is_dir($component_path) && ($component_layouts = Folder::files($component_path, '^[^_]*\.xml$', false, true))) {
+ // Create the group for the component
+ $groups['_'] = array();
+ $groups['_']['id'] = $this->id . '__';
+ $groups['_']['text'] = Text::sprintf('JOPTION_FROM_COMPONENT');
+ $groups['_']['items'] = array();
+
+ foreach ($component_layouts as $i => $file) {
+ // Attempt to load the XML file.
+ if (!$xml = simplexml_load_file($file)) {
+ unset($component_layouts[$i]);
+
+ continue;
+ }
+
+ // Get the help data from the XML file if present.
+ if (!$menu = $xml->xpath('layout[1]')) {
+ unset($component_layouts[$i]);
+
+ continue;
+ }
+
+ $menu = $menu[0];
+
+ // Add an option to the component group
+ $value = basename($file, '.xml');
+ $component_layouts[$i] = $value;
+ $text = isset($menu['option']) ? Text::_($menu['option']) : (isset($menu['title']) ? Text::_($menu['title']) : $value);
+ $groups['_']['items'][] = HTMLHelper::_('select.option', '_:' . $value, $text);
+ }
+ }
+
+ // Loop on all templates
+ if ($templates) {
+ foreach ($templates as $template) {
+ // Load language file
+ $lang->load('tpl_' . $template->element . '.sys', $client->path)
+ || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
+
+ $template_path = Path::clean(
+ $client->path
+ . '/templates/'
+ . $template->element
+ . '/html/'
+ . $extension
+ . '/'
+ . $view
+ );
+
+ // Add the layout options from the template path.
+ if (is_dir($template_path) && ($files = Folder::files($template_path, '^[^_]*\.php$', false, true))) {
+ foreach ($files as $i => $file) {
+ // Remove layout files that exist in the component folder
+ if (\in_array(basename($file, '.php'), $component_layouts)) {
+ unset($files[$i]);
+ }
+ }
+
+ if (\count($files)) {
+ // Create the group for the template
+ $groups[$template->name] = array();
+ $groups[$template->name]['id'] = $this->id . '_' . $template->element;
+ $groups[$template->name]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
+ $groups[$template->name]['items'] = array();
+
+ foreach ($files as $file) {
+ // Add an option to the template group
+ $value = basename($file, '.php');
+ $text = $lang
+ ->hasKey(
+ $key = strtoupper(
+ 'TPL_'
+ . $template->name
+ . '_'
+ . $extension
+ . '_'
+ . $view
+ . '_LAYOUT_'
+ . $value
+ )
+ )
+ ? Text::_($key) : $value;
+ $groups[$template->name]['items'][] = HTMLHelper::_('select.option', $template->element . ':' . $value, $text);
+ }
+ }
+ }
+ }
+ }
+
+ // Compute attributes for the grouped list
+ $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
+ $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
+
+ // Prepare HTML code
+ $html = array();
+
+ // Compute the current selected values
+ $selected = array($this->value);
+
+ // Add a grouped list
+ $html[] = HTMLHelper::_(
+ 'select.groupedlist',
+ $groups,
+ $this->name,
+ array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
+ );
+
+ return implode($html);
+ } else {
+ return '';
+ }
+ }
}
diff --git a/libraries/src/Form/Field/ComponentsField.php b/libraries/src/Form/Field/ComponentsField.php
index 085aa6cbf2246..62050892bbf59 100644
--- a/libraries/src/Form/Field/ComponentsField.php
+++ b/libraries/src/Form/Field/ComponentsField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('name', 'text'),
- $db->quoteName('element', 'value'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('enabled') . ' >= 1',
- $db->quoteName('type') . ' = ' . $db->quote('component'),
- ]
- );
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 2.5.0
+ */
+ protected function getOptions()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('name', 'text'),
+ $db->quoteName('element', 'value'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('enabled') . ' >= 1',
+ $db->quoteName('type') . ' = ' . $db->quote('component'),
+ ]
+ );
- $items = $db->setQuery($query)->loadObjectList();
+ $items = $db->setQuery($query)->loadObjectList();
- if ($items)
- {
- $lang = Factory::getLanguage();
+ if ($items) {
+ $lang = Factory::getLanguage();
- foreach ($items as &$item)
- {
- // Load language
- $extension = $item->value;
+ foreach ($items as &$item) {
+ // Load language
+ $extension = $item->value;
- $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
- || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension);
+ $lang->load("$extension.sys", JPATH_ADMINISTRATOR)
+ || $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension);
- // Translate component name
- $item->text = Text::_($item->text);
- }
+ // Translate component name
+ $item->text = Text::_($item->text);
+ }
- // Sort by component name
- $items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
- }
+ // Sort by component name
+ $items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $items);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $items);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/ContenthistoryField.php b/libraries/src/Form/Field/ContenthistoryField.php
index 642d697334b66..8108312c87a73 100644
--- a/libraries/src/Form/Field/ContenthistoryField.php
+++ b/libraries/src/Form/Field/ContenthistoryField.php
@@ -1,4 +1,5 @@
element['data-typeAlias'] . '.' . $this->form->getValue('id');
- $label = Text::_('JTOOLBAR_VERSIONS');
+ $itemId = $this->element['data-typeAlias'] . '.' . $this->form->getValue('id');
+ $label = Text::_('JTOOLBAR_VERSIONS');
- $link = 'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&field='
- . $this->id . '&item_id=' . $itemId . '&' . Session::getFormToken() . '=1';
+ $link = 'index.php?option=com_contenthistory&view=history&layout=modal&tmpl=component&field='
+ . $this->id . '&item_id=' . $itemId . '&' . Session::getFormToken() . '=1';
- $extraData = array(
- 'item' => $itemId,
- 'label' => $label,
- 'link' => $link,
- );
+ $extraData = array(
+ 'item' => $itemId,
+ 'label' => $label,
+ 'link' => $link,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
- /**
- * Method to get the content history field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.2
- */
- protected function getInput()
- {
- if (empty($this->layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
+ /**
+ * Method to get the content history field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
}
diff --git a/libraries/src/Form/Field/ContentlanguageField.php b/libraries/src/Form/Field/ContentlanguageField.php
index 7fa42ae8946b5..8cf9acbcad6f5 100644
--- a/libraries/src/Form/Field/ContentlanguageField.php
+++ b/libraries/src/Form/Field/ContentlanguageField.php
@@ -1,4 +1,5 @@
value))
- {
- if (\is_object($this->value))
- {
- $this->value = $this->value->tags;
- }
-
- if (\is_string($this->value))
- {
- $this->value = explode(',', $this->value);
- }
- }
-
- return parent::getInput();
- }
-
- /**
- * Method to get a list of content types
- *
- * @return array The field option objects.
- *
- * @since 3.1
- */
- protected function getOptions()
- {
- $lang = Factory::getLanguage();
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.type_id', 'value'),
- $db->quoteName('a.type_title', 'text'),
- $db->quoteName('a.type_alias', 'alias'),
- ]
- )
- ->from($db->quoteName('#__content_types', 'a'))
- ->order($db->quoteName('a.type_title') . ' ASC');
-
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- return array();
- }
-
- foreach ($options as $option)
- {
- // Make up the string from the component sys.ini file
- $parts = explode('.', $option->alias);
- $comp = array_shift($parts);
-
- // Make sure the component sys.ini is loaded
- $lang->load($comp . '.sys', JPATH_ADMINISTRATOR)
- || $lang->load($comp . '.sys', JPATH_ADMINISTRATOR . '/components/' . $comp);
-
- $option->string = implode('_', $parts);
- $option->string = $comp . '_CONTENT_TYPE_' . $option->string;
-
- if ($lang->hasKey($option->string))
- {
- $option->text = Text::_($option->string);
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * A flexible tag list that respects access controls
+ *
+ * @var string
+ * @since 3.1
+ */
+ public $type = 'Contenttype';
+
+ /**
+ * Method to get the field input for a list of content types.
+ *
+ * @return string The field input.
+ *
+ * @since 3.1
+ */
+ protected function getInput()
+ {
+ if (!\is_array($this->value)) {
+ if (\is_object($this->value)) {
+ $this->value = $this->value->tags;
+ }
+
+ if (\is_string($this->value)) {
+ $this->value = explode(',', $this->value);
+ }
+ }
+
+ return parent::getInput();
+ }
+
+ /**
+ * Method to get a list of content types
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.1
+ */
+ protected function getOptions()
+ {
+ $lang = Factory::getLanguage();
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.type_id', 'value'),
+ $db->quoteName('a.type_title', 'text'),
+ $db->quoteName('a.type_alias', 'alias'),
+ ]
+ )
+ ->from($db->quoteName('#__content_types', 'a'))
+ ->order($db->quoteName('a.type_title') . ' ASC');
+
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ return array();
+ }
+
+ foreach ($options as $option) {
+ // Make up the string from the component sys.ini file
+ $parts = explode('.', $option->alias);
+ $comp = array_shift($parts);
+
+ // Make sure the component sys.ini is loaded
+ $lang->load($comp . '.sys', JPATH_ADMINISTRATOR)
+ || $lang->load($comp . '.sys', JPATH_ADMINISTRATOR . '/components/' . $comp);
+
+ $option->string = implode('_', $parts);
+ $option->string = $comp . '_CONTENT_TYPE_' . $option->string;
+
+ if ($lang->hasKey($option->string)) {
+ $option->text = Text::_($option->string);
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/DatabaseconnectionField.php b/libraries/src/Form/Field/DatabaseconnectionField.php
index 11d612096cf1e..b6b1886a4113c 100644
--- a/libraries/src/Form/Field/DatabaseconnectionField.php
+++ b/libraries/src/Form/Field/DatabaseconnectionField.php
@@ -1,4 +1,5 @@
element['supported'];
+ /**
+ * This gets the list of database types supported by the application.
+ * This should be entered in the form definition as a comma separated list.
+ * If no supported databases are listed, it is assumed all available databases
+ * are supported.
+ */
+ $supported = $this->element['supported'];
- if (!empty($supported))
- {
- $supported = explode(',', $supported);
+ if (!empty($supported)) {
+ $supported = explode(',', $supported);
- foreach ($supported as $support)
- {
- if (\in_array($support, $available))
- {
- $options[$support] = Text::_(ucfirst($support));
- }
- }
- }
- else
- {
- foreach ($available as $support)
- {
- $options[$support] = Text::_(ucfirst($support));
- }
- }
+ foreach ($supported as $support) {
+ if (\in_array($support, $available)) {
+ $options[$support] = Text::_(ucfirst($support));
+ }
+ }
+ } else {
+ foreach ($available as $support) {
+ $options[$support] = Text::_(ucfirst($support));
+ }
+ }
- // This will come into play if an application is installed that requires
- // a database that is not available on the server.
- if (empty($options))
- {
- $options[''] = Text::_('JNONE');
- }
+ // This will come into play if an application is installed that requires
+ // a database that is not available on the server.
+ if (empty($options)) {
+ $options[''] = Text::_('JNONE');
+ }
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/EditorField.php b/libraries/src/Form/Field/EditorField.php
index 8ee458a13de61..dd1140522bdf6 100644
--- a/libraries/src/Form/Field/EditorField.php
+++ b/libraries/src/Form/Field/EditorField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'height':
- case 'width':
- case 'assetField':
- case 'authorField':
- case 'asset':
- $this->$name = (string) $value;
- break;
-
- case 'buttons':
- $value = (string) $value;
-
- if ($value === 'true' || $value === 'yes' || $value === '1')
- {
- $this->buttons = true;
- }
- elseif ($value === 'false' || $value === 'no' || $value === '0')
- {
- $this->buttons = false;
- }
- else
- {
- $this->buttons = explode(',', $value);
- }
- break;
-
- case 'hide':
- $value = (string) $value;
- $this->hide = $value ? explode(',', $value) : array();
- break;
-
- case 'editorType':
- // Can be in the form of: editor="desired|alternative".
- $this->editorType = explode('|', trim((string) $value));
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- $this->height = $this->element['height'] ? (string) $this->element['height'] : '500';
- $this->width = $this->element['width'] ? (string) $this->element['width'] : '100%';
- $this->assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
- $this->authorField = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
- $this->asset = $this->form->getValue($this->assetField) ?: (string) $this->element['asset_id'];
-
- $buttons = (string) $this->element['buttons'];
- $hide = (string) $this->element['hide'];
- $editorType = (string) $this->element['editor'];
-
- if ($buttons === 'true' || $buttons === 'yes' || $buttons === '1')
- {
- $this->buttons = true;
- }
- elseif ($buttons === 'false' || $buttons === 'no' || $buttons === '0')
- {
- $this->buttons = false;
- }
- else
- {
- $this->buttons = !empty($hide) ? explode(',', $buttons) : array();
- }
-
- $this->hide = !empty($hide) ? explode(',', (string) $this->element['hide']) : array();
- $this->editorType = !empty($editorType) ? explode('|', trim($editorType)) : array();
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup for the editor area
- *
- * @return string The field input markup.
- *
- * @since 1.6
- */
- protected function getInput()
- {
- // Get an editor object.
- $editor = $this->getEditor();
- $params = array(
- 'autofocus' => $this->autofocus,
- 'readonly' => $this->readonly || $this->disabled,
- 'syntax' => (string) $this->element['syntax'],
- );
-
- return $editor->display(
- $this->name,
- htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8'),
- $this->width,
- $this->height,
- $this->columns,
- $this->rows,
- $this->buttons ? (\is_array($this->buttons) ? array_merge($this->buttons, $this->hide) : $this->hide) : false,
- $this->id,
- $this->asset,
- $this->form->getValue($this->authorField),
- $params
- );
- }
-
- /**
- * Method to get an Editor object based on the form field.
- *
- * @return Editor The Editor object.
- *
- * @since 1.6
- */
- protected function getEditor()
- {
- // Only create the editor if it is not already created.
- if (empty($this->editor))
- {
- $editor = null;
-
- if ($this->editorType)
- {
- // Get the list of editor types.
- $types = $this->editorType;
-
- // Get the database object.
- $db = $this->getDatabase();
-
- // Build the query.
- $query = $db->getQuery(true)
- ->select($db->quoteName('element'))
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('element') . ' = :editor',
- $db->quoteName('folder') . ' = ' . $db->quote('editors'),
- $db->quoteName('enabled') . ' = 1',
- ]
- );
-
- // Declare variable before binding.
- $element = '';
- $query->bind(':editor', $element);
- $query->setLimit(1);
-
- // Iterate over the types looking for an existing editor.
- foreach ($types as $element)
- {
- // Check if the editor exists.
- $db->setQuery($query);
- $editor = $db->loadResult();
-
- // If an editor was found stop looking.
- if ($editor)
- {
- break;
- }
- }
- }
-
- // Create the JEditor instance based on the given editor.
- if ($editor === null)
- {
- $editor = Factory::getApplication()->get('editor');
- }
-
- $this->editor = Editor::getInstance($editor);
- }
-
- return $this->editor;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'Editor';
+
+ /**
+ * The Editor object.
+ *
+ * @var Editor
+ * @since 1.6
+ */
+ protected $editor;
+
+ /**
+ * The height of the editor.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $height;
+
+ /**
+ * The width of the editor.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $width;
+
+ /**
+ * The assetField of the editor.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $assetField;
+
+ /**
+ * The authorField of the editor.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $authorField;
+
+ /**
+ * The asset of the editor.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $asset;
+
+ /**
+ * The buttons of the editor.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $buttons;
+
+ /**
+ * The hide of the editor.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $hide;
+
+ /**
+ * The editorType of the editor.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $editorType;
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'height':
+ case 'width':
+ case 'assetField':
+ case 'authorField':
+ case 'asset':
+ case 'buttons':
+ case 'hide':
+ case 'editorType':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'height':
+ case 'width':
+ case 'assetField':
+ case 'authorField':
+ case 'asset':
+ $this->$name = (string) $value;
+ break;
+
+ case 'buttons':
+ $value = (string) $value;
+
+ if ($value === 'true' || $value === 'yes' || $value === '1') {
+ $this->buttons = true;
+ } elseif ($value === 'false' || $value === 'no' || $value === '0') {
+ $this->buttons = false;
+ } else {
+ $this->buttons = explode(',', $value);
+ }
+ break;
+
+ case 'hide':
+ $value = (string) $value;
+ $this->hide = $value ? explode(',', $value) : array();
+ break;
+
+ case 'editorType':
+ // Can be in the form of: editor="desired|alternative".
+ $this->editorType = explode('|', trim((string) $value));
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ $this->height = $this->element['height'] ? (string) $this->element['height'] : '500';
+ $this->width = $this->element['width'] ? (string) $this->element['width'] : '100%';
+ $this->assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
+ $this->authorField = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
+ $this->asset = $this->form->getValue($this->assetField) ?: (string) $this->element['asset_id'];
+
+ $buttons = (string) $this->element['buttons'];
+ $hide = (string) $this->element['hide'];
+ $editorType = (string) $this->element['editor'];
+
+ if ($buttons === 'true' || $buttons === 'yes' || $buttons === '1') {
+ $this->buttons = true;
+ } elseif ($buttons === 'false' || $buttons === 'no' || $buttons === '0') {
+ $this->buttons = false;
+ } else {
+ $this->buttons = !empty($hide) ? explode(',', $buttons) : array();
+ }
+
+ $this->hide = !empty($hide) ? explode(',', (string) $this->element['hide']) : array();
+ $this->editorType = !empty($editorType) ? explode('|', trim($editorType)) : array();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup for the editor area
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ // Get an editor object.
+ $editor = $this->getEditor();
+ $params = array(
+ 'autofocus' => $this->autofocus,
+ 'readonly' => $this->readonly || $this->disabled,
+ 'syntax' => (string) $this->element['syntax'],
+ );
+
+ return $editor->display(
+ $this->name,
+ htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8'),
+ $this->width,
+ $this->height,
+ $this->columns,
+ $this->rows,
+ $this->buttons ? (\is_array($this->buttons) ? array_merge($this->buttons, $this->hide) : $this->hide) : false,
+ $this->id,
+ $this->asset,
+ $this->form->getValue($this->authorField),
+ $params
+ );
+ }
+
+ /**
+ * Method to get an Editor object based on the form field.
+ *
+ * @return Editor The Editor object.
+ *
+ * @since 1.6
+ */
+ protected function getEditor()
+ {
+ // Only create the editor if it is not already created.
+ if (empty($this->editor)) {
+ $editor = null;
+
+ if ($this->editorType) {
+ // Get the list of editor types.
+ $types = $this->editorType;
+
+ // Get the database object.
+ $db = $this->getDatabase();
+
+ // Build the query.
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('element'))
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('element') . ' = :editor',
+ $db->quoteName('folder') . ' = ' . $db->quote('editors'),
+ $db->quoteName('enabled') . ' = 1',
+ ]
+ );
+
+ // Declare variable before binding.
+ $element = '';
+ $query->bind(':editor', $element);
+ $query->setLimit(1);
+
+ // Iterate over the types looking for an existing editor.
+ foreach ($types as $element) {
+ // Check if the editor exists.
+ $db->setQuery($query);
+ $editor = $db->loadResult();
+
+ // If an editor was found stop looking.
+ if ($editor) {
+ break;
+ }
+ }
+ }
+
+ // Create the JEditor instance based on the given editor.
+ if ($editor === null) {
+ $editor = Factory::getApplication()->get('editor');
+ }
+
+ $this->editor = Editor::getInstance($editor);
+ }
+
+ return $this->editor;
+ }
}
diff --git a/libraries/src/Form/Field/EmailField.php b/libraries/src/Form/Field/EmailField.php
index 957c8502c3b6d..6dd03d7abb087 100644
--- a/libraries/src/Form/Field/EmailField.php
+++ b/libraries/src/Form/Field/EmailField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the field input markup for email addresses.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- $extraData = array(
- 'maxLength' => $this->maxLength,
- 'multiple' => $this->multiple,
- );
+ $extraData = array(
+ 'maxLength' => $this->maxLength,
+ 'multiple' => $this->multiple,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/FileField.php b/libraries/src/Form/Field/FileField.php
index ccfd70198e462..9482a55733cb3 100644
--- a/libraries/src/Form/Field/FileField.php
+++ b/libraries/src/Form/Field/FileField.php
@@ -1,4 +1,5 @@
accept;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'accept':
- $this->accept = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->accept = (string) $this->element['accept'];
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup for the file field.
- * Field attributes allow specification of a maximum file size and a string
- * of accepted file extensions.
- *
- * @return string The field input markup.
- *
- * @note The field does not include an upload mechanism.
- * @see \Joomla\CMS\Form\Field\MediaField
- * @since 1.7.0
- */
- protected function getInput()
- {
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.6
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- $extraData = array(
- 'accept' => $this->accept,
- 'multiple' => $this->multiple,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'File';
+
+ /**
+ * The accepted file type list.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $accept;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.6
+ */
+ protected $layout = 'joomla.form.field.file';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'accept') {
+ return $this->accept;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'accept':
+ $this->accept = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->accept = (string) $this->element['accept'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup for the file field.
+ * Field attributes allow specification of a maximum file size and a string
+ * of accepted file extensions.
+ *
+ * @return string The field input markup.
+ *
+ * @note The field does not include an upload mechanism.
+ * @see \Joomla\CMS\Form\Field\MediaField
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.6
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ $extraData = array(
+ 'accept' => $this->accept,
+ 'multiple' => $this->multiple,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/FilelistField.php b/libraries/src/Form/Field/FilelistField.php
index 29a993e742796..63a5efad3f8c8 100644
--- a/libraries/src/Form/Field/FilelistField.php
+++ b/libraries/src/Form/Field/FilelistField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'fileFilter':
- case 'directory':
- case 'exclude':
- $this->$name = (string) $value;
- break;
-
- case 'hideNone':
- case 'hideDefault':
- case 'stripExt':
- $value = (string) $value;
- $this->$name = ($value === 'true' || $value === $name || $value === '1');
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->fileFilter = (string) $this->element['fileFilter'];
- $this->exclude = (string) $this->element['exclude'];
-
- $hideNone = (string) $this->element['hide_none'];
- $this->hideNone = ($hideNone === 'true' || $hideNone === 'hideNone' || $hideNone === '1');
-
- $hideDefault = (string) $this->element['hide_default'];
- $this->hideDefault = ($hideDefault === 'true' || $hideDefault === 'hideDefault' || $hideDefault === '1');
-
- $stripExt = (string) $this->element['stripext'];
- $this->stripExt = ($stripExt === 'true' || $stripExt === 'stripExt' || $stripExt === '1');
-
- // Get the path in which to search for file options.
- $this->directory = (string) $this->element['directory'];
- }
-
- return $return;
- }
-
- /**
- * Method to get the list of files for the field options.
- * Specify the target directory with a directory attribute
- * Attributes allow an exclude mask and stripping of extensions from file name.
- * Default attribute may optionally be set to null (no file) or -1 (use a default).
- *
- * @return array The field option objects.
- *
- * @since 1.7.0
- */
- protected function getOptions()
- {
- $options = array();
-
- $path = $this->directory;
-
- if (!is_dir($path))
- {
- $path = JPATH_ROOT . '/' . $path;
- }
-
- $path = Path::clean($path);
-
- // Prepend some default options based on field attributes.
- if (!$this->hideNone)
- {
- $options[] = HTMLHelper::_('select.option', '-1', Text::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
- }
-
- if (!$this->hideDefault)
- {
- $options[] = HTMLHelper::_('select.option', '', Text::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
- }
-
- // Get a list of files in the search path with the given filter.
- $files = Folder::files($path, $this->fileFilter);
-
- // Build the options list from the list of files.
- if (\is_array($files))
- {
- foreach ($files as $file)
- {
- // Check to see if the file is in the exclude mask.
- if ($this->exclude)
- {
- if (preg_match(\chr(1) . $this->exclude . \chr(1), $file))
- {
- continue;
- }
- }
-
- // If the extension is to be stripped, do it.
- if ($this->stripExt)
- {
- $file = File::stripExt($file);
- }
-
- $options[] = HTMLHelper::_('select.option', $file, $file);
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Filelist';
+
+ /**
+ * The filename filter.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $fileFilter;
+
+ /**
+ * The exclude.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $exclude;
+
+ /**
+ * The hideNone.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $hideNone = false;
+
+ /**
+ * The hideDefault.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $hideDefault = false;
+
+ /**
+ * The stripExt.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $stripExt = false;
+
+ /**
+ * The directory.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $directory;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'fileFilter':
+ case 'exclude':
+ case 'hideNone':
+ case 'hideDefault':
+ case 'stripExt':
+ case 'directory':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'fileFilter':
+ case 'directory':
+ case 'exclude':
+ $this->$name = (string) $value;
+ break;
+
+ case 'hideNone':
+ case 'hideDefault':
+ case 'stripExt':
+ $value = (string) $value;
+ $this->$name = ($value === 'true' || $value === $name || $value === '1');
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->fileFilter = (string) $this->element['fileFilter'];
+ $this->exclude = (string) $this->element['exclude'];
+
+ $hideNone = (string) $this->element['hide_none'];
+ $this->hideNone = ($hideNone === 'true' || $hideNone === 'hideNone' || $hideNone === '1');
+
+ $hideDefault = (string) $this->element['hide_default'];
+ $this->hideDefault = ($hideDefault === 'true' || $hideDefault === 'hideDefault' || $hideDefault === '1');
+
+ $stripExt = (string) $this->element['stripext'];
+ $this->stripExt = ($stripExt === 'true' || $stripExt === 'stripExt' || $stripExt === '1');
+
+ // Get the path in which to search for file options.
+ $this->directory = (string) $this->element['directory'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the list of files for the field options.
+ * Specify the target directory with a directory attribute
+ * Attributes allow an exclude mask and stripping of extensions from file name.
+ * Default attribute may optionally be set to null (no file) or -1 (use a default).
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ $path = $this->directory;
+
+ if (!is_dir($path)) {
+ $path = JPATH_ROOT . '/' . $path;
+ }
+
+ $path = Path::clean($path);
+
+ // Prepend some default options based on field attributes.
+ if (!$this->hideNone) {
+ $options[] = HTMLHelper::_('select.option', '-1', Text::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
+ }
+
+ if (!$this->hideDefault) {
+ $options[] = HTMLHelper::_('select.option', '', Text::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
+ }
+
+ // Get a list of files in the search path with the given filter.
+ $files = Folder::files($path, $this->fileFilter);
+
+ // Build the options list from the list of files.
+ if (\is_array($files)) {
+ foreach ($files as $file) {
+ // Check to see if the file is in the exclude mask.
+ if ($this->exclude) {
+ if (preg_match(\chr(1) . $this->exclude . \chr(1), $file)) {
+ continue;
+ }
+ }
+
+ // If the extension is to be stripped, do it.
+ if ($this->stripExt) {
+ $file = File::stripExt($file);
+ }
+
+ $options[] = HTMLHelper::_('select.option', $file, $file);
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/FolderlistField.php b/libraries/src/Form/Field/FolderlistField.php
index eb0f6a0a60ad3..bb9d160d5e895 100644
--- a/libraries/src/Form/Field/FolderlistField.php
+++ b/libraries/src/Form/Field/FolderlistField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'folderFilter':
- case 'directory':
- case 'exclude':
- case 'recursive':
- $this->$name = (string) $value;
- break;
-
- case 'hideNone':
- case 'hideDefault':
- $value = (string) $value;
- $this->$name = ($value === 'true' || $value === $name || $value === '1');
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->folderFilter = (string) $this->element['folderFilter'];
- $this->exclude = (string) $this->element['exclude'];
-
- $recursive = (string) $this->element['recursive'];
- $this->recursive = ($recursive === 'true' || $recursive === 'recursive' || $recursive === '1');
-
- $hideNone = (string) $this->element['hide_none'];
- $this->hideNone = ($hideNone === 'true' || $hideNone === 'hideNone' || $hideNone === '1');
-
- $hideDefault = (string) $this->element['hide_default'];
- $this->hideDefault = ($hideDefault === 'true' || $hideDefault === 'hideDefault' || $hideDefault === '1');
-
- // Get the path in which to search for file options.
- $this->directory = (string) $this->element['directory'];
- }
-
- return $return;
- }
-
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 1.7.0
- */
- protected function getOptions()
- {
- $options = array();
-
- $path = $this->directory;
-
- if (!is_dir($path))
- {
- if (is_dir(JPATH_ROOT . '/' . $path))
- {
- $path = JPATH_ROOT . '/' . $path;
- }
- else
- {
- return [];
- }
- }
-
- $path = Path::clean($path);
-
- // Prepend some default options based on field attributes.
- if (!$this->hideNone)
- {
- $options[] = HTMLHelper::_('select.option', '-1', Text::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
- }
-
- if (!$this->hideDefault)
- {
- $options[] = HTMLHelper::_('select.option', '', Text::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
- }
-
- // Get a list of folders in the search path with the given filter.
- $folders = Folder::folders($path, $this->folderFilter, $this->recursive, true);
-
- // Build the options list from the list of folders.
- if (\is_array($folders))
- {
- foreach ($folders as $folder)
- {
- // Remove the root part and the leading /
- $folder = trim(str_replace($path, '', $folder), DIRECTORY_SEPARATOR);
-
- // Check to see if the file is in the exclude mask.
- if ($this->exclude)
- {
- if (preg_match(\chr(1) . $this->exclude . \chr(1), $folder))
- {
- continue;
- }
- }
-
- $options[] = HTMLHelper::_('select.option', $folder, $folder);
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Folderlist';
+
+ /**
+ * The folder name filter.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $folderFilter;
+
+ /**
+ * The exclude.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $exclude;
+
+ /**
+ * The recursive.
+ *
+ * @var string
+ * @since 3.6
+ */
+ protected $recursive;
+
+ /**
+ * The hideNone.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $hideNone = false;
+
+ /**
+ * The hideDefault.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $hideDefault = false;
+
+ /**
+ * The directory.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $directory;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'folderFilter':
+ case 'exclude':
+ case 'recursive':
+ case 'hideNone':
+ case 'hideDefault':
+ case 'directory':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'folderFilter':
+ case 'directory':
+ case 'exclude':
+ case 'recursive':
+ $this->$name = (string) $value;
+ break;
+
+ case 'hideNone':
+ case 'hideDefault':
+ $value = (string) $value;
+ $this->$name = ($value === 'true' || $value === $name || $value === '1');
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->folderFilter = (string) $this->element['folderFilter'];
+ $this->exclude = (string) $this->element['exclude'];
+
+ $recursive = (string) $this->element['recursive'];
+ $this->recursive = ($recursive === 'true' || $recursive === 'recursive' || $recursive === '1');
+
+ $hideNone = (string) $this->element['hide_none'];
+ $this->hideNone = ($hideNone === 'true' || $hideNone === 'hideNone' || $hideNone === '1');
+
+ $hideDefault = (string) $this->element['hide_default'];
+ $this->hideDefault = ($hideDefault === 'true' || $hideDefault === 'hideDefault' || $hideDefault === '1');
+
+ // Get the path in which to search for file options.
+ $this->directory = (string) $this->element['directory'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ $path = $this->directory;
+
+ if (!is_dir($path)) {
+ if (is_dir(JPATH_ROOT . '/' . $path)) {
+ $path = JPATH_ROOT . '/' . $path;
+ } else {
+ return [];
+ }
+ }
+
+ $path = Path::clean($path);
+
+ // Prepend some default options based on field attributes.
+ if (!$this->hideNone) {
+ $options[] = HTMLHelper::_('select.option', '-1', Text::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
+ }
+
+ if (!$this->hideDefault) {
+ $options[] = HTMLHelper::_('select.option', '', Text::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
+ }
+
+ // Get a list of folders in the search path with the given filter.
+ $folders = Folder::folders($path, $this->folderFilter, $this->recursive, true);
+
+ // Build the options list from the list of folders.
+ if (\is_array($folders)) {
+ foreach ($folders as $folder) {
+ // Remove the root part and the leading /
+ $folder = trim(str_replace($path, '', $folder), DIRECTORY_SEPARATOR);
+
+ // Check to see if the file is in the exclude mask.
+ if ($this->exclude) {
+ if (preg_match(\chr(1) . $this->exclude . \chr(1), $folder)) {
+ continue;
+ }
+ }
+
+ $options[] = HTMLHelper::_('select.option', $folder, $folder);
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/FrontendlanguageField.php b/libraries/src/Form/Field/FrontendlanguageField.php
index 869f060b65405..2874c2e9b5c01 100644
--- a/libraries/src/Form/Field/FrontendlanguageField.php
+++ b/libraries/src/Form/Field/FrontendlanguageField.php
@@ -1,4 +1,5 @@
getDatabase();
- $query = $db->getQuery(true);
+ /**
+ * Method to get the field options for frontend published content languages with homes.
+ *
+ * @return array The options the field is going to show.
+ *
+ * @since 3.5
+ */
+ protected function getOptions()
+ {
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
- $query->select('a.lang_code AS value, a.title AS text')
- ->from($db->quoteName('#__languages') . ' AS a')
- ->where('a.published = 1')
- ->order('a.title');
+ $query->select('a.lang_code AS value, a.title AS text')
+ ->from($db->quoteName('#__languages') . ' AS a')
+ ->where('a.published = 1')
+ ->order('a.title');
- // Select the language home pages.
- $query->select('l.home, l.language')
- ->innerJoin($db->quoteName('#__menu') . ' AS l ON l.language=a.lang_code AND l.home=1 AND l.published=1 AND l.language <> ' . $db->quote('*'))
- ->innerJoin($db->quoteName('#__extensions') . ' AS e ON e.element = a.lang_code')
- ->where('e.client_id = 0')
- ->where('e.enabled = 1')
- ->where('e.state = 0');
+ // Select the language home pages.
+ $query->select('l.home, l.language')
+ ->innerJoin($db->quoteName('#__menu') . ' AS l ON l.language=a.lang_code AND l.home=1 AND l.published=1 AND l.language <> ' . $db->quote('*'))
+ ->innerJoin($db->quoteName('#__extensions') . ' AS e ON e.element = a.lang_code')
+ ->where('e.client_id = 0')
+ ->where('e.enabled = 1')
+ ->where('e.state = 0');
- $db->setQuery($query);
+ $db->setQuery($query);
- try
- {
- $languages = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- $languages = array();
+ try {
+ $languages = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ $languages = array();
- if (Factory::getUser()->authorise('core.admin'))
- {
- Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
- }
- }
+ if (Factory::getUser()->authorise('core.admin')) {
+ Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+ }
+ }
- // Merge any additional options in the XML definition.
- return array_merge(parent::getOptions(), $languages);
- }
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getOptions(), $languages);
+ }
}
diff --git a/libraries/src/Form/Field/GroupedlistField.php b/libraries/src/Form/Field/GroupedlistField.php
index 756856129c138..ffb613c312197 100644
--- a/libraries/src/Form/Field/GroupedlistField.php
+++ b/libraries/src/Form/Field/GroupedlistField.php
@@ -1,4 +1,5 @@
element->children() as $element)
- {
- switch ($element->getName())
- {
- // The element is an
- case 'option':
- // Initialize the group if necessary.
- if (!isset($groups[$label]))
- {
- $groups[$label] = array();
- }
-
- $disabled = (string) $element['disabled'];
- $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
-
- // Create a new option object based on the element.
- $tmp = HTMLHelper::_(
- 'select.option', ($element['value']) ? (string) $element['value'] : trim((string) $element),
- Text::alt(trim((string) $element), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)), 'value', 'text',
- $disabled
- );
-
- // Set some option attributes.
- $tmp->class = (string) $element['class'];
-
- // Set some JavaScript option attributes.
- $tmp->onclick = (string) $element['onclick'];
-
- // Add the option.
- $groups[$label][] = $tmp;
- break;
-
- // The element is a
- case 'group':
- // Get the group label.
- if ($groupLabel = (string) $element['label'])
- {
- $label = Text::_($groupLabel);
- }
-
- // Initialize the group if necessary.
- if (!isset($groups[$label]))
- {
- $groups[$label] = array();
- }
-
- // Iterate through the children and build an array of options.
- foreach ($element->children() as $option)
- {
- // Only add elements.
- if ($option->getName() !== 'option')
- {
- continue;
- }
-
- $disabled = (string) $option['disabled'];
- $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
-
- // Create a new option object based on the element.
- $tmp = HTMLHelper::_(
- 'select.option', ($option['value']) ? (string) $option['value'] : Text::_(trim((string) $option)),
- Text::_(trim((string) $option)), 'value', 'text', $disabled
- );
-
- // Set some option attributes.
- $tmp->class = (string) $option['class'];
-
- // Set some JavaScript option attributes.
- $tmp->onclick = (string) $option['onclick'];
-
- // Add the option.
- $groups[$label][] = $tmp;
- }
-
- if ($groupLabel)
- {
- $label = \count($groups);
- }
- break;
-
- // Unknown element type.
- default:
- throw new \UnexpectedValueException(sprintf('Unsupported element %s in GroupedlistField', $element->getName()), 500);
- }
- }
-
- reset($groups);
-
- return $groups;
- }
-
- /**
- * Method to get the field input markup fora grouped list.
- * Multiselect is enabled by using the multiple attribute.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- $data = $this->getLayoutData();
-
- // Get the field groups.
- $data['groups'] = (array) $this->getGroups();
-
- return $this->getRenderer($this->layout)->render($data);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Groupedlist';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.groupedlist';
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+ $label = 0;
+
+ foreach ($this->element->children() as $element) {
+ switch ($element->getName()) {
+ // The element is an
+ case 'option':
+ // Initialize the group if necessary.
+ if (!isset($groups[$label])) {
+ $groups[$label] = array();
+ }
+
+ $disabled = (string) $element['disabled'];
+ $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
+
+ // Create a new option object based on the element.
+ $tmp = HTMLHelper::_(
+ 'select.option',
+ ($element['value']) ? (string) $element['value'] : trim((string) $element),
+ Text::alt(trim((string) $element), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)),
+ 'value',
+ 'text',
+ $disabled
+ );
+
+ // Set some option attributes.
+ $tmp->class = (string) $element['class'];
+
+ // Set some JavaScript option attributes.
+ $tmp->onclick = (string) $element['onclick'];
+
+ // Add the option.
+ $groups[$label][] = $tmp;
+ break;
+
+ // The element is a
+ case 'group':
+ // Get the group label.
+ if ($groupLabel = (string) $element['label']) {
+ $label = Text::_($groupLabel);
+ }
+
+ // Initialize the group if necessary.
+ if (!isset($groups[$label])) {
+ $groups[$label] = array();
+ }
+
+ // Iterate through the children and build an array of options.
+ foreach ($element->children() as $option) {
+ // Only add elements.
+ if ($option->getName() !== 'option') {
+ continue;
+ }
+
+ $disabled = (string) $option['disabled'];
+ $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
+
+ // Create a new option object based on the element.
+ $tmp = HTMLHelper::_(
+ 'select.option',
+ ($option['value']) ? (string) $option['value'] : Text::_(trim((string) $option)),
+ Text::_(trim((string) $option)),
+ 'value',
+ 'text',
+ $disabled
+ );
+
+ // Set some option attributes.
+ $tmp->class = (string) $option['class'];
+
+ // Set some JavaScript option attributes.
+ $tmp->onclick = (string) $option['onclick'];
+
+ // Add the option.
+ $groups[$label][] = $tmp;
+ }
+
+ if ($groupLabel) {
+ $label = \count($groups);
+ }
+ break;
+
+ // Unknown element type.
+ default:
+ throw new \UnexpectedValueException(sprintf('Unsupported element %s in GroupedlistField', $element->getName()), 500);
+ }
+ }
+
+ reset($groups);
+
+ return $groups;
+ }
+
+ /**
+ * Method to get the field input markup fora grouped list.
+ * Multiselect is enabled by using the multiple attribute.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ $data = $this->getLayoutData();
+
+ // Get the field groups.
+ $data['groups'] = (array) $this->getGroups();
+
+ return $this->getRenderer($this->layout)->render($data);
+ }
}
diff --git a/libraries/src/Form/Field/HeadertagField.php b/libraries/src/Form/Field/HeadertagField.php
index 7d861fb5fcea3..50945edf7c0fc 100644
--- a/libraries/src/Form/Field/HeadertagField.php
+++ b/libraries/src/Form/Field/HeadertagField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- return parent::getLayoutData();
- }
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ return parent::getLayoutData();
+ }
}
diff --git a/libraries/src/Form/Field/ImagelistField.php b/libraries/src/Form/Field/ImagelistField.php
index b16a2cfacee0c..fb511c53a071c 100644
--- a/libraries/src/Form/Field/ImagelistField.php
+++ b/libraries/src/Form/Field/ImagelistField.php
@@ -1,4 +1,5 @@
fileFilter = '\.png$|\.gif$|\.jpg$|\.bmp$|\.ico$|\.jpeg$|\.psd$|\.eps$';
+ /**
+ * Method to get the list of images field options.
+ * Use the filter attribute to specify allowable file extensions.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ // Define the image file type filter.
+ $this->fileFilter = '\.png$|\.gif$|\.jpg$|\.bmp$|\.ico$|\.jpeg$|\.psd$|\.eps$';
- // Get the field options.
- return parent::getOptions();
- }
+ // Get the field options.
+ return parent::getOptions();
+ }
}
diff --git a/libraries/src/Form/Field/IntegerField.php b/libraries/src/Form/Field/IntegerField.php
index 29bbf013dffec..ebe27eaa6b59c 100644
--- a/libraries/src/Form/Field/IntegerField.php
+++ b/libraries/src/Form/Field/IntegerField.php
@@ -1,4 +1,5 @@
element['first'];
- $last = (int) $this->element['last'];
- $step = (int) $this->element['step'];
+ // Initialize some field attributes.
+ $first = (int) $this->element['first'];
+ $last = (int) $this->element['last'];
+ $step = (int) $this->element['step'];
- // Sanity checks.
- if ($step == 0)
- {
- // Step of 0 will create an endless loop.
- return $options;
- }
- elseif ($first < $last && $step < 0)
- {
- // A negative step will never reach the last number.
- return $options;
- }
- elseif ($first > $last && $step > 0)
- {
- // A position step will never reach the last number.
- return $options;
- }
- elseif ($step < 0)
- {
- // Build the options array backwards.
- for ($i = $first; $i >= $last; $i += $step)
- {
- $options[] = HTMLHelper::_('select.option', $i);
- }
- }
- else
- {
- // Build the options array.
- for ($i = $first; $i <= $last; $i += $step)
- {
- $options[] = HTMLHelper::_('select.option', $i);
- }
- }
+ // Sanity checks.
+ if ($step == 0) {
+ // Step of 0 will create an endless loop.
+ return $options;
+ } elseif ($first < $last && $step < 0) {
+ // A negative step will never reach the last number.
+ return $options;
+ } elseif ($first > $last && $step > 0) {
+ // A position step will never reach the last number.
+ return $options;
+ } elseif ($step < 0) {
+ // Build the options array backwards.
+ for ($i = $first; $i >= $last; $i += $step) {
+ $options[] = HTMLHelper::_('select.option', $i);
+ }
+ } else {
+ // Build the options array.
+ for ($i = $first; $i <= $last; $i += $step) {
+ $options[] = HTMLHelper::_('select.option', $i);
+ }
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/LanguageField.php b/libraries/src/Form/Field/LanguageField.php
index 711aa15e657fe..14b75e63e0b66 100644
--- a/libraries/src/Form/Field/LanguageField.php
+++ b/libraries/src/Form/Field/LanguageField.php
@@ -1,4 +1,5 @@
element['client'];
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ // Initialize some field attributes.
+ $client = (string) $this->element['client'];
- if ($client !== 'site' && $client !== 'administrator')
- {
- $client = 'site';
- }
+ if ($client !== 'site' && $client !== 'administrator') {
+ $client = 'site';
+ }
- // Make sure the languages are sorted base on locale instead of random sorting
- $languages = LanguageHelper::createLanguageList($this->value, \constant('JPATH_' . strtoupper($client)), true, true);
+ // Make sure the languages are sorted base on locale instead of random sorting
+ $languages = LanguageHelper::createLanguageList($this->value, \constant('JPATH_' . strtoupper($client)), true, true);
- if (\count($languages) > 1)
- {
- usort(
- $languages,
- function ($a, $b)
- {
- return strcmp($a['value'], $b['value']);
- }
- );
- }
+ if (\count($languages) > 1) {
+ usort(
+ $languages,
+ function ($a, $b) {
+ return strcmp($a['value'], $b['value']);
+ }
+ );
+ }
- // Merge any additional options in the XML definition.
- $options = array_merge(
- parent::getOptions(),
- $languages
- );
+ // Merge any additional options in the XML definition.
+ $options = array_merge(
+ parent::getOptions(),
+ $languages
+ );
- // Set the default value active language
- if ($langParams = ComponentHelper::getParams('com_languages'))
- {
- switch ((string) $this->value)
- {
- case 'site':
- case 'frontend':
- case '0':
- $this->value = $langParams->get('site', 'en-GB');
- break;
- case 'admin':
- case 'administrator':
- case 'backend':
- case '1':
- $this->value = $langParams->get('administrator', 'en-GB');
- break;
- case 'active':
- case 'auto':
- $lang = Factory::getLanguage();
- $this->value = $lang->getTag();
- break;
- default:
- break;
- }
- }
+ // Set the default value active language
+ if ($langParams = ComponentHelper::getParams('com_languages')) {
+ switch ((string) $this->value) {
+ case 'site':
+ case 'frontend':
+ case '0':
+ $this->value = $langParams->get('site', 'en-GB');
+ break;
+ case 'admin':
+ case 'administrator':
+ case 'backend':
+ case '1':
+ $this->value = $langParams->get('administrator', 'en-GB');
+ break;
+ case 'active':
+ case 'auto':
+ $lang = Factory::getLanguage();
+ $this->value = $lang->getTag();
+ break;
+ default:
+ break;
+ }
+ }
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/LastvisitdaterangeField.php b/libraries/src/Form/Field/LastvisitdaterangeField.php
index 950e2e9c03337..a2d8ba4bc6c65 100644
--- a/libraries/src/Form/Field/LastvisitdaterangeField.php
+++ b/libraries/src/Form/Field/LastvisitdaterangeField.php
@@ -1,4 +1,5 @@
type = 'LastvisitDateRange';
+ // Set the type
+ $this->type = 'LastvisitDateRange';
- // Load the required language
- $lang = Factory::getLanguage();
- $lang->load('com_users', JPATH_ADMINISTRATOR);
+ // Load the required language
+ $lang = Factory::getLanguage();
+ $lang->load('com_users', JPATH_ADMINISTRATOR);
- // Set the pre-defined options
- $this->predefinedOptions = array(
- 'today' => 'COM_USERS_OPTION_RANGE_TODAY',
- 'past_week' => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
- 'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
- 'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
- 'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
- 'past_year' => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
- 'post_year' => 'COM_USERS_OPTION_RANGE_POST_YEAR',
- 'never' => 'COM_USERS_OPTION_RANGE_NEVER',
- );
- }
+ // Set the pre-defined options
+ $this->predefinedOptions = array(
+ 'today' => 'COM_USERS_OPTION_RANGE_TODAY',
+ 'past_week' => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
+ 'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
+ 'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
+ 'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
+ 'past_year' => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
+ 'post_year' => 'COM_USERS_OPTION_RANGE_POST_YEAR',
+ 'never' => 'COM_USERS_OPTION_RANGE_NEVER',
+ );
+ }
}
diff --git a/libraries/src/Form/Field/LimitboxField.php b/libraries/src/Form/Field/LimitboxField.php
index 2cdb7886bf9f7..6331c5f77767a 100644
--- a/libraries/src/Form/Field/LimitboxField.php
+++ b/libraries/src/Form/Field/LimitboxField.php
@@ -1,4 +1,5 @@
element->asXML());
-
- if (!isset(static::$options[$hash]))
- {
- static::$options[$hash] = parent::getOptions();
-
- $options = array();
- $limits = $this->defaultLimits;
-
- // Limits manually specified
- if (isset($this->element['limits']))
- {
- $limits = explode(',', $this->element['limits']);
- }
-
- // User wants to add custom limits
- if (isset($this->element['append']))
- {
- $limits = array_unique(array_merge($limits, explode(',', $this->element['append'])));
- }
-
- // User wants to remove some default limits
- if (isset($this->element['remove']))
- {
- $limits = array_diff($limits, explode(',', $this->element['remove']));
- }
-
- // Order the options
- asort($limits);
-
- // Add an option to show all?
- $showAll = isset($this->element['showall']) ? (string) $this->element['showall'] === 'true' : true;
-
- if ($showAll)
- {
- $limits[] = 0;
- }
-
- if (!empty($limits))
- {
- foreach ($limits as $value)
- {
- $options[] = (object) array(
- 'value' => $value,
- 'text' => ($value != 0) ? Text::_('J' . $value) : Text::_('JALL'),
- );
- }
-
- static::$options[$hash] = array_merge(static::$options[$hash], $options);
- }
- }
-
- return static::$options[$hash];
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ public $type = 'Limitbox';
+
+ /**
+ * Cached array of the category items.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected static $options = array();
+
+ /**
+ * Default options
+ *
+ * @var array
+ */
+ protected $defaultLimits = array(5, 10, 15, 20, 25, 30, 50, 100, 200, 500);
+
+ /**
+ * Method to get the options to populate to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.2
+ */
+ protected function getOptions()
+ {
+ // Accepted modifiers
+ $hash = md5($this->element->asXML());
+
+ if (!isset(static::$options[$hash])) {
+ static::$options[$hash] = parent::getOptions();
+
+ $options = array();
+ $limits = $this->defaultLimits;
+
+ // Limits manually specified
+ if (isset($this->element['limits'])) {
+ $limits = explode(',', $this->element['limits']);
+ }
+
+ // User wants to add custom limits
+ if (isset($this->element['append'])) {
+ $limits = array_unique(array_merge($limits, explode(',', $this->element['append'])));
+ }
+
+ // User wants to remove some default limits
+ if (isset($this->element['remove'])) {
+ $limits = array_diff($limits, explode(',', $this->element['remove']));
+ }
+
+ // Order the options
+ asort($limits);
+
+ // Add an option to show all?
+ $showAll = isset($this->element['showall']) ? (string) $this->element['showall'] === 'true' : true;
+
+ if ($showAll) {
+ $limits[] = 0;
+ }
+
+ if (!empty($limits)) {
+ foreach ($limits as $value) {
+ $options[] = (object) array(
+ 'value' => $value,
+ 'text' => ($value != 0) ? Text::_('J' . $value) : Text::_('JALL'),
+ );
+ }
+
+ static::$options[$hash] = array_merge(static::$options[$hash], $options);
+ }
+ }
+
+ return static::$options[$hash];
+ }
}
diff --git a/libraries/src/Form/Field/ListField.php b/libraries/src/Form/Field/ListField.php
index 00aeb511e4fda..c94bebafa50cd 100644
--- a/libraries/src/Form/Field/ListField.php
+++ b/libraries/src/Form/Field/ListField.php
@@ -1,4 +1,5 @@
getLayoutData();
-
- $data['options'] = (array) $this->getOptions();
-
- return $this->getRenderer($this->layout)->render($data);
- }
-
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.7.0
- */
- protected function getOptions()
- {
- $fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
- $options = array();
-
- foreach ($this->element->xpath('option') as $option)
- {
- // Filter requirements
- if ($requires = explode(',', (string) $option['requires']))
- {
- // Requires multilanguage
- if (\in_array('multilanguage', $requires) && !Multilanguage::isEnabled())
- {
- continue;
- }
-
- // Requires associations
- if (\in_array('associations', $requires) && !Associations::isEnabled())
- {
- continue;
- }
-
- // Requires adminlanguage
- if (\in_array('adminlanguage', $requires) && !ModuleHelper::isAdminMultilang())
- {
- continue;
- }
-
- // Requires vote plugin
- if (\in_array('vote', $requires) && !PluginHelper::isEnabled('content', 'vote'))
- {
- continue;
- }
-
- // Requires record hits
- if (\in_array('hits', $requires) && !ComponentHelper::getParams('com_content')->get('record_hits', 1))
- {
- continue;
- }
- }
-
- $value = (string) $option['value'];
- $text = trim((string) $option) != '' ? trim((string) $option) : $value;
-
- $disabled = (string) $option['disabled'];
- $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
- $disabled = $disabled || ($this->readonly && $value != $this->value);
-
- $checked = (string) $option['checked'];
- $checked = ($checked === 'true' || $checked === 'checked' || $checked === '1');
-
- $selected = (string) $option['selected'];
- $selected = ($selected === 'true' || $selected === 'selected' || $selected === '1');
-
- $tmp = array(
- 'value' => $value,
- 'text' => Text::alt($text, $fieldname),
- 'disable' => $disabled,
- 'class' => (string) $option['class'],
- 'selected' => ($checked || $selected),
- 'checked' => ($checked || $selected),
- );
-
- // Set some event handler attributes. But really, should be using unobtrusive js.
- $tmp['onclick'] = (string) $option['onclick'];
- $tmp['onchange'] = (string) $option['onchange'];
-
- if ((string) $option['showon'])
- {
- $encodedConditions = json_encode(
- FormHelper::parseShowOnConditions((string) $option['showon'], $this->formControl, $this->group)
- );
-
- $tmp['optionattr'] = " data-showon='" . $encodedConditions . "'";
- }
-
- // Add the option object to the result set.
- $options[] = (object) $tmp;
- }
-
- if ($this->element['useglobal'])
- {
- $tmp = new \stdClass;
- $tmp->value = '';
- $tmp->text = Text::_('JGLOBAL_USE_GLOBAL');
- $component = Factory::getApplication()->input->getCmd('option');
-
- // Get correct component for menu items
- if ($component === 'com_menus')
- {
- $link = $this->form->getData()->get('link');
- $uri = new Uri($link);
- $component = $uri->getVar('option', 'com_menus');
- }
-
- $params = ComponentHelper::getParams($component);
- $value = $params->get($this->fieldname);
-
- // Try with global configuration
- if (\is_null($value))
- {
- $value = Factory::getApplication()->get($this->fieldname);
- }
-
- // Try with menu configuration
- if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus')
- {
- $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
- }
-
- if (!\is_null($value))
- {
- $value = (string) $value;
-
- foreach ($options as $option)
- {
- if ($option->value === $value)
- {
- $value = $option->text;
-
- break;
- }
- }
-
- $tmp->text = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
- }
-
- array_unshift($options, $tmp);
- }
-
- reset($options);
-
- return $options;
- }
-
- /**
- * Method to add an option to the list field.
- *
- * @param string $text Text/Language variable of the option.
- * @param array $attributes Array of attributes ('name' => 'value' format)
- *
- * @return ListField For chaining.
- *
- * @since 3.7.0
- */
- public function addOption($text, $attributes = array())
- {
- if ($text && $this->element instanceof \SimpleXMLElement)
- {
- $child = $this->element->addChild('option', $text);
-
- foreach ($attributes as $name => $value)
- {
- $child->addAttribute($name, $value);
- }
- }
-
- return $this;
- }
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 3.7.0
- */
- public function __get($name)
- {
- if ($name === 'options')
- {
- return $this->getOptions();
- }
-
- return parent::__get($name);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'List';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.list';
+
+ /**
+ * Method to get the field input markup for a generic list.
+ * Use the multiple attribute to enable multiselect.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.7.0
+ */
+ protected function getInput()
+ {
+ $data = $this->getLayoutData();
+
+ $data['options'] = (array) $this->getOptions();
+
+ return $this->getRenderer($this->layout)->render($data);
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.7.0
+ */
+ protected function getOptions()
+ {
+ $fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
+ $options = array();
+
+ foreach ($this->element->xpath('option') as $option) {
+ // Filter requirements
+ if ($requires = explode(',', (string) $option['requires'])) {
+ // Requires multilanguage
+ if (\in_array('multilanguage', $requires) && !Multilanguage::isEnabled()) {
+ continue;
+ }
+
+ // Requires associations
+ if (\in_array('associations', $requires) && !Associations::isEnabled()) {
+ continue;
+ }
+
+ // Requires adminlanguage
+ if (\in_array('adminlanguage', $requires) && !ModuleHelper::isAdminMultilang()) {
+ continue;
+ }
+
+ // Requires vote plugin
+ if (\in_array('vote', $requires) && !PluginHelper::isEnabled('content', 'vote')) {
+ continue;
+ }
+
+ // Requires record hits
+ if (\in_array('hits', $requires) && !ComponentHelper::getParams('com_content')->get('record_hits', 1)) {
+ continue;
+ }
+ }
+
+ $value = (string) $option['value'];
+ $text = trim((string) $option) != '' ? trim((string) $option) : $value;
+
+ $disabled = (string) $option['disabled'];
+ $disabled = ($disabled === 'true' || $disabled === 'disabled' || $disabled === '1');
+ $disabled = $disabled || ($this->readonly && $value != $this->value);
+
+ $checked = (string) $option['checked'];
+ $checked = ($checked === 'true' || $checked === 'checked' || $checked === '1');
+
+ $selected = (string) $option['selected'];
+ $selected = ($selected === 'true' || $selected === 'selected' || $selected === '1');
+
+ $tmp = array(
+ 'value' => $value,
+ 'text' => Text::alt($text, $fieldname),
+ 'disable' => $disabled,
+ 'class' => (string) $option['class'],
+ 'selected' => ($checked || $selected),
+ 'checked' => ($checked || $selected),
+ );
+
+ // Set some event handler attributes. But really, should be using unobtrusive js.
+ $tmp['onclick'] = (string) $option['onclick'];
+ $tmp['onchange'] = (string) $option['onchange'];
+
+ if ((string) $option['showon']) {
+ $encodedConditions = json_encode(
+ FormHelper::parseShowOnConditions((string) $option['showon'], $this->formControl, $this->group)
+ );
+
+ $tmp['optionattr'] = " data-showon='" . $encodedConditions . "'";
+ }
+
+ // Add the option object to the result set.
+ $options[] = (object) $tmp;
+ }
+
+ if ($this->element['useglobal']) {
+ $tmp = new \stdClass();
+ $tmp->value = '';
+ $tmp->text = Text::_('JGLOBAL_USE_GLOBAL');
+ $component = Factory::getApplication()->input->getCmd('option');
+
+ // Get correct component for menu items
+ if ($component === 'com_menus') {
+ $link = $this->form->getData()->get('link');
+ $uri = new Uri($link);
+ $component = $uri->getVar('option', 'com_menus');
+ }
+
+ $params = ComponentHelper::getParams($component);
+ $value = $params->get($this->fieldname);
+
+ // Try with global configuration
+ if (\is_null($value)) {
+ $value = Factory::getApplication()->get($this->fieldname);
+ }
+
+ // Try with menu configuration
+ if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus') {
+ $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
+ }
+
+ if (!\is_null($value)) {
+ $value = (string) $value;
+
+ foreach ($options as $option) {
+ if ($option->value === $value) {
+ $value = $option->text;
+
+ break;
+ }
+ }
+
+ $tmp->text = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
+ }
+
+ array_unshift($options, $tmp);
+ }
+
+ reset($options);
+
+ return $options;
+ }
+
+ /**
+ * Method to add an option to the list field.
+ *
+ * @param string $text Text/Language variable of the option.
+ * @param array $attributes Array of attributes ('name' => 'value' format)
+ *
+ * @return ListField For chaining.
+ *
+ * @since 3.7.0
+ */
+ public function addOption($text, $attributes = array())
+ {
+ if ($text && $this->element instanceof \SimpleXMLElement) {
+ $child = $this->element->addChild('option', $text);
+
+ foreach ($attributes as $name => $value) {
+ $child->addAttribute($name, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.7.0
+ */
+ public function __get($name)
+ {
+ if ($name === 'options') {
+ return $this->getOptions();
+ }
+
+ return parent::__get($name);
+ }
}
diff --git a/libraries/src/Form/Field/MediaField.php b/libraries/src/Form/Field/MediaField.php
index ea8cd7d00335d..7fb58f37540e6 100644
--- a/libraries/src/Form/Field/MediaField.php
+++ b/libraries/src/Form/Field/MediaField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'authorField':
- case 'asset':
- case 'link':
- case 'width':
- case 'height':
- case 'preview':
- case 'directory':
- case 'types':
- $this->$name = (string) $value;
- break;
-
- case 'previewWidth':
- case 'previewHeight':
- $this->$name = (int) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- $assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
-
- $this->authorField = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
- $this->asset = $this->form->getValue($assetField) ?: (string) $this->element['asset_id'];
- $this->link = (string) $this->element['link'];
- $this->width = isset($this->element['width']) ? (int) $this->element['width'] : 800;
- $this->height = isset($this->element['height']) ? (int) $this->element['height'] : 500;
- $this->preview = (string) $this->element['preview'];
- $this->directory = (string) $this->element['directory'];
- $this->previewWidth = isset($this->element['preview_width']) ? (int) $this->element['preview_width'] : 200;
- $this->previewHeight = isset($this->element['preview_height']) ? (int) $this->element['preview_height'] : 200;
- $this->types = isset($this->element['types']) ? (string) $this->element['types'] : 'images';
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup for a media selector.
- * Use attributes to identify specific created_by and asset_id fields
- *
- * @return string The field input markup.
- *
- * @since 1.6
- */
- protected function getInput()
- {
- if (empty($this->layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Get the data that is going to be passed to the layout
- *
- * @return array
- */
- public function getLayoutData()
- {
- // Get the basic field data
- $data = parent::getLayoutData();
-
- $asset = $this->asset;
-
- if ($asset === '')
- {
- $asset = Factory::getApplication()->input->get('option');
- }
-
- // Value in new format such as images/headers/blue-flower.jpg#joomlaImage://local-images/headers/blue-flower.jpg?width=700&height=180
- if ($this->value && strpos($this->value, '#') !== false)
- {
- $uri = new Uri(explode('#', $this->value)[1]);
- $adapter = $uri->getHost();
- $path = $uri->getPath();
-
- // Remove filename from stored path to get the path to the folder which file is stored
- $pos = strrpos($path, '/');
-
- if ($pos !== false)
- {
- $path = substr($path, 0, $pos);
- }
-
- if ($path === '')
- {
- $path = '/';
- }
-
- $this->folder = $adapter . ':' . $path;
- }
- elseif ($this->value && is_file(JPATH_ROOT . '/' . $this->value))
- {
- /**
- * Local image, for example images/sampledata/cassiopeia/nasa2-640.jpg . We need to validate and make sure
- * the top level folder is one of the directory configured in filesystem local plugin to avoid error message
- * displayed in manage when users click on Select button to select a new image
- */
- $paths = explode('/', $this->value);
-
- // Remove filename from $paths array
- array_pop($paths);
-
- if (MediaHelper::isValidLocalDirectory($paths[0]))
- {
- $adapterName = array_shift($paths);
- $this->folder = 'local-' . $adapterName . ':/' . implode('/', $paths);
- }
- }
- elseif ($this->directory && is_dir(JPATH_ROOT . '/' . ComponentHelper::getParams('com_media')->get('image_path', 'images') . '/' . $this->directory))
- {
- /**
- * This is the case where a folder is configured in directory attribute of the form field. The directory needs
- * to be a relative folder of the folder configured in Path to Images Folder config option of Media component.
- * Same with a already stored local image above, we need to validate and make sure top level folder is one of the directory
- * configured in filesystem local plugin
- */
- $path = ComponentHelper::getParams('com_media')->get('image_path', 'images') . '/' . $this->directory;
- $paths = explode('/', $path);
-
- if (MediaHelper::isValidLocalDirectory($paths[0]))
- {
- $adapterName = array_shift($paths);
- $this->folder = 'local-' . $adapterName . ':/' . implode('/', $paths);
- }
- }
- elseif ($this->directory && strpos($this->directory, ':'))
- {
- /**
- * Directory contains adapter information and path, for example via programming or directly defined in xml
- * via directory attribute
- */
- $this->folder = $this->directory;
- }
- else
- {
- $this->folder = '';
- }
-
- $mediaTypes = array_map('trim', explode(',', $this->types));
- $types = [];
- $imagesExt = array_map(
- 'trim',
- explode(
- ',',
- ComponentHelper::getParams('com_media')->get(
- 'image_extensions',
- 'bmp,gif,jpg,jpeg,png,webp'
- )
- )
- );
- $audiosExt = array_map(
- 'trim',
- explode(
- ',',
- ComponentHelper::getParams('com_media')->get(
- 'audio_extensions',
- 'mp3,m4a,mp4a,ogg'
- )
- )
- );
- $videosExt = array_map(
- 'trim',
- explode(
- ',',
- ComponentHelper::getParams('com_media')->get(
- 'video_extensions',
- 'mp4,mp4v,mpeg,mov,webm'
- )
- )
- );
- $documentsExt = array_map(
- 'trim',
- explode(
- ',',
- ComponentHelper::getParams('com_media')->get(
- 'doc_extensions',
- 'doc,odg,odp,ods,odt,pdf,ppt,txt,xcf,xls,csv'
- )
- )
- );
-
- $imagesAllowedExt = [];
- $audiosAllowedExt = [];
- $videosAllowedExt = [];
- $documentsAllowedExt = [];
-
- // Cleanup the media types
- array_map(
- function ($mediaType) use (&$types, &$imagesAllowedExt, &$audiosAllowedExt, &$videosAllowedExt, &$documentsAllowedExt, $imagesExt, $audiosExt, $videosExt, $documentsExt) {
- switch ($mediaType)
- {
- case 'images':
- $types[] = '0';
- $imagesAllowedExt = $imagesExt;
- break;
- case 'audios':
- $types[] = '1';
- $audiosAllowedExt = $audiosExt;
- break;
- case 'videos':
- $types[] = '2';
- $videosAllowedExt = $videosExt;
- break;
- case 'documents':
- $types[] = '3';
- $documentsAllowedExt = $documentsExt;
- break;
- default:
- break;
- }
- },
- $mediaTypes
- );
-
- sort($types);
-
- $extraData = array(
- 'asset' => $asset,
- 'authorField' => $this->authorField,
- 'authorId' => $this->form->getValue($this->authorField),
- 'folder' => $this->folder,
- 'link' => $this->link,
- 'preview' => $this->preview,
- 'previewHeight' => $this->previewHeight,
- 'previewWidth' => $this->previewWidth,
- 'mediaTypes' => implode(',', $types),
- 'imagesExt' => $imagesExt,
- 'audiosExt' => $audiosExt,
- 'videosExt' => $videosExt,
- 'documentsExt' => $documentsExt,
- 'imagesAllowedExt' => $imagesAllowedExt,
- 'audiosAllowedExt' => $audiosAllowedExt,
- 'videosAllowedExt' => $videosAllowedExt,
- 'documentsAllowedExt' => $documentsAllowedExt,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'Media';
+
+ /**
+ * The authorField.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $authorField;
+
+ /**
+ * The asset.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $asset;
+
+ /**
+ * The link.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $link;
+
+ /**
+ * Modal width.
+ *
+ * @var integer
+ * @since 3.4.5
+ */
+ protected $width;
+
+ /**
+ * Modal height.
+ *
+ * @var integer
+ * @since 3.4.5
+ */
+ protected $height;
+
+ /**
+ * The preview.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $preview;
+
+ /**
+ * The directory.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $directory;
+
+ /**
+ * The previewWidth.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $previewWidth;
+
+ /**
+ * The previewHeight.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $previewHeight;
+
+ /**
+ * Comma separated types of files for Media Manager
+ * Possible values: images,audios,videos,documents
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $types;
+
+ /**
+ * Layout to render
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $layout = 'joomla.form.field.media';
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'authorField':
+ case 'asset':
+ case 'link':
+ case 'width':
+ case 'height':
+ case 'preview':
+ case 'directory':
+ case 'previewWidth':
+ case 'previewHeight':
+ case 'types':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'authorField':
+ case 'asset':
+ case 'link':
+ case 'width':
+ case 'height':
+ case 'preview':
+ case 'directory':
+ case 'types':
+ $this->$name = (string) $value;
+ break;
+
+ case 'previewWidth':
+ case 'previewHeight':
+ $this->$name = (int) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ $assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
+
+ $this->authorField = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
+ $this->asset = $this->form->getValue($assetField) ?: (string) $this->element['asset_id'];
+ $this->link = (string) $this->element['link'];
+ $this->width = isset($this->element['width']) ? (int) $this->element['width'] : 800;
+ $this->height = isset($this->element['height']) ? (int) $this->element['height'] : 500;
+ $this->preview = (string) $this->element['preview'];
+ $this->directory = (string) $this->element['directory'];
+ $this->previewWidth = isset($this->element['preview_width']) ? (int) $this->element['preview_width'] : 200;
+ $this->previewHeight = isset($this->element['preview_height']) ? (int) $this->element['preview_height'] : 200;
+ $this->types = isset($this->element['types']) ? (string) $this->element['types'] : 'images';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup for a media selector.
+ * Use attributes to identify specific created_by and asset_id fields
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Get the data that is going to be passed to the layout
+ *
+ * @return array
+ */
+ public function getLayoutData()
+ {
+ // Get the basic field data
+ $data = parent::getLayoutData();
+
+ $asset = $this->asset;
+
+ if ($asset === '') {
+ $asset = Factory::getApplication()->input->get('option');
+ }
+
+ // Value in new format such as images/headers/blue-flower.jpg#joomlaImage://local-images/headers/blue-flower.jpg?width=700&height=180
+ if ($this->value && strpos($this->value, '#') !== false) {
+ $uri = new Uri(explode('#', $this->value)[1]);
+ $adapter = $uri->getHost();
+ $path = $uri->getPath();
+
+ // Remove filename from stored path to get the path to the folder which file is stored
+ $pos = strrpos($path, '/');
+
+ if ($pos !== false) {
+ $path = substr($path, 0, $pos);
+ }
+
+ if ($path === '') {
+ $path = '/';
+ }
+
+ $this->folder = $adapter . ':' . $path;
+ } elseif ($this->value && is_file(JPATH_ROOT . '/' . $this->value)) {
+ /**
+ * Local image, for example images/sampledata/cassiopeia/nasa2-640.jpg . We need to validate and make sure
+ * the top level folder is one of the directory configured in filesystem local plugin to avoid error message
+ * displayed in manage when users click on Select button to select a new image
+ */
+ $paths = explode('/', $this->value);
+
+ // Remove filename from $paths array
+ array_pop($paths);
+
+ if (MediaHelper::isValidLocalDirectory($paths[0])) {
+ $adapterName = array_shift($paths);
+ $this->folder = 'local-' . $adapterName . ':/' . implode('/', $paths);
+ }
+ } elseif ($this->directory && is_dir(JPATH_ROOT . '/' . ComponentHelper::getParams('com_media')->get('image_path', 'images') . '/' . $this->directory)) {
+ /**
+ * This is the case where a folder is configured in directory attribute of the form field. The directory needs
+ * to be a relative folder of the folder configured in Path to Images Folder config option of Media component.
+ * Same with a already stored local image above, we need to validate and make sure top level folder is one of the directory
+ * configured in filesystem local plugin
+ */
+ $path = ComponentHelper::getParams('com_media')->get('image_path', 'images') . '/' . $this->directory;
+ $paths = explode('/', $path);
+
+ if (MediaHelper::isValidLocalDirectory($paths[0])) {
+ $adapterName = array_shift($paths);
+ $this->folder = 'local-' . $adapterName . ':/' . implode('/', $paths);
+ }
+ } elseif ($this->directory && strpos($this->directory, ':')) {
+ /**
+ * Directory contains adapter information and path, for example via programming or directly defined in xml
+ * via directory attribute
+ */
+ $this->folder = $this->directory;
+ } else {
+ $this->folder = '';
+ }
+
+ $mediaTypes = array_map('trim', explode(',', $this->types));
+ $types = [];
+ $imagesExt = array_map(
+ 'trim',
+ explode(
+ ',',
+ ComponentHelper::getParams('com_media')->get(
+ 'image_extensions',
+ 'bmp,gif,jpg,jpeg,png,webp'
+ )
+ )
+ );
+ $audiosExt = array_map(
+ 'trim',
+ explode(
+ ',',
+ ComponentHelper::getParams('com_media')->get(
+ 'audio_extensions',
+ 'mp3,m4a,mp4a,ogg'
+ )
+ )
+ );
+ $videosExt = array_map(
+ 'trim',
+ explode(
+ ',',
+ ComponentHelper::getParams('com_media')->get(
+ 'video_extensions',
+ 'mp4,mp4v,mpeg,mov,webm'
+ )
+ )
+ );
+ $documentsExt = array_map(
+ 'trim',
+ explode(
+ ',',
+ ComponentHelper::getParams('com_media')->get(
+ 'doc_extensions',
+ 'doc,odg,odp,ods,odt,pdf,ppt,txt,xcf,xls,csv'
+ )
+ )
+ );
+
+ $imagesAllowedExt = [];
+ $audiosAllowedExt = [];
+ $videosAllowedExt = [];
+ $documentsAllowedExt = [];
+
+ // Cleanup the media types
+ array_map(
+ function ($mediaType) use (&$types, &$imagesAllowedExt, &$audiosAllowedExt, &$videosAllowedExt, &$documentsAllowedExt, $imagesExt, $audiosExt, $videosExt, $documentsExt) {
+ switch ($mediaType) {
+ case 'images':
+ $types[] = '0';
+ $imagesAllowedExt = $imagesExt;
+ break;
+ case 'audios':
+ $types[] = '1';
+ $audiosAllowedExt = $audiosExt;
+ break;
+ case 'videos':
+ $types[] = '2';
+ $videosAllowedExt = $videosExt;
+ break;
+ case 'documents':
+ $types[] = '3';
+ $documentsAllowedExt = $documentsExt;
+ break;
+ default:
+ break;
+ }
+ },
+ $mediaTypes
+ );
+
+ sort($types);
+
+ $extraData = array(
+ 'asset' => $asset,
+ 'authorField' => $this->authorField,
+ 'authorId' => $this->form->getValue($this->authorField),
+ 'folder' => $this->folder,
+ 'link' => $this->link,
+ 'preview' => $this->preview,
+ 'previewHeight' => $this->previewHeight,
+ 'previewWidth' => $this->previewWidth,
+ 'mediaTypes' => implode(',', $types),
+ 'imagesExt' => $imagesExt,
+ 'audiosExt' => $audiosExt,
+ 'videosExt' => $videosExt,
+ 'documentsExt' => $documentsExt,
+ 'imagesAllowedExt' => $imagesAllowedExt,
+ 'audiosAllowedExt' => $audiosAllowedExt,
+ 'videosAllowedExt' => $videosAllowedExt,
+ 'documentsAllowedExt' => $documentsAllowedExt,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/MenuField.php b/libraries/src/Form/Field/MenuField.php
index a61e55dbaabb5..43020ae7e8630 100644
--- a/libraries/src/Form/Field/MenuField.php
+++ b/libraries/src/Form/Field/MenuField.php
@@ -1,4 +1,5 @@
element['clientid'];
- $accessType = (string) $this->element['accesstype'];
- $showAll = (string) $this->element['showAll'] === 'true';
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('id'),
- $db->quoteName('menutype', 'value'),
- $db->quoteName('title', 'text'),
- $db->quoteName('client_id'),
- ]
- )
- ->from($db->quoteName('#__menu_types'))
- ->order(
- [
- $db->quoteName('client_id'),
- $db->quoteName('title'),
- ]
- );
-
- if (\strlen($clientId))
- {
- $client = (int) $clientId;
- $query->where($db->quoteName('client_id') . ' = :client')
- ->bind(':client', $client, ParameterType::INTEGER);
- }
-
- $menus = $db->setQuery($query)->loadObjectList();
-
- if ($accessType)
- {
- $user = Factory::getUser();
-
- foreach ($menus as $key => $menu)
- {
- switch ($accessType)
- {
- case 'create':
- case 'manage':
- if (!$user->authorise('core.' . $accessType, 'com_menus.menu.' . (int) $menu->id))
- {
- unset($menus[$key]);
- }
- break;
-
- // Editing a menu item is a bit tricky, we have to check the current menutype for core.edit and all others for core.create
- case 'edit':
- $check = $this->value == $menu->value ? 'edit' : 'create';
-
- if (!$user->authorise('core.' . $check, 'com_menus.menu.' . (int) $menu->id))
- {
- unset($menus[$key]);
- }
- break;
- }
- }
- }
-
- $opts = array();
-
- // Protected menutypes can be shown if requested
- if ($clientId == 1 && $showAll)
- {
- $opts[] = (object) array(
- 'value' => 'main',
- 'text' => Text::_('COM_MENUS_MENU_TYPE_PROTECTED_MAIN_LABEL'),
- 'client_id' => 1,
- );
- }
-
- $options = array_merge($opts, $menus);
- $groups = array();
-
- if (\strlen($clientId))
- {
- $groups[0] = $options;
- }
- else
- {
- foreach ($options as $option)
- {
- // If client id is not specified we group the items.
- $label = ($option->client_id == 1 ? Text::_('JADMINISTRATOR') : Text::_('JSITE'));
-
- $groups[$label][] = $option;
- }
- }
-
- // Merge any additional options in the XML definition.
- return array_merge(parent::getGroups(), $groups);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'Menu';
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ protected function getGroups()
+ {
+ $clientId = (string) $this->element['clientid'];
+ $accessType = (string) $this->element['accesstype'];
+ $showAll = (string) $this->element['showAll'] === 'true';
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('id'),
+ $db->quoteName('menutype', 'value'),
+ $db->quoteName('title', 'text'),
+ $db->quoteName('client_id'),
+ ]
+ )
+ ->from($db->quoteName('#__menu_types'))
+ ->order(
+ [
+ $db->quoteName('client_id'),
+ $db->quoteName('title'),
+ ]
+ );
+
+ if (\strlen($clientId)) {
+ $client = (int) $clientId;
+ $query->where($db->quoteName('client_id') . ' = :client')
+ ->bind(':client', $client, ParameterType::INTEGER);
+ }
+
+ $menus = $db->setQuery($query)->loadObjectList();
+
+ if ($accessType) {
+ $user = Factory::getUser();
+
+ foreach ($menus as $key => $menu) {
+ switch ($accessType) {
+ case 'create':
+ case 'manage':
+ if (!$user->authorise('core.' . $accessType, 'com_menus.menu.' . (int) $menu->id)) {
+ unset($menus[$key]);
+ }
+ break;
+
+ // Editing a menu item is a bit tricky, we have to check the current menutype for core.edit and all others for core.create
+ case 'edit':
+ $check = $this->value == $menu->value ? 'edit' : 'create';
+
+ if (!$user->authorise('core.' . $check, 'com_menus.menu.' . (int) $menu->id)) {
+ unset($menus[$key]);
+ }
+ break;
+ }
+ }
+ }
+
+ $opts = array();
+
+ // Protected menutypes can be shown if requested
+ if ($clientId == 1 && $showAll) {
+ $opts[] = (object) array(
+ 'value' => 'main',
+ 'text' => Text::_('COM_MENUS_MENU_TYPE_PROTECTED_MAIN_LABEL'),
+ 'client_id' => 1,
+ );
+ }
+
+ $options = array_merge($opts, $menus);
+ $groups = array();
+
+ if (\strlen($clientId)) {
+ $groups[0] = $options;
+ } else {
+ foreach ($options as $option) {
+ // If client id is not specified we group the items.
+ $label = ($option->client_id == 1 ? Text::_('JADMINISTRATOR') : Text::_('JSITE'));
+
+ $groups[$label][] = $option;
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getGroups(), $groups);
+ }
}
diff --git a/libraries/src/Form/Field/MenuitemField.php b/libraries/src/Form/Field/MenuitemField.php
index f644fc1b439c4..95bfcff5d19ee 100644
--- a/libraries/src/Form/Field/MenuitemField.php
+++ b/libraries/src/Form/Field/MenuitemField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'menuType':
- $this->menuType = (string) $value;
- break;
-
- case 'clientId':
- $this->clientId = (int) $value;
- break;
-
- case 'language':
- case 'published':
- case 'disable':
- $value = (string) $value;
- $this->$name = $value ? explode(',', $value) : array();
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- $this->menuType = (string) $this->element['menu_type'];
- $this->clientId = (int) $this->element['client_id'];
- $this->published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array();
- $this->disable = $this->element['disable'] ? explode(',', (string) $this->element['disable']) : array();
- $this->language = $this->element['language'] ? explode(',', (string) $this->element['language']) : array();
- }
-
- return $result;
- }
-
- /**
- * Method to get the field option groups.
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 1.6
- */
- protected function getGroups()
- {
- $groups = array();
-
- $menuType = $this->menuType;
-
- // Get the menu items.
- $items = MenusHelper::getMenuLinks($menuType, 0, 0, $this->published, $this->language, $this->clientId);
-
- // Build group for a specific menu type.
- if ($menuType)
- {
- // If the menutype is empty, group the items by menutype.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__menu_types'))
- ->where($db->quoteName('menutype') . ' = :menuType')
- ->bind(':menuType', $menuType);
- $db->setQuery($query);
-
- try
- {
- $menuTitle = $db->loadResult();
- }
- catch (\RuntimeException $e)
- {
- $menuTitle = $menuType;
- }
-
- // Initialize the group.
- $groups[$menuTitle] = array();
-
- // Build the options array.
- foreach ($items as $link)
- {
- $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
-
- // Displays language code if not set to All
- if ($link->language !== '*')
- {
- $lang = ' (' . $link->language . ')';
- }
- else
- {
- $lang = '';
- }
-
- $groups[$menuTitle][] = HTMLHelper::_('select.option',
- $link->value, $levelPrefix . $link->text . $lang,
- 'value',
- 'text',
- \in_array($link->type, $this->disable)
- );
- }
- }
- // Build groups for all menu types.
- else
- {
- // Build the groups arrays.
- foreach ($items as $menu)
- {
- // Initialize the group.
- $groups[$menu->title] = array();
-
- // Build the options array.
- foreach ($menu->links as $link)
- {
- $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
-
- // Displays language code if not set to All
- if ($link->language !== '*')
- {
- $lang = ' (' . $link->language . ')';
- }
- else
- {
- $lang = '';
- }
-
- $groups[$menu->title][] = HTMLHelper::_('select.option',
- $link->value, $levelPrefix . $link->text . $lang,
- 'value',
- 'text',
- \in_array($link->type, $this->disable)
- );
- }
- }
- }
-
- // Merge any additional groups in the XML definition.
- $groups = array_merge(parent::getGroups(), $groups);
-
- return $groups;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'MenuItem';
+
+ /**
+ * The menu type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $menuType;
+
+ /**
+ * The client id.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $clientId;
+
+ /**
+ * The language.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $language;
+
+ /**
+ * The published status.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $published;
+
+ /**
+ * The disabled status.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $disable;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'menuType':
+ case 'clientId':
+ case 'language':
+ case 'published':
+ case 'disable':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'menuType':
+ $this->menuType = (string) $value;
+ break;
+
+ case 'clientId':
+ $this->clientId = (int) $value;
+ break;
+
+ case 'language':
+ case 'published':
+ case 'disable':
+ $value = (string) $value;
+ $this->$name = $value ? explode(',', $value) : array();
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ $this->menuType = (string) $this->element['menu_type'];
+ $this->clientId = (int) $this->element['client_id'];
+ $this->published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array();
+ $this->disable = $this->element['disable'] ? explode(',', (string) $this->element['disable']) : array();
+ $this->language = $this->element['language'] ? explode(',', (string) $this->element['language']) : array();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 1.6
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+
+ $menuType = $this->menuType;
+
+ // Get the menu items.
+ $items = MenusHelper::getMenuLinks($menuType, 0, 0, $this->published, $this->language, $this->clientId);
+
+ // Build group for a specific menu type.
+ if ($menuType) {
+ // If the menutype is empty, group the items by menutype.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__menu_types'))
+ ->where($db->quoteName('menutype') . ' = :menuType')
+ ->bind(':menuType', $menuType);
+ $db->setQuery($query);
+
+ try {
+ $menuTitle = $db->loadResult();
+ } catch (\RuntimeException $e) {
+ $menuTitle = $menuType;
+ }
+
+ // Initialize the group.
+ $groups[$menuTitle] = array();
+
+ // Build the options array.
+ foreach ($items as $link) {
+ $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
+
+ // Displays language code if not set to All
+ if ($link->language !== '*') {
+ $lang = ' (' . $link->language . ')';
+ } else {
+ $lang = '';
+ }
+
+ $groups[$menuTitle][] = HTMLHelper::_(
+ 'select.option',
+ $link->value,
+ $levelPrefix . $link->text . $lang,
+ 'value',
+ 'text',
+ \in_array($link->type, $this->disable)
+ );
+ }
+ } else {
+ // Build groups for all menu types.
+ // Build the groups arrays.
+ foreach ($items as $menu) {
+ // Initialize the group.
+ $groups[$menu->title] = array();
+
+ // Build the options array.
+ foreach ($menu->links as $link) {
+ $levelPrefix = str_repeat('- ', max(0, $link->level - 1));
+
+ // Displays language code if not set to All
+ if ($link->language !== '*') {
+ $lang = ' (' . $link->language . ')';
+ } else {
+ $lang = '';
+ }
+
+ $groups[$menu->title][] = HTMLHelper::_(
+ 'select.option',
+ $link->value,
+ $levelPrefix . $link->text . $lang,
+ 'value',
+ 'text',
+ \in_array($link->type, $this->disable)
+ );
+ }
+ }
+ }
+
+ // Merge any additional groups in the XML definition.
+ $groups = array_merge(parent::getGroups(), $groups);
+
+ return $groups;
+ }
}
diff --git a/libraries/src/Form/Field/MeterField.php b/libraries/src/Form/Field/MeterField.php
index 089f22c8818b8..5d955b818a700 100644
--- a/libraries/src/Form/Field/MeterField.php
+++ b/libraries/src/Form/Field/MeterField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'width':
- case 'color':
- $this->$name = (string) $value;
- break;
-
- case 'active':
- $value = (string) $value;
- $this->active = ($value === 'true' || $value === $name || $value === '1');
- break;
-
- case 'animated':
- $value = (string) $value;
- $this->animated = !($value === 'false' || $value === 'off' || $value === '0');
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->width = isset($this->element['width']) ? (string) $this->element['width'] : '';
- $this->color = isset($this->element['color']) ? (string) $this->element['color'] : '';
-
- $active = (string) $this->element['active'];
- $this->active = ($active === 'true' || $active === 'on' || $active === '1');
-
- $animated = (string) $this->element['animated'];
- $this->animated = !($animated === 'false' || $animated === 'off' || $animated === '0');
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.2
- */
- protected function getInput()
- {
- // Trim the trailing line in the layout file
- return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // Initialize some field attributes.
- $extraData = array(
- 'width' => $this->width,
- 'color' => $this->color,
- 'animated' => $this->animated,
- 'active' => $this->active,
- 'max' => $this->max,
- 'min' => $this->min,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $type = 'Meter';
+
+ /**
+ * Whether the field is active or not.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $active = false;
+
+ /**
+ * Whether the field is animated or not.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $animated = true;
+
+ /**
+ * The max value of the progress bar
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $max = 100;
+
+ /**
+ * The striped class for the progress bar
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $striped;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7
+ */
+ protected $layout = 'joomla.form.field.meter';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'active':
+ case 'width':
+ case 'animated':
+ case 'color':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'width':
+ case 'color':
+ $this->$name = (string) $value;
+ break;
+
+ case 'active':
+ $value = (string) $value;
+ $this->active = ($value === 'true' || $value === $name || $value === '1');
+ break;
+
+ case 'animated':
+ $value = (string) $value;
+ $this->animated = !($value === 'false' || $value === 'off' || $value === '0');
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->width = isset($this->element['width']) ? (string) $this->element['width'] : '';
+ $this->color = isset($this->element['color']) ? (string) $this->element['color'] : '';
+
+ $active = (string) $this->element['active'];
+ $this->active = ($active === 'true' || $active === 'on' || $active === '1');
+
+ $animated = (string) $this->element['animated'];
+ $this->animated = !($animated === 'false' || $animated === 'off' || $animated === '0');
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // Initialize some field attributes.
+ $extraData = array(
+ 'width' => $this->width,
+ 'color' => $this->color,
+ 'animated' => $this->animated,
+ 'active' => $this->active,
+ 'max' => $this->max,
+ 'min' => $this->min,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/ModulelayoutField.php b/libraries/src/Form/Field/ModulelayoutField.php
index 9eb1197fee581..e4331dc7f565a 100644
--- a/libraries/src/Form/Field/ModulelayoutField.php
+++ b/libraries/src/Form/Field/ModulelayoutField.php
@@ -1,4 +1,5 @@
element['client_id'];
-
- if ($clientId === null && $this->form instanceof Form)
- {
- $clientId = $this->form->getValue('client_id');
- }
-
- $clientId = (int) $clientId;
-
- $client = ApplicationHelper::getClientInfo($clientId);
-
- // Get the module.
- $module = (string) $this->element['module'];
-
- if (empty($module) && ($this->form instanceof Form))
- {
- $module = $this->form->getValue('module');
- }
-
- $module = preg_replace('#\W#', '', $module);
-
- // Get the template.
- $template = (string) $this->element['template'];
- $template = preg_replace('#\W#', '', $template);
-
- // Get the style.
- $template_style_id = 0;
-
- if ($this->form instanceof Form)
- {
- $template_style_id = $this->form->getValue('template_style_id', null, 0);
- $template_style_id = (int) preg_replace('#\W#', '', $template_style_id);
- }
-
- // If an extension and view are present build the options.
- if ($module && $client)
- {
- // Load language file
- $lang = Factory::getLanguage();
- $lang->load($module . '.sys', $client->path)
- || $lang->load($module . '.sys', $client->path . '/modules/' . $module);
-
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select(
- [
- $db->quoteName('element'),
- $db->quoteName('name'),
- ]
- )
- ->from($db->quoteName('#__extensions', 'e'))
- ->where(
- [
- $db->quoteName('e.client_id') . ' = :clientId',
- $db->quoteName('e.type') . ' = ' . $db->quote('template'),
- $db->quoteName('e.enabled') . ' = 1',
- ]
- )
- ->bind(':clientId', $clientId, ParameterType::INTEGER);
-
- if ($template)
- {
- $query->where($db->quoteName('e.element') . ' = :template')
- ->bind(':template', $template);
- }
-
- if ($template_style_id)
- {
- $query->join('LEFT', $db->quoteName('#__template_styles', 's'), $db->quoteName('s.template') . ' = ' . $db->quoteName('e.element'))
- ->where($db->quoteName('s.id') . ' = :style')
- ->bind(':style', $template_style_id, ParameterType::INTEGER);
- }
-
- // Set the query and load the templates.
- $db->setQuery($query);
- $templates = $db->loadObjectList('element');
-
- // Build the search paths for module layouts.
- $module_path = Path::clean($client->path . '/modules/' . $module . '/tmpl');
-
- // Prepare array of component layouts
- $module_layouts = array();
-
- // Prepare the grouped list
- $groups = array();
-
- // Add the layout options from the module path.
- if (is_dir($module_path) && ($module_layouts = Folder::files($module_path, '^[^_]*\.php$')))
- {
- // Create the group for the module
- $groups['_'] = array();
- $groups['_']['id'] = $this->id . '__';
- $groups['_']['text'] = Text::sprintf('JOPTION_FROM_MODULE');
- $groups['_']['items'] = array();
-
- foreach ($module_layouts as $file)
- {
- // Add an option to the module group
- $value = basename($file, '.php');
- $text = $lang->hasKey($key = strtoupper($module . '_LAYOUT_' . $value)) ? Text::_($key) : $value;
- $groups['_']['items'][] = HTMLHelper::_('select.option', '_:' . $value, $text);
- }
- }
-
- // Loop on all templates
- if ($templates)
- {
- foreach ($templates as $template)
- {
- // Load language file
- $lang->load('tpl_' . $template->element . '.sys', $client->path)
- || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
-
- $template_path = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $module);
-
- // Add the layout options from the template path.
- if (is_dir($template_path) && ($files = Folder::files($template_path, '^[^_]*\.php$')))
- {
- foreach ($files as $i => $file)
- {
- // Remove layout that already exist in component ones
- if (\in_array($file, $module_layouts))
- {
- unset($files[$i]);
- }
- }
-
- if (\count($files))
- {
- // Create the group for the template
- $groups[$template->element] = array();
- $groups[$template->element]['id'] = $this->id . '_' . $template->element;
- $groups[$template->element]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
- $groups[$template->element]['items'] = array();
-
- foreach ($files as $file)
- {
- // Add an option to the template group
- $value = basename($file, '.php');
- $text = $lang->hasKey($key = strtoupper('TPL_' . $template->element . '_' . $module . '_LAYOUT_' . $value))
- ? Text::_($key) : $value;
- $groups[$template->element]['items'][] = HTMLHelper::_('select.option', $template->element . ':' . $value, $text);
- }
- }
- }
- }
- }
-
- // Compute attributes for the grouped list
- $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
- $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
-
- // Prepare HTML code
- $html = array();
-
- // Compute the current selected values
- $selected = array($this->value);
-
- // Add a grouped list
- $html[] = HTMLHelper::_(
- 'select.groupedlist', $groups, $this->name,
- array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
- );
-
- return implode($html);
- }
- else
- {
- return '';
- }
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'ModuleLayout';
+
+ /**
+ * Method to get the field input for module layouts.
+ *
+ * @return string The field input.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ // Get the client id.
+ $clientId = $this->element['client_id'];
+
+ if ($clientId === null && $this->form instanceof Form) {
+ $clientId = $this->form->getValue('client_id');
+ }
+
+ $clientId = (int) $clientId;
+
+ $client = ApplicationHelper::getClientInfo($clientId);
+
+ // Get the module.
+ $module = (string) $this->element['module'];
+
+ if (empty($module) && ($this->form instanceof Form)) {
+ $module = $this->form->getValue('module');
+ }
+
+ $module = preg_replace('#\W#', '', $module);
+
+ // Get the template.
+ $template = (string) $this->element['template'];
+ $template = preg_replace('#\W#', '', $template);
+
+ // Get the style.
+ $template_style_id = 0;
+
+ if ($this->form instanceof Form) {
+ $template_style_id = $this->form->getValue('template_style_id', null, 0);
+ $template_style_id = (int) preg_replace('#\W#', '', $template_style_id);
+ }
+
+ // If an extension and view are present build the options.
+ if ($module && $client) {
+ // Load language file
+ $lang = Factory::getLanguage();
+ $lang->load($module . '.sys', $client->path)
+ || $lang->load($module . '.sys', $client->path . '/modules/' . $module);
+
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select(
+ [
+ $db->quoteName('element'),
+ $db->quoteName('name'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions', 'e'))
+ ->where(
+ [
+ $db->quoteName('e.client_id') . ' = :clientId',
+ $db->quoteName('e.type') . ' = ' . $db->quote('template'),
+ $db->quoteName('e.enabled') . ' = 1',
+ ]
+ )
+ ->bind(':clientId', $clientId, ParameterType::INTEGER);
+
+ if ($template) {
+ $query->where($db->quoteName('e.element') . ' = :template')
+ ->bind(':template', $template);
+ }
+
+ if ($template_style_id) {
+ $query->join('LEFT', $db->quoteName('#__template_styles', 's'), $db->quoteName('s.template') . ' = ' . $db->quoteName('e.element'))
+ ->where($db->quoteName('s.id') . ' = :style')
+ ->bind(':style', $template_style_id, ParameterType::INTEGER);
+ }
+
+ // Set the query and load the templates.
+ $db->setQuery($query);
+ $templates = $db->loadObjectList('element');
+
+ // Build the search paths for module layouts.
+ $module_path = Path::clean($client->path . '/modules/' . $module . '/tmpl');
+
+ // Prepare array of component layouts
+ $module_layouts = array();
+
+ // Prepare the grouped list
+ $groups = array();
+
+ // Add the layout options from the module path.
+ if (is_dir($module_path) && ($module_layouts = Folder::files($module_path, '^[^_]*\.php$'))) {
+ // Create the group for the module
+ $groups['_'] = array();
+ $groups['_']['id'] = $this->id . '__';
+ $groups['_']['text'] = Text::sprintf('JOPTION_FROM_MODULE');
+ $groups['_']['items'] = array();
+
+ foreach ($module_layouts as $file) {
+ // Add an option to the module group
+ $value = basename($file, '.php');
+ $text = $lang->hasKey($key = strtoupper($module . '_LAYOUT_' . $value)) ? Text::_($key) : $value;
+ $groups['_']['items'][] = HTMLHelper::_('select.option', '_:' . $value, $text);
+ }
+ }
+
+ // Loop on all templates
+ if ($templates) {
+ foreach ($templates as $template) {
+ // Load language file
+ $lang->load('tpl_' . $template->element . '.sys', $client->path)
+ || $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element);
+
+ $template_path = Path::clean($client->path . '/templates/' . $template->element . '/html/' . $module);
+
+ // Add the layout options from the template path.
+ if (is_dir($template_path) && ($files = Folder::files($template_path, '^[^_]*\.php$'))) {
+ foreach ($files as $i => $file) {
+ // Remove layout that already exist in component ones
+ if (\in_array($file, $module_layouts)) {
+ unset($files[$i]);
+ }
+ }
+
+ if (\count($files)) {
+ // Create the group for the template
+ $groups[$template->element] = array();
+ $groups[$template->element]['id'] = $this->id . '_' . $template->element;
+ $groups[$template->element]['text'] = Text::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
+ $groups[$template->element]['items'] = array();
+
+ foreach ($files as $file) {
+ // Add an option to the template group
+ $value = basename($file, '.php');
+ $text = $lang->hasKey($key = strtoupper('TPL_' . $template->element . '_' . $module . '_LAYOUT_' . $value))
+ ? Text::_($key) : $value;
+ $groups[$template->element]['items'][] = HTMLHelper::_('select.option', $template->element . ':' . $value, $text);
+ }
+ }
+ }
+ }
+ }
+
+ // Compute attributes for the grouped list
+ $attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
+ $attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
+
+ // Prepare HTML code
+ $html = array();
+
+ // Compute the current selected values
+ $selected = array($this->value);
+
+ // Add a grouped list
+ $html[] = HTMLHelper::_(
+ 'select.groupedlist',
+ $groups,
+ $this->name,
+ array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
+ );
+
+ return implode($html);
+ } else {
+ return '';
+ }
+ }
}
diff --git a/libraries/src/Form/Field/ModuleorderField.php b/libraries/src/Form/Field/ModuleorderField.php
index 9ec2e7d03ed34..830f9cc58f0ed 100644
--- a/libraries/src/Form/Field/ModuleorderField.php
+++ b/libraries/src/Form/Field/ModuleorderField.php
@@ -1,4 +1,5 @@
linked;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.6.3
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'linked':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.6.3
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->linked = isset($this->element['linked']) ? (int) $this->element['linked'] : 'position';
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup for the moduleorder field.
- *
- * @return string The field input markup.
- *
- * @since 1.6
- */
- protected function getInput()
- {
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.6.3
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- $extraData = array(
- 'ordering' => $this->form->getValue('ordering'),
- 'clientId' => $this->form->getValue('client_id'),
- 'moduleId' => $this->form->getValue('id'),
- 'name' => $this->name,
- 'token' => Session::getFormToken() . '=1',
- 'element' => $this->form->getName() . '_' . $this->linked
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'ModuleOrder';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.6.3
+ */
+ protected $layout = 'joomla.form.field.moduleorder';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.6.3
+ */
+ public function __get($name)
+ {
+ if ($name === 'linked') {
+ return $this->linked;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.6.3
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'linked':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.6.3
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->linked = isset($this->element['linked']) ? (int) $this->element['linked'] : 'position';
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup for the moduleorder field.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.6.3
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ $extraData = array(
+ 'ordering' => $this->form->getValue('ordering'),
+ 'clientId' => $this->form->getValue('client_id'),
+ 'moduleId' => $this->form->getValue('id'),
+ 'name' => $this->name,
+ 'token' => Session::getFormToken() . '=1',
+ 'element' => $this->form->getName() . '_' . $this->linked
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/ModulepositionField.php b/libraries/src/Form/Field/ModulepositionField.php
index 52858504225ba..f264f129984b6 100644
--- a/libraries/src/Form/Field/ModulepositionField.php
+++ b/libraries/src/Form/Field/ModulepositionField.php
@@ -1,4 +1,5 @@
clientId;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'clientId':
- $this->clientId = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- // Get the client id.
- $clientId = $this->element['client_id'];
-
- if (!isset($clientId))
- {
- $clientName = $this->element['client'];
-
- if (isset($clientName))
- {
- $client = ApplicationHelper::getClientInfo($clientName, true);
- $clientId = $client->id;
- }
- }
-
- if (!isset($clientId) && $this->form instanceof Form)
- {
- $clientId = $this->form->getValue('client_id');
- }
-
- $this->clientId = (int) $clientId;
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.6
- */
- protected function getInput()
- {
- // Build the script.
- $script = array();
- $script[] = ' function jSelectPosition_' . $this->id . '(name) {';
- $script[] = ' document.getElementById("' . $this->id . '").value = name;';
- $script[] = ' jModalClose();';
- $script[] = ' }';
-
- // Add the script to the document head.
- Factory::getDocument()->addScriptDeclaration(implode("\n", $script));
-
- // Setup variables for display.
- $html = array();
- $link = 'index.php?option=com_modules&view=positions&layout=modal&tmpl=component&function=jSelectPosition_' . $this->id
- . '&client_id=' . $this->clientId;
-
- // The current user display field.
- $html[] = '';
-
- return implode("\n", $html);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $type = 'ModulePosition';
+
+ /**
+ * The client ID.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $clientId;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'clientId') {
+ return $this->clientId;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'clientId':
+ $this->clientId = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ // Get the client id.
+ $clientId = $this->element['client_id'];
+
+ if (!isset($clientId)) {
+ $clientName = $this->element['client'];
+
+ if (isset($clientName)) {
+ $client = ApplicationHelper::getClientInfo($clientName, true);
+ $clientId = $client->id;
+ }
+ }
+
+ if (!isset($clientId) && $this->form instanceof Form) {
+ $clientId = $this->form->getValue('client_id');
+ }
+
+ $this->clientId = (int) $clientId;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ // Build the script.
+ $script = array();
+ $script[] = ' function jSelectPosition_' . $this->id . '(name) {';
+ $script[] = ' document.getElementById("' . $this->id . '").value = name;';
+ $script[] = ' jModalClose();';
+ $script[] = ' }';
+
+ // Add the script to the document head.
+ Factory::getDocument()->addScriptDeclaration(implode("\n", $script));
+
+ // Setup variables for display.
+ $html = array();
+ $link = 'index.php?option=com_modules&view=positions&layout=modal&tmpl=component&function=jSelectPosition_' . $this->id
+ . '&client_id=' . $this->clientId;
+
+ // The current user display field.
+ $html[] = '';
+
+ return implode("\n", $html);
+ }
}
diff --git a/libraries/src/Form/Field/ModuletagField.php b/libraries/src/Form/Field/ModuletagField.php
index 91f8c7e28b97b..a05120cc603fd 100644
--- a/libraries/src/Form/Field/ModuletagField.php
+++ b/libraries/src/Form/Field/ModuletagField.php
@@ -1,4 +1,5 @@
element['label']) && empty($this->element['description']))
- {
- return '';
- }
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getLabel()
+ {
+ if (empty($this->element['label']) && empty($this->element['description'])) {
+ return '';
+ }
- $html = [];
- $class = [];
+ $html = [];
+ $class = [];
- if (!empty($this->class))
- {
- $class[] = $this->class;
- }
+ if (!empty($this->class)) {
+ $class[] = $this->class;
+ }
- if ($close = (string) $this->element['close'])
- {
- HTMLHelper::_('bootstrap.alert');
- $close = $close === 'true' ? 'alert' : $close;
- $html[] = ' ';
- $class[] = 'alert-dismissible show';
- }
+ if ($close = (string) $this->element['close']) {
+ HTMLHelper::_('bootstrap.alert');
+ $close = $close === 'true' ? 'alert' : $close;
+ $html[] = ' ';
+ $class[] = 'alert-dismissible show';
+ }
- $class = $class ? ' class="' . implode(' ', $class) . '"' : '';
- $title = $this->element['label'] ? (string) $this->element['label'] : ($this->element['title'] ? (string) $this->element['title'] : '');
- $heading = $this->element['heading'] ? (string) $this->element['heading'] : 'h4';
- $description = (string) $this->element['description'];
- $html[] = !empty($title) ? '<' . $heading . '>' . Text::_($title) . '' . $heading . '>' : '';
- $html[] = !empty($description) ? Text::_($description) : '';
+ $class = $class ? ' class="' . implode(' ', $class) . '"' : '';
+ $title = $this->element['label'] ? (string) $this->element['label'] : ($this->element['title'] ? (string) $this->element['title'] : '');
+ $heading = $this->element['heading'] ? (string) $this->element['heading'] : 'h4';
+ $description = (string) $this->element['description'];
+ $html[] = !empty($title) ? '<' . $heading . '>' . Text::_($title) . '' . $heading . '>' : '';
+ $html[] = !empty($description) ? Text::_($description) : '';
- return '' . implode('', $html);
- }
+ return '
' . implode('', $html);
+ }
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- return '';
- }
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ return '';
+ }
}
diff --git a/libraries/src/Form/Field/NumberField.php b/libraries/src/Form/Field/NumberField.php
index e138060978a30..88a6e8530e136 100644
--- a/libraries/src/Form/Field/NumberField.php
+++ b/libraries/src/Form/Field/NumberField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'step':
- case 'min':
- case 'max':
- $this->$name = (float) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- // It is better not to force any default limits if none is specified
- $this->max = isset($this->element['max']) ? (float) $this->element['max'] : null;
- $this->min = isset($this->element['min']) ? (float) $this->element['min'] : null;
- $this->step = isset($this->element['step']) ? (float) $this->element['step'] : 1;
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.2
- */
- protected function getInput()
- {
- if ($this->element['useglobal'])
- {
- $component = Factory::getApplication()->input->getCmd('option');
-
- // Get correct component for menu items
- if ($component === 'com_menus')
- {
- $link = $this->form->getData()->get('link');
- $uri = new Uri($link);
- $component = $uri->getVar('option', 'com_menus');
- }
-
- $params = ComponentHelper::getParams($component);
- $value = $params->get($this->fieldname);
-
- // Try with global configuration
- if (\is_null($value))
- {
- $value = Factory::getApplication()->get($this->fieldname);
- }
-
- // Try with menu configuration
- if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus')
- {
- $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
- }
-
- if (!\is_null($value))
- {
- $value = (string) $value;
-
- $this->hint = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
- }
- }
-
- // Trim the trailing line in the layout file
- return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // Initialize some field attributes.
- $extraData = array(
- 'max' => $this->max,
- 'min' => $this->min,
- 'step' => $this->step,
- 'value' => $this->value,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $type = 'Number';
+
+ /**
+ * The allowable maximum value of the field.
+ *
+ * @var float
+ * @since 3.2
+ */
+ protected $max = null;
+
+ /**
+ * The allowable minimum value of the field.
+ *
+ * @var float
+ * @since 3.2
+ */
+ protected $min = null;
+
+ /**
+ * The step by which value of the field increased or decreased.
+ *
+ * @var float
+ * @since 3.2
+ */
+ protected $step = 0;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7
+ */
+ protected $layout = 'joomla.form.field.number';
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'max':
+ case 'min':
+ case 'step':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'step':
+ case 'min':
+ case 'max':
+ $this->$name = (float) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ // It is better not to force any default limits if none is specified
+ $this->max = isset($this->element['max']) ? (float) $this->element['max'] : null;
+ $this->min = isset($this->element['min']) ? (float) $this->element['min'] : null;
+ $this->step = isset($this->element['step']) ? (float) $this->element['step'] : 1;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ if ($this->element['useglobal']) {
+ $component = Factory::getApplication()->input->getCmd('option');
+
+ // Get correct component for menu items
+ if ($component === 'com_menus') {
+ $link = $this->form->getData()->get('link');
+ $uri = new Uri($link);
+ $component = $uri->getVar('option', 'com_menus');
+ }
+
+ $params = ComponentHelper::getParams($component);
+ $value = $params->get($this->fieldname);
+
+ // Try with global configuration
+ if (\is_null($value)) {
+ $value = Factory::getApplication()->get($this->fieldname);
+ }
+
+ // Try with menu configuration
+ if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus') {
+ $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
+ }
+
+ if (!\is_null($value)) {
+ $value = (string) $value;
+
+ $this->hint = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
+ }
+ }
+
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // Initialize some field attributes.
+ $extraData = array(
+ 'max' => $this->max,
+ 'min' => $this->min,
+ 'step' => $this->step,
+ 'value' => $this->value,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/OrderingField.php b/libraries/src/Form/Field/OrderingField.php
index 35753b471ff17..e88da2112c398 100644
--- a/libraries/src/Form/Field/OrderingField.php
+++ b/libraries/src/Form/Field/OrderingField.php
@@ -1,4 +1,5 @@
contentType;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'contentType':
- $this->contentType = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- $this->contentType = (string) $this->element['content_type'];
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.2
- */
- protected function getInput()
- {
- $html = array();
- $attr = '';
-
- // Initialize some field attributes.
- $attr .= !empty($this->class) ? ' class="form-select' . $this->class . '"' : ' class="form-select"';
- $attr .= $this->disabled ? ' disabled' : '';
- $attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
-
- // Initialize JavaScript field attributes.
- $attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';
-
- $itemId = (int) $this->getItemId();
-
- $query = $this->getQuery();
-
- // Create a read-only list (no name) with a hidden input to store the value.
- if ($this->readonly)
- {
- $html[] = HTMLHelper::_('list.ordering', '', $query, trim($attr), $this->value, $itemId ? 0 : 1, $this->id);
- $html[] = ' ';
- }
- else
- {
- // Create a regular list.
- $html[] = HTMLHelper::_('list.ordering', $this->name, $query, trim($attr), $this->value, $itemId ? 0 : 1, $this->id);
- }
-
- return implode($html);
- }
-
- /**
- * Builds the query for the ordering list.
- *
- * @return DatabaseQuery The query for the ordering form field
- *
- * @since 3.2
- */
- protected function getQuery()
- {
- $categoryId = (int) $this->form->getValue('catid');
- $ucmType = new UCMType;
- $ucmRow = $ucmType->getType($ucmType->getTypeId($this->contentType));
- $ucmMapCommon = json_decode($ucmRow->field_mappings)->common;
-
- if (\is_object($ucmMapCommon))
- {
- $ordering = $ucmMapCommon->core_ordering;
- $title = $ucmMapCommon->core_title;
- }
- elseif (\is_array($ucmMapCommon))
- {
- $ordering = $ucmMapCommon[0]->core_ordering;
- $title = $ucmMapCommon[0]->core_title;
- }
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
- $query->select([$db->quoteName($ordering, 'value'), $db->quoteName($title, 'text')])
- ->from($db->quoteName(json_decode($ucmRow->table)->special->dbtable))
- ->where($db->quoteName('catid') . ' = :categoryId')
- ->order($db->quoteName('ordering'))
- ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
-
- return $query;
- }
-
- /**
- * Retrieves the current Item's Id.
- *
- * @return integer The current item ID
- *
- * @since 3.2
- */
- protected function getItemId()
- {
- return (int) $this->form->getValue('id');
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $type = 'Ordering';
+
+ /**
+ * The form field content type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $contentType;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'contentType') {
+ return $this->contentType;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'contentType':
+ $this->contentType = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ $this->contentType = (string) $this->element['content_type'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ $html = array();
+ $attr = '';
+
+ // Initialize some field attributes.
+ $attr .= !empty($this->class) ? ' class="form-select' . $this->class . '"' : ' class="form-select"';
+ $attr .= $this->disabled ? ' disabled' : '';
+ $attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
+
+ // Initialize JavaScript field attributes.
+ $attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';
+
+ $itemId = (int) $this->getItemId();
+
+ $query = $this->getQuery();
+
+ // Create a read-only list (no name) with a hidden input to store the value.
+ if ($this->readonly) {
+ $html[] = HTMLHelper::_('list.ordering', '', $query, trim($attr), $this->value, $itemId ? 0 : 1, $this->id);
+ $html[] = ' ';
+ } else {
+ // Create a regular list.
+ $html[] = HTMLHelper::_('list.ordering', $this->name, $query, trim($attr), $this->value, $itemId ? 0 : 1, $this->id);
+ }
+
+ return implode($html);
+ }
+
+ /**
+ * Builds the query for the ordering list.
+ *
+ * @return DatabaseQuery The query for the ordering form field
+ *
+ * @since 3.2
+ */
+ protected function getQuery()
+ {
+ $categoryId = (int) $this->form->getValue('catid');
+ $ucmType = new UCMType();
+ $ucmRow = $ucmType->getType($ucmType->getTypeId($this->contentType));
+ $ucmMapCommon = json_decode($ucmRow->field_mappings)->common;
+
+ if (\is_object($ucmMapCommon)) {
+ $ordering = $ucmMapCommon->core_ordering;
+ $title = $ucmMapCommon->core_title;
+ } elseif (\is_array($ucmMapCommon)) {
+ $ordering = $ucmMapCommon[0]->core_ordering;
+ $title = $ucmMapCommon[0]->core_title;
+ }
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+ $query->select([$db->quoteName($ordering, 'value'), $db->quoteName($title, 'text')])
+ ->from($db->quoteName(json_decode($ucmRow->table)->special->dbtable))
+ ->where($db->quoteName('catid') . ' = :categoryId')
+ ->order($db->quoteName('ordering'))
+ ->bind(':categoryId', $categoryId, ParameterType::INTEGER);
+
+ return $query;
+ }
+
+ /**
+ * Retrieves the current Item's Id.
+ *
+ * @return integer The current item ID
+ *
+ * @since 3.2
+ */
+ protected function getItemId()
+ {
+ return (int) $this->form->getValue('id');
+ }
}
diff --git a/libraries/src/Form/Field/PasswordField.php b/libraries/src/Form/Field/PasswordField.php
index d58e0063232ae..38701fed17475 100644
--- a/libraries/src/Form/Field/PasswordField.php
+++ b/libraries/src/Form/Field/PasswordField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- $value = (string) $value;
-
- switch ($name)
- {
- case 'maxLength':
- case 'threshold':
- $this->$name = $value;
- break;
-
- case 'lock':
- case 'meter':
- case 'force':
- $this->$name = ($value === 'true' || $value === $name || $value === '1');
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $lock = (string) $this->element['lock'];
- $this->lock = ($lock === 'true' || $lock === 'on' || $lock === '1');
- $this->maxLength = $this->element['maxlength'] ? (int) $this->element['maxlength'] : 99;
- $this->threshold = $this->element['threshold'] ? (int) $this->element['threshold'] : 66;
- $meter = (string) $this->element['strengthmeter'];
- $this->meter = ($meter === 'true' || $meter === 'on' || $meter === '1');
- $force = (string) $this->element['forcePassword'];
- $this->force = (($force === 'true' || $force === 'on' || $force === '1') && $this->meter === true);
- $rules = (string) $this->element['rules'];
- $this->rules = (($rules === 'true' || $rules === 'on' || $rules === '1') && $this->meter === true);
-
- // Set some initial values
- $this->minLength = 12;
- $this->minIntegers = 0;
- $this->minSymbols = 0;
- $this->minUppercase = 0;
- $this->minLowercase = 0;
-
- if (Factory::getApplication()->get('db') != '')
- {
- $this->minLength = (int) ComponentHelper::getParams('com_users')->get('minimum_length', 12);
- $this->minIntegers = (int) ComponentHelper::getParams('com_users')->get('minimum_integers', 0);
- $this->minSymbols = (int) ComponentHelper::getParams('com_users')->get('minimum_symbols', 0);
- $this->minUppercase = (int) ComponentHelper::getParams('com_users')->get('minimum_uppercase', 0);
- $this->minLowercase = (int) ComponentHelper::getParams('com_users')->get('minimum_lowercase', 0);
- }
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup for password.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- // Trim the trailing line in the layout file
- return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // Initialize some field attributes.
- $extraData = array(
- 'lock' => $this->lock,
- 'maxLength' => $this->maxLength,
- 'meter' => $this->meter,
- 'threshold' => $this->threshold,
- 'minLength' => $this->minLength,
- 'minIntegers' => $this->minIntegers,
- 'minSymbols' => $this->minSymbols,
- 'minUppercase' => $this->minUppercase,
- 'minLowercase' => $this->minLowercase,
- 'forcePassword' => $this->force,
- 'rules' => $this->rules,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Password';
+
+ /**
+ * The threshold of password field.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $threshold = 66;
+
+ /**
+ * The allowable maxlength of password.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $maxLength;
+
+ /**
+ * Whether to attach a password strength meter or not.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $meter = false;
+
+ /**
+ * Whether to attach a password strength meter or not.
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $force = false;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7
+ */
+ protected $layout = 'joomla.form.field.password';
+
+ /**
+ * Attach an unlock button and disable the input field,
+ * also remove the value from the output.
+ *
+ * @var boolean
+ * @since 3.9.24
+ */
+ protected $lock = false;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'lock':
+ case 'threshold':
+ case 'maxLength':
+ case 'meter':
+ case 'force':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ $value = (string) $value;
+
+ switch ($name) {
+ case 'maxLength':
+ case 'threshold':
+ $this->$name = $value;
+ break;
+
+ case 'lock':
+ case 'meter':
+ case 'force':
+ $this->$name = ($value === 'true' || $value === $name || $value === '1');
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $lock = (string) $this->element['lock'];
+ $this->lock = ($lock === 'true' || $lock === 'on' || $lock === '1');
+ $this->maxLength = $this->element['maxlength'] ? (int) $this->element['maxlength'] : 99;
+ $this->threshold = $this->element['threshold'] ? (int) $this->element['threshold'] : 66;
+ $meter = (string) $this->element['strengthmeter'];
+ $this->meter = ($meter === 'true' || $meter === 'on' || $meter === '1');
+ $force = (string) $this->element['forcePassword'];
+ $this->force = (($force === 'true' || $force === 'on' || $force === '1') && $this->meter === true);
+ $rules = (string) $this->element['rules'];
+ $this->rules = (($rules === 'true' || $rules === 'on' || $rules === '1') && $this->meter === true);
+
+ // Set some initial values
+ $this->minLength = 12;
+ $this->minIntegers = 0;
+ $this->minSymbols = 0;
+ $this->minUppercase = 0;
+ $this->minLowercase = 0;
+
+ if (Factory::getApplication()->get('db') != '') {
+ $this->minLength = (int) ComponentHelper::getParams('com_users')->get('minimum_length', 12);
+ $this->minIntegers = (int) ComponentHelper::getParams('com_users')->get('minimum_integers', 0);
+ $this->minSymbols = (int) ComponentHelper::getParams('com_users')->get('minimum_symbols', 0);
+ $this->minUppercase = (int) ComponentHelper::getParams('com_users')->get('minimum_uppercase', 0);
+ $this->minLowercase = (int) ComponentHelper::getParams('com_users')->get('minimum_lowercase', 0);
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup for password.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // Initialize some field attributes.
+ $extraData = array(
+ 'lock' => $this->lock,
+ 'maxLength' => $this->maxLength,
+ 'meter' => $this->meter,
+ 'threshold' => $this->threshold,
+ 'minLength' => $this->minLength,
+ 'minIntegers' => $this->minIntegers,
+ 'minSymbols' => $this->minSymbols,
+ 'minUppercase' => $this->minUppercase,
+ 'minLowercase' => $this->minLowercase,
+ 'forcePassword' => $this->force,
+ 'rules' => $this->rules,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/PluginsField.php b/libraries/src/Form/Field/PluginsField.php
index 6904223eeba29..a6964f10d0215 100644
--- a/libraries/src/Form/Field/PluginsField.php
+++ b/libraries/src/Form/Field/PluginsField.php
@@ -1,4 +1,5 @@
folder;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'folder':
- $this->folder = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->folder = (string) $this->element['folder'];
- }
-
- return $return;
- }
-
- /**
- * Method to get a list of options for a list input.
- *
- * @return array An array of JHtml options.
- *
- * @since 2.5.0
- */
- protected function getOptions()
- {
- $folder = $this->folder;
- $parentOptions = parent::getOptions();
-
- if (!empty($folder))
- {
- // Get list of plugins
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('element', 'value'),
- $db->quoteName('name', 'text'),
- ]
- )
- ->from($db->quoteName('#__extensions'))
- ->where(
- [
- $db->quoteName('folder') . ' = :folder',
- $db->quoteName('enabled') . ' = 1',
- ]
- )
- ->bind(':folder', $folder)
- ->order(
- [
- $db->quoteName('ordering'),
- $db->quoteName('name'),
- ]
- );
-
- if ((string) $this->element['useaccess'] === 'true')
- {
- $query->whereIn($db->quoteName('access'), Factory::getUser()->getAuthorisedViewLevels());
- }
-
- $options = $db->setQuery($query)->loadObjectList();
- $lang = Factory::getLanguage();
- $useGlobal = $this->element['useglobal'];
-
- if ($useGlobal)
- {
- $globalValue = Factory::getApplication()->get($this->fieldname);
- }
-
- foreach ($options as $i => $item)
- {
- $source = JPATH_PLUGINS . '/' . $folder . '/' . $item->value;
- $extension = 'plg_' . $folder . '_' . $item->value;
- $lang->load($extension . '.sys', JPATH_ADMINISTRATOR) || $lang->load($extension . '.sys', $source);
- $options[$i]->text = Text::_($item->text);
-
- // If we are using useglobal update the use global value text with the plugin text.
- if ($useGlobal && isset($parentOptions[0]) && $item->value === $globalValue)
- {
- $text = Text::_($extension);
- $parentOptions[0]->text = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', ($text === '' || $text === $extension ? $item->value : $text));
- }
- }
- }
- else
- {
- Log::add(Text::_('JFRAMEWORK_FORM_FIELDS_PLUGINS_ERROR_FOLDER_EMPTY'), Log::WARNING, 'jerror');
- }
-
- return array_merge($parentOptions, $options);
- }
+ /**
+ * The field type.
+ *
+ * @var string
+ * @since 2.5.0
+ */
+ protected $type = 'Plugins';
+
+ /**
+ * The path to folder for plugins.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $folder;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'folder') {
+ return $this->folder;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'folder':
+ $this->folder = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->folder = (string) $this->element['folder'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of JHtml options.
+ *
+ * @since 2.5.0
+ */
+ protected function getOptions()
+ {
+ $folder = $this->folder;
+ $parentOptions = parent::getOptions();
+
+ if (!empty($folder)) {
+ // Get list of plugins
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('element', 'value'),
+ $db->quoteName('name', 'text'),
+ ]
+ )
+ ->from($db->quoteName('#__extensions'))
+ ->where(
+ [
+ $db->quoteName('folder') . ' = :folder',
+ $db->quoteName('enabled') . ' = 1',
+ ]
+ )
+ ->bind(':folder', $folder)
+ ->order(
+ [
+ $db->quoteName('ordering'),
+ $db->quoteName('name'),
+ ]
+ );
+
+ if ((string) $this->element['useaccess'] === 'true') {
+ $query->whereIn($db->quoteName('access'), Factory::getUser()->getAuthorisedViewLevels());
+ }
+
+ $options = $db->setQuery($query)->loadObjectList();
+ $lang = Factory::getLanguage();
+ $useGlobal = $this->element['useglobal'];
+
+ if ($useGlobal) {
+ $globalValue = Factory::getApplication()->get($this->fieldname);
+ }
+
+ foreach ($options as $i => $item) {
+ $source = JPATH_PLUGINS . '/' . $folder . '/' . $item->value;
+ $extension = 'plg_' . $folder . '_' . $item->value;
+ $lang->load($extension . '.sys', JPATH_ADMINISTRATOR) || $lang->load($extension . '.sys', $source);
+ $options[$i]->text = Text::_($item->text);
+
+ // If we are using useglobal update the use global value text with the plugin text.
+ if ($useGlobal && isset($parentOptions[0]) && $item->value === $globalValue) {
+ $text = Text::_($extension);
+ $parentOptions[0]->text = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', ($text === '' || $text === $extension ? $item->value : $text));
+ }
+ }
+ } else {
+ Log::add(Text::_('JFRAMEWORK_FORM_FIELDS_PLUGINS_ERROR_FOLDER_EMPTY'), Log::WARNING, 'jerror');
+ }
+
+ return array_merge($parentOptions, $options);
+ }
}
diff --git a/libraries/src/Form/Field/PluginstatusField.php b/libraries/src/Form/Field/PluginstatusField.php
index 52e61619dff06..f99890d818ee7 100644
--- a/libraries/src/Form/Field/PluginstatusField.php
+++ b/libraries/src/Form/Field/PluginstatusField.php
@@ -1,4 +1,5 @@
'JDISABLED',
- '1' => 'JENABLED',
- );
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.5
+ */
+ protected $predefinedOptions = array(
+ '0' => 'JDISABLED',
+ '1' => 'JENABLED',
+ );
}
diff --git a/libraries/src/Form/Field/PredefinedlistField.php b/libraries/src/Form/Field/PredefinedlistField.php
index c6ce20b220e8a..9d124721c9cc0 100644
--- a/libraries/src/Form/Field/PredefinedlistField.php
+++ b/libraries/src/Form/Field/PredefinedlistField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see \Joomla\CMS\Form\FormField::setup()
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- // Note: $this->element['optionsFilter'] is not cast to string here to allow empty string value.
- $this->optionsFilter = $this->element['optionsFilter'] ? explode(',', (string) $this->element['optionsFilter']) : [];
- }
-
- return $return;
- }
-
- /**
- * Method to get the options to populate list
- *
- * @return array The field option objects.
- *
- * @since 3.2
- */
- protected function getOptions()
- {
- // Hash for caching
- $hash = md5($this->element);
- $type = strtolower($this->type);
-
- if (!isset(static::$options[$type][$hash]) && !empty($this->predefinedOptions))
- {
- static::$options[$type][$hash] = parent::getOptions();
-
- $options = array();
-
- foreach ($this->predefinedOptions as $value => $text)
- {
- $val = (string) $value;
-
- if (empty($this->optionsFilter) || in_array($val, $this->optionsFilter, true))
- {
- $text = $this->translate ? Text::_($text) : $text;
-
- $options[] = (object) array(
- 'value' => $value,
- 'text' => $text,
- );
- }
- }
-
- static::$options[$type][$hash] = array_merge(static::$options[$type][$hash], $options);
- }
-
- return static::$options[$type][$hash];
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $type = 'Predefinedlist';
+
+ /**
+ * Cached array of the category items.
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected static $options = array();
+
+ /**
+ * Available predefined options
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $predefinedOptions = array();
+
+ /**
+ * Translate options labels ?
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $translate = true;
+
+ /**
+ * Allows to use only specific values of the predefined list
+ *
+ * @var array
+ * @since 4.0.0
+ */
+ protected $optionsFilter = [];
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see \Joomla\CMS\Form\FormField::setup()
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ // Note: $this->element['optionsFilter'] is not cast to string here to allow empty string value.
+ $this->optionsFilter = $this->element['optionsFilter'] ? explode(',', (string) $this->element['optionsFilter']) : [];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the options to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.2
+ */
+ protected function getOptions()
+ {
+ // Hash for caching
+ $hash = md5($this->element);
+ $type = strtolower($this->type);
+
+ if (!isset(static::$options[$type][$hash]) && !empty($this->predefinedOptions)) {
+ static::$options[$type][$hash] = parent::getOptions();
+
+ $options = array();
+
+ foreach ($this->predefinedOptions as $value => $text) {
+ $val = (string) $value;
+
+ if (empty($this->optionsFilter) || in_array($val, $this->optionsFilter, true)) {
+ $text = $this->translate ? Text::_($text) : $text;
+
+ $options[] = (object) array(
+ 'value' => $value,
+ 'text' => $text,
+ );
+ }
+ }
+
+ static::$options[$type][$hash] = array_merge(static::$options[$type][$hash], $options);
+ }
+
+ return static::$options[$type][$hash];
+ }
}
diff --git a/libraries/src/Form/Field/RadioField.php b/libraries/src/Form/Field/RadioField.php
index 7d7a712375390..b137edaae7d4f 100644
--- a/libraries/src/Form/Field/RadioField.php
+++ b/libraries/src/Form/Field/RadioField.php
@@ -1,4 +1,5 @@
$this->getOptions(),
- 'value' => (string) $this->value,
- );
+ $extraData = array(
+ 'options' => $this->getOptions(),
+ 'value' => (string) $this->value,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/RadiobasicField.php b/libraries/src/Form/Field/RadiobasicField.php
index 4c12dd1478a12..d89832c982a7c 100644
--- a/libraries/src/Form/Field/RadiobasicField.php
+++ b/libraries/src/Form/Field/RadiobasicField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData());
- }
+ /**
+ * Method to get the radio button field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 4.0.0
+ */
+ protected function getInput()
+ {
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- $extraData = array(
- 'options' => $this->getOptions(),
- 'value' => (string) $this->value
- );
+ $extraData = array(
+ 'options' => $this->getOptions(),
+ 'value' => (string) $this->value
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/RangeField.php b/libraries/src/Form/Field/RangeField.php
index 560cbe36dad32..eb6ca934395b9 100644
--- a/libraries/src/Form/Field/RangeField.php
+++ b/libraries/src/Form/Field/RangeField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData());
- }
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- // Initialize some field attributes.
- $extraData = array(
- 'max' => $this->max,
- 'min' => $this->min,
- 'step' => $this->step,
- );
+ // Initialize some field attributes.
+ $extraData = array(
+ 'max' => $this->max,
+ 'min' => $this->min,
+ 'step' => $this->step,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/RedirectStatusField.php b/libraries/src/Form/Field/RedirectStatusField.php
index 5f4576eb10757..52b4980aa0bef 100644
--- a/libraries/src/Form/Field/RedirectStatusField.php
+++ b/libraries/src/Form/Field/RedirectStatusField.php
@@ -1,4 +1,5 @@
'JTRASHED',
- '0' => 'JDISABLED',
- '1' => 'JENABLED',
- '2' => 'JARCHIVED',
- '*' => 'JALL',
- );
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.8.0
+ */
+ protected $predefinedOptions = array(
+ '-2' => 'JTRASHED',
+ '0' => 'JDISABLED',
+ '1' => 'JENABLED',
+ '2' => 'JARCHIVED',
+ '*' => 'JALL',
+ );
}
diff --git a/libraries/src/Form/Field/RegistrationdaterangeField.php b/libraries/src/Form/Field/RegistrationdaterangeField.php
index 06b8b648581bb..b594d0cf57afc 100644
--- a/libraries/src/Form/Field/RegistrationdaterangeField.php
+++ b/libraries/src/Form/Field/RegistrationdaterangeField.php
@@ -1,4 +1,5 @@
'COM_USERS_OPTION_RANGE_TODAY',
- 'past_week' => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
- 'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
- 'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
- 'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
- 'past_year' => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
- 'post_year' => 'COM_USERS_OPTION_RANGE_POST_YEAR',
- );
+ /**
+ * Available options
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $predefinedOptions = array(
+ 'today' => 'COM_USERS_OPTION_RANGE_TODAY',
+ 'past_week' => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
+ 'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
+ 'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
+ 'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
+ 'past_year' => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
+ 'post_year' => 'COM_USERS_OPTION_RANGE_POST_YEAR',
+ );
- /**
- * Method to instantiate the form field object.
- *
- * @param Form $form The form to attach to the form field object.
- *
- * @since 1.7.0
- */
- public function __construct($form = null)
- {
- parent::__construct($form);
+ /**
+ * Method to instantiate the form field object.
+ *
+ * @param Form $form The form to attach to the form field object.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($form = null)
+ {
+ parent::__construct($form);
- // Load the required language
- $lang = Factory::getLanguage();
- $lang->load('com_users', JPATH_ADMINISTRATOR);
- }
+ // Load the required language
+ $lang = Factory::getLanguage();
+ $lang->load('com_users', JPATH_ADMINISTRATOR);
+ }
}
diff --git a/libraries/src/Form/Field/RulesField.php b/libraries/src/Form/Field/RulesField.php
index 5966b3a8f5373..7111f179efc83 100644
--- a/libraries/src/Form/Field/RulesField.php
+++ b/libraries/src/Form/Field/RulesField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'section':
- case 'component':
- case 'assetField':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->section = $this->element['section'] ? (string) $this->element['section'] : '';
- $this->component = $this->element['component'] ? (string) $this->element['component'] : '';
- $this->assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
- }
-
- return $return;
- }
-
- /**
- * Method to get the field input markup for Access Control Lists.
- * Optionally can be associated with a specific component and section.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- * @todo: Add access check.
- */
- protected function getInput()
- {
- // Initialise some field attributes.
- $section = $this->section;
- $assetField = $this->assetField;
- $component = empty($this->component) ? 'root.1' : $this->component;
-
- // Current view is global config?
- $this->isGlobalConfig = $component === 'root.1';
-
- // Get the actions for the asset.
- $this->actions = Access::getActionsFromFile(
- JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
- "/access/section[@name='" . $section . "']/"
- );
-
- if ($this->actions === false)
- {
- $this->actions = [];
- }
-
- // Iterate over the children and add to the actions.
- foreach ($this->element->children() as $el)
- {
- if ($el->getName() === 'action')
- {
- $this->actions[] = (object) array(
- 'name' => (string) $el['name'],
- 'title' => (string) $el['title'],
- 'description' => (string) $el['description'],
- );
- }
- }
-
- // Get the asset id.
- // Note that for global configuration, com_config injects asset_id = 1 into the form.
- $this->assetId = (int) $this->form->getValue($assetField);
- $this->newItem = empty($this->assetId) && $this->isGlobalConfig === false && $section !== 'component';
-
- // If the asset id is empty (component or new item).
- if (empty($this->assetId))
- {
- // Get the component asset id as fallback.
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select($db->quoteName('id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('name') . ' = :component')
- ->bind(':component', $component);
-
- $db->setQuery($query);
-
- $this->assetId = (int) $db->loadResult();
-
- /**
- * @todo: incorrect info
- * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config).
- * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct.
- * Also, currently it uses the component permission, but should use the calculated permissions for achild of the component/section.
- */
- }
-
- // If not in global config we need the parent_id asset to calculate permissions.
- if (!$this->isGlobalConfig)
- {
- // In this case we need to get the component rules too.
- $db = $this->getDatabase();
-
- $query = $db->getQuery(true)
- ->select($db->quoteName('parent_id'))
- ->from($db->quoteName('#__assets'))
- ->where($db->quoteName('id') . ' = :assetId')
- ->bind(':assetId', $this->assetId, ParameterType::INTEGER);
-
- $db->setQuery($query);
-
- $this->parentAssetId = (int) $db->loadResult();
- }
-
- // Get the rules for just this asset (non-recursive).
- $this->assetRules = Access::getAssetRules($this->assetId, false, false);
-
- // Get the available user groups.
- $this->groups = $this->getUserGroups();
-
- // Trim the trailing line in the layout file
- return trim($this->getRenderer($this->layout)->render($this->getLayoutData()));
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- $extraData = array(
- 'groups' => $this->groups,
- 'section' => $this->section,
- 'actions' => $this->actions,
- 'assetId' => $this->assetId,
- 'newItem' => $this->newItem,
- 'assetRules' => $this->assetRules,
- 'isGlobalConfig' => $this->isGlobalConfig,
- 'parentAssetId' => $this->parentAssetId,
- 'component' => $this->component,
- );
-
- return array_merge($data, $extraData);
- }
-
- /**
- * Get a list of the user groups.
- *
- * @return array
- *
- * @since 1.7.0
- */
- protected function getUserGroups()
- {
- $options = UserGroupsHelper::getInstance()->getAll();
-
- foreach ($options as &$option)
- {
- $option->value = $option->id;
- $option->text = $option->title;
- }
-
- return array_values($options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Rules';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.rules';
+
+ /**
+ * The section.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $section;
+
+ /**
+ * The component.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $component;
+
+ /**
+ * The assetField.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $assetField;
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'section':
+ case 'component':
+ case 'assetField':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'section':
+ case 'component':
+ case 'assetField':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->section = $this->element['section'] ? (string) $this->element['section'] : '';
+ $this->component = $this->element['component'] ? (string) $this->element['component'] : '';
+ $this->assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the field input markup for Access Control Lists.
+ * Optionally can be associated with a specific component and section.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ * @todo: Add access check.
+ */
+ protected function getInput()
+ {
+ // Initialise some field attributes.
+ $section = $this->section;
+ $assetField = $this->assetField;
+ $component = empty($this->component) ? 'root.1' : $this->component;
+
+ // Current view is global config?
+ $this->isGlobalConfig = $component === 'root.1';
+
+ // Get the actions for the asset.
+ $this->actions = Access::getActionsFromFile(
+ JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
+ "/access/section[@name='" . $section . "']/"
+ );
+
+ if ($this->actions === false) {
+ $this->actions = [];
+ }
+
+ // Iterate over the children and add to the actions.
+ foreach ($this->element->children() as $el) {
+ if ($el->getName() === 'action') {
+ $this->actions[] = (object) array(
+ 'name' => (string) $el['name'],
+ 'title' => (string) $el['title'],
+ 'description' => (string) $el['description'],
+ );
+ }
+ }
+
+ // Get the asset id.
+ // Note that for global configuration, com_config injects asset_id = 1 into the form.
+ $this->assetId = (int) $this->form->getValue($assetField);
+ $this->newItem = empty($this->assetId) && $this->isGlobalConfig === false && $section !== 'component';
+
+ // If the asset id is empty (component or new item).
+ if (empty($this->assetId)) {
+ // Get the component asset id as fallback.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('name') . ' = :component')
+ ->bind(':component', $component);
+
+ $db->setQuery($query);
+
+ $this->assetId = (int) $db->loadResult();
+
+ /**
+ * @todo: incorrect info
+ * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config).
+ * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct.
+ * Also, currently it uses the component permission, but should use the calculated permissions for achild of the component/section.
+ */
+ }
+
+ // If not in global config we need the parent_id asset to calculate permissions.
+ if (!$this->isGlobalConfig) {
+ // In this case we need to get the component rules too.
+ $db = $this->getDatabase();
+
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('parent_id'))
+ ->from($db->quoteName('#__assets'))
+ ->where($db->quoteName('id') . ' = :assetId')
+ ->bind(':assetId', $this->assetId, ParameterType::INTEGER);
+
+ $db->setQuery($query);
+
+ $this->parentAssetId = (int) $db->loadResult();
+ }
+
+ // Get the rules for just this asset (non-recursive).
+ $this->assetRules = Access::getAssetRules($this->assetId, false, false);
+
+ // Get the available user groups.
+ $this->groups = $this->getUserGroups();
+
+ // Trim the trailing line in the layout file
+ return trim($this->getRenderer($this->layout)->render($this->getLayoutData()));
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ $extraData = array(
+ 'groups' => $this->groups,
+ 'section' => $this->section,
+ 'actions' => $this->actions,
+ 'assetId' => $this->assetId,
+ 'newItem' => $this->newItem,
+ 'assetRules' => $this->assetRules,
+ 'isGlobalConfig' => $this->isGlobalConfig,
+ 'parentAssetId' => $this->parentAssetId,
+ 'component' => $this->component,
+ );
+
+ return array_merge($data, $extraData);
+ }
+
+ /**
+ * Get a list of the user groups.
+ *
+ * @return array
+ *
+ * @since 1.7.0
+ */
+ protected function getUserGroups()
+ {
+ $options = UserGroupsHelper::getInstance()->getAll();
+
+ foreach ($options as &$option) {
+ $option->value = $option->id;
+ $option->text = $option->title;
+ }
+
+ return array_values($options);
+ }
}
diff --git a/libraries/src/Form/Field/SessionhandlerField.php b/libraries/src/Form/Field/SessionhandlerField.php
index 600bc48524e81..db5dae650be99 100644
--- a/libraries/src/Form/Field/SessionhandlerField.php
+++ b/libraries/src/Form/Field/SessionhandlerField.php
@@ -1,4 +1,5 @@
class) ? ' class="' . $this->class . '"' : '';
- $html[] = '';
- $html[] = ' ';
- $html[] = '';
-
- if ((string) $this->element['hr'] === 'true')
- {
- $html[] = ' ';
- }
- else
- {
- $label = '';
-
- // Get the label text from the XML element, defaulting to the element name.
- $text = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
- $text = $this->translateLabel ? Text::_($text) : $text;
-
- // Build the class for the label.
- $class = !empty($this->description) ? 'hasPopover' : '';
- $class = $this->required == true ? $class . ' required' : $class;
-
- // Add the opening label tag and main attributes attributes.
- $label .= 'description))
- {
- HTMLHelper::_('bootstrap.popover', '.hasPopover');
- $label .= ' title="' . htmlspecialchars(trim($text, ':'), ENT_COMPAT, 'UTF-8') . '"';
- $label .= ' data-bs-content="' . htmlspecialchars(
- $this->translateDescription ? Text::_($this->description) : $this->description,
- ENT_COMPAT,
- 'UTF-8'
- ) . '"';
-
- if (Factory::getLanguage()->isRtl())
- {
- $label .= ' data-bs-placement="left"';
- }
- }
-
- // Add the label text and closing tag.
- $label .= '>' . $text . ' ';
- $html[] = $label;
- }
-
- $html[] = ' ';
- $html[] = ' ';
- $html[] = ' ';
-
- return implode('', $html);
- }
-
- /**
- * Method to get the field title.
- *
- * @return string The field title.
- *
- * @since 1.7.0
- */
- protected function getTitle()
- {
- return $this->getLabel();
- }
-
- /**
- * Method to get a control group with label and input.
- *
- * @param array $options Options to be passed into the rendering of the field
- *
- * @return string A string containing the html for the control group
- *
- * @since 3.7.3
- */
- public function renderField($options = array())
- {
- $options['class'] = empty($options['class']) ? 'field-spacer' : $options['class'] . ' field-spacer';
-
- return parent::renderField($options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Spacer';
+
+ /**
+ * Method to get the field input markup for a spacer.
+ * The spacer does not have accept input.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ return ' ';
+ }
+
+ /**
+ * Method to get the field label markup for a spacer.
+ * Use the label text or name from the XML element as the spacer or
+ * Use a hr="true" to automatically generate plain hr markup
+ *
+ * @return string The field label markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getLabel()
+ {
+ $html = array();
+ $class = !empty($this->class) ? ' class="' . $this->class . '"' : '';
+ $html[] = '';
+ $html[] = ' ';
+ $html[] = '';
+
+ if ((string) $this->element['hr'] === 'true') {
+ $html[] = ' ';
+ } else {
+ $label = '';
+
+ // Get the label text from the XML element, defaulting to the element name.
+ $text = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
+ $text = $this->translateLabel ? Text::_($text) : $text;
+
+ // Build the class for the label.
+ $class = !empty($this->description) ? 'hasPopover' : '';
+ $class = $this->required == true ? $class . ' required' : $class;
+
+ // Add the opening label tag and main attributes attributes.
+ $label .= 'description)) {
+ HTMLHelper::_('bootstrap.popover', '.hasPopover');
+ $label .= ' title="' . htmlspecialchars(trim($text, ':'), ENT_COMPAT, 'UTF-8') . '"';
+ $label .= ' data-bs-content="' . htmlspecialchars(
+ $this->translateDescription ? Text::_($this->description) : $this->description,
+ ENT_COMPAT,
+ 'UTF-8'
+ ) . '"';
+
+ if (Factory::getLanguage()->isRtl()) {
+ $label .= ' data-bs-placement="left"';
+ }
+ }
+
+ // Add the label text and closing tag.
+ $label .= '>' . $text . ' ';
+ $html[] = $label;
+ }
+
+ $html[] = ' ';
+ $html[] = ' ';
+ $html[] = ' ';
+
+ return implode('', $html);
+ }
+
+ /**
+ * Method to get the field title.
+ *
+ * @return string The field title.
+ *
+ * @since 1.7.0
+ */
+ protected function getTitle()
+ {
+ return $this->getLabel();
+ }
+
+ /**
+ * Method to get a control group with label and input.
+ *
+ * @param array $options Options to be passed into the rendering of the field
+ *
+ * @return string A string containing the html for the control group
+ *
+ * @since 3.7.3
+ */
+ public function renderField($options = array())
+ {
+ $options['class'] = empty($options['class']) ? 'field-spacer' : $options['class'] . ' field-spacer';
+
+ return parent::renderField($options);
+ }
}
diff --git a/libraries/src/Form/Field/SqlField.php b/libraries/src/Form/Field/SqlField.php
index 6a9d286a210c4..355e383aeecf4 100644
--- a/libraries/src/Form/Field/SqlField.php
+++ b/libraries/src/Form/Field/SqlField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'keyField':
- case 'valueField':
- case 'translate':
- case 'query':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- // Check if its using the old way
- $this->query = (string) $this->element['query'];
-
- if (empty($this->query))
- {
- // Get the query from the form
- $query = array();
- $defaults = array();
-
- $sql_select = (string) $this->element['sql_select'];
- $sql_from = (string) $this->element['sql_from'];
-
- if ($sql_select && $sql_from)
- {
- $query['select'] = $sql_select;
- $query['from'] = $sql_from;
- $query['join'] = (string) $this->element['sql_join'];
- $query['where'] = (string) $this->element['sql_where'];
- $query['group'] = (string) $this->element['sql_group'];
- $query['order'] = (string) $this->element['sql_order'];
-
- // Get the filters
- $filters = isset($this->element['sql_filter']) ? explode(',', $this->element['sql_filter']) : '';
-
- // Get the default value for query if empty
- if (\is_array($filters))
- {
- foreach ($filters as $filter)
- {
- $name = "sql_default_{$filter}";
- $attrib = (string) $this->element[$name];
-
- if (!empty($attrib))
- {
- $defaults[$filter] = $attrib;
- }
- }
- }
-
- // Process the query
- $this->query = $this->processQuery($query, $filters, $defaults);
- }
- }
-
- $this->keyField = (string) $this->element['key_field'] ?: 'value';
- $this->valueField = (string) $this->element['value_field'] ?: (string) $this->element['name'];
- $this->translate = (string) $this->element['translate'] ?: false;
- $this->header = (string) $this->element['header'] ?: false;
- }
-
- return $return;
- }
-
- /**
- * Method to process the query from form.
- *
- * @param array $conditions The conditions from the form.
- * @param string $filters The columns to filter.
- * @param array $defaults The defaults value to set if condition is empty.
- *
- * @return DatabaseQuery The query object.
- *
- * @since 3.5
- */
- protected function processQuery($conditions, $filters, $defaults)
- {
- // Get the database object.
- $db = $this->getDatabase();
-
- // Get the query object
- $query = $db->getQuery(true);
-
- // Select fields
- $query->select($conditions['select']);
-
- // From selected table
- $query->from($conditions['from']);
-
- // Join over the groups
- if (!empty($conditions['join']))
- {
- $query->join('LEFT', $conditions['join']);
- }
-
- // Where condition
- if (!empty($conditions['where']))
- {
- $query->where($conditions['where']);
- }
-
- // Group by
- if (!empty($conditions['group']))
- {
- $query->group($conditions['group']);
- }
-
- // Process the filters
- if (\is_array($filters))
- {
- $html_filters = Factory::getApplication()->getUserStateFromRequest($this->context . '.filter', 'filter', array(), 'array');
-
- foreach ($filters as $k => $value)
- {
- if (!empty($html_filters[$value]))
- {
- $escape = $db->quote($db->escape($html_filters[$value]), false);
-
- $query->where("{$value} = {$escape}");
- }
- elseif (!empty($defaults[$value]))
- {
- $escape = $db->quote($db->escape($defaults[$value]), false);
-
- $query->where("{$value} = {$escape}");
- }
- }
- }
-
- // Add order to query
- if (!empty($conditions['order']))
- {
- $query->order($conditions['order']);
- }
-
- return $query;
- }
-
- /**
- * Method to get the custom field options.
- * Use the query attribute to supply a query to generate the list.
- *
- * @return array The field option objects.
- *
- * @since 1.7.0
- */
- protected function getOptions()
- {
- $options = array();
-
- // Initialize some field attributes.
- $key = $this->keyField;
- $value = $this->valueField;
- $header = $this->header;
-
- if ($this->query)
- {
- // Get the database object.
- $db = $this->getDatabase();
-
- // Set the query and get the result list.
- $db->setQuery($this->query);
-
- try
- {
- $items = $db->loadObjectList();
- }
- catch (ExecutionFailureException $e)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
- }
- }
-
- // Add header.
- if (!empty($header))
- {
- $header_title = Text::_($header);
- $options[] = HTMLHelper::_('select.option', '', $header_title);
- }
-
- // Build the field options.
- if (!empty($items))
- {
- foreach ($items as $item)
- {
- if ($this->translate == true)
- {
- $options[] = HTMLHelper::_('select.option', $item->$key, Text::_($item->$value));
- }
- else
- {
- $options[] = HTMLHelper::_('select.option', $item->$key, $item->$value);
- }
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- return $options;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ public $type = 'SQL';
+
+ /**
+ * The keyField.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $keyField;
+
+ /**
+ * The valueField.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $valueField;
+
+ /**
+ * The translate.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $translate = false;
+
+ /**
+ * The query.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $query;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'keyField':
+ case 'valueField':
+ case 'translate':
+ case 'query':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'keyField':
+ case 'valueField':
+ case 'translate':
+ case 'query':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ // Check if its using the old way
+ $this->query = (string) $this->element['query'];
+
+ if (empty($this->query)) {
+ // Get the query from the form
+ $query = array();
+ $defaults = array();
+
+ $sql_select = (string) $this->element['sql_select'];
+ $sql_from = (string) $this->element['sql_from'];
+
+ if ($sql_select && $sql_from) {
+ $query['select'] = $sql_select;
+ $query['from'] = $sql_from;
+ $query['join'] = (string) $this->element['sql_join'];
+ $query['where'] = (string) $this->element['sql_where'];
+ $query['group'] = (string) $this->element['sql_group'];
+ $query['order'] = (string) $this->element['sql_order'];
+
+ // Get the filters
+ $filters = isset($this->element['sql_filter']) ? explode(',', $this->element['sql_filter']) : '';
+
+ // Get the default value for query if empty
+ if (\is_array($filters)) {
+ foreach ($filters as $filter) {
+ $name = "sql_default_{$filter}";
+ $attrib = (string) $this->element[$name];
+
+ if (!empty($attrib)) {
+ $defaults[$filter] = $attrib;
+ }
+ }
+ }
+
+ // Process the query
+ $this->query = $this->processQuery($query, $filters, $defaults);
+ }
+ }
+
+ $this->keyField = (string) $this->element['key_field'] ?: 'value';
+ $this->valueField = (string) $this->element['value_field'] ?: (string) $this->element['name'];
+ $this->translate = (string) $this->element['translate'] ?: false;
+ $this->header = (string) $this->element['header'] ?: false;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to process the query from form.
+ *
+ * @param array $conditions The conditions from the form.
+ * @param string $filters The columns to filter.
+ * @param array $defaults The defaults value to set if condition is empty.
+ *
+ * @return DatabaseQuery The query object.
+ *
+ * @since 3.5
+ */
+ protected function processQuery($conditions, $filters, $defaults)
+ {
+ // Get the database object.
+ $db = $this->getDatabase();
+
+ // Get the query object
+ $query = $db->getQuery(true);
+
+ // Select fields
+ $query->select($conditions['select']);
+
+ // From selected table
+ $query->from($conditions['from']);
+
+ // Join over the groups
+ if (!empty($conditions['join'])) {
+ $query->join('LEFT', $conditions['join']);
+ }
+
+ // Where condition
+ if (!empty($conditions['where'])) {
+ $query->where($conditions['where']);
+ }
+
+ // Group by
+ if (!empty($conditions['group'])) {
+ $query->group($conditions['group']);
+ }
+
+ // Process the filters
+ if (\is_array($filters)) {
+ $html_filters = Factory::getApplication()->getUserStateFromRequest($this->context . '.filter', 'filter', array(), 'array');
+
+ foreach ($filters as $k => $value) {
+ if (!empty($html_filters[$value])) {
+ $escape = $db->quote($db->escape($html_filters[$value]), false);
+
+ $query->where("{$value} = {$escape}");
+ } elseif (!empty($defaults[$value])) {
+ $escape = $db->quote($db->escape($defaults[$value]), false);
+
+ $query->where("{$value} = {$escape}");
+ }
+ }
+ }
+
+ // Add order to query
+ if (!empty($conditions['order'])) {
+ $query->order($conditions['order']);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Method to get the custom field options.
+ * Use the query attribute to supply a query to generate the list.
+ *
+ * @return array The field option objects.
+ *
+ * @since 1.7.0
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ // Initialize some field attributes.
+ $key = $this->keyField;
+ $value = $this->valueField;
+ $header = $this->header;
+
+ if ($this->query) {
+ // Get the database object.
+ $db = $this->getDatabase();
+
+ // Set the query and get the result list.
+ $db->setQuery($this->query);
+
+ try {
+ $items = $db->loadObjectList();
+ } catch (ExecutionFailureException $e) {
+ Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
+ }
+ }
+
+ // Add header.
+ if (!empty($header)) {
+ $header_title = Text::_($header);
+ $options[] = HTMLHelper::_('select.option', '', $header_title);
+ }
+
+ // Build the field options.
+ if (!empty($items)) {
+ foreach ($items as $item) {
+ if ($this->translate == true) {
+ $options[] = HTMLHelper::_('select.option', $item->$key, Text::_($item->$value));
+ } else {
+ $options[] = HTMLHelper::_('select.option', $item->$key, $item->$value);
+ }
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/StatusField.php b/libraries/src/Form/Field/StatusField.php
index cb48372ac5f29..f10891807120c 100644
--- a/libraries/src/Form/Field/StatusField.php
+++ b/libraries/src/Form/Field/StatusField.php
@@ -1,4 +1,5 @@
'JTRASHED',
- 0 => 'JUNPUBLISHED',
- 1 => 'JPUBLISHED',
- 2 => 'JARCHIVED',
- '*' => 'JALL',
- );
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $predefinedOptions = array(
+ -2 => 'JTRASHED',
+ 0 => 'JUNPUBLISHED',
+ 1 => 'JPUBLISHED',
+ 2 => 'JARCHIVED',
+ '*' => 'JALL',
+ );
}
diff --git a/libraries/src/Form/Field/SubformField.php b/libraries/src/Form/Field/SubformField.php
index ecff6faf366a5..4d5012274b915 100644
--- a/libraries/src/Form/Field/SubformField.php
+++ b/libraries/src/Form/Field/SubformField.php
@@ -1,4 +1,5 @@
+ *
*
* @since 3.6
*/
class SubformField extends FormField
{
- /**
- * The form field type.
- * @var string
- */
- protected $type = 'Subform';
-
- /**
- * Form source
- * @var string
- */
- protected $formsource;
-
- /**
- * Minimum items in repeat mode
- * @var integer
- */
- protected $min = 0;
-
- /**
- * Maximum items in repeat mode
- * @var integer
- */
- protected $max = 1000;
-
- /**
- * Layout to render the form
- * @var string
- */
- protected $layout = 'joomla.form.field.subform.default';
-
- /**
- * Whether group subform fields by it`s fieldset
- * @var boolean
- */
- protected $groupByFieldset = false;
-
- /**
- * Which buttons to show in multiple mode
- * @var array $buttons
- */
- protected $buttons = array('add' => true, 'remove' => true, 'move' => true);
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 3.6
- */
- public function __get($name)
- {
- switch ($name)
- {
- case 'formsource':
- case 'min':
- case 'max':
- case 'layout':
- case 'groupByFieldset':
- case 'buttons':
- return $this->$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.6
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'formsource':
- $this->formsource = (string) $value;
-
- // Add root path if we have a path to XML file
- if (strrpos($this->formsource, '.xml') === \strlen($this->formsource) - 4)
- {
- $this->formsource = Path::clean(JPATH_ROOT . '/' . $this->formsource);
- }
-
- break;
-
- case 'min':
- $this->min = (int) $value;
- break;
-
- case 'max':
- if ($value)
- {
- $this->max = max(1, (int) $value);
- }
- break;
-
- case 'groupByFieldset':
- if ($value !== null)
- {
- $value = (string) $value;
- $this->groupByFieldset = !($value === 'false' || $value === 'off' || $value === '0');
- }
- break;
-
- case 'layout':
- $this->layout = (string) $value;
-
- // Make sure the layout is not empty.
- if (!$this->layout)
- {
- // Set default value depend from "multiple" mode
- $this->layout = !$this->multiple ? 'joomla.form.field.subform.default' : 'joomla.form.field.subform.repeatable';
- }
-
- break;
-
- case 'buttons':
-
- if (!$this->multiple)
- {
- $this->buttons = array();
- break;
- }
-
- if ($value && !\is_array($value))
- {
- $value = explode(',', (string) $value);
- $value = array_fill_keys(array_filter($value), true);
- }
-
- if ($value)
- {
- $value = array_merge(array('add' => false, 'remove' => false, 'move' => false), $value);
- $this->buttons = $value;
- }
-
- break;
-
- case 'value':
- // We allow a json encoded string or an array
- if (is_string($value))
- {
- $value = json_decode($value, true);
- }
-
- $this->value = $value !== null ? (array) $value : null;
-
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value.
- *
- * @return boolean True on success.
- *
- * @since 3.6
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- if (!parent::setup($element, $value, $group))
- {
- return false;
- }
-
- foreach (array('formsource', 'min', 'max', 'layout', 'groupByFieldset', 'buttons') as $attributeName)
- {
- $this->__set($attributeName, $element[$attributeName]);
- }
-
- if ((string) $element['fieldname'])
- {
- $this->__set('fieldname', $element['fieldname']);
- }
-
- if ($this->value && \is_string($this->value))
- {
- // Guess here is the JSON string from 'default' attribute
- $this->value = json_decode($this->value, true);
- }
-
- if (!$this->formsource && $element->form)
- {
- // Set the formsource parameter from the content of the node
- $this->formsource = $element->form->saveXML();
- }
-
- return true;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 3.6
- */
- protected function getInput()
- {
- // Prepare data for renderer
- $data = $this->getLayoutData();
- $tmpl = null;
- $control = $this->name;
-
- try
- {
- $tmpl = $this->loadSubForm();
- $forms = $this->loadSubFormData($tmpl);
- }
- catch (\Exception $e)
- {
- return $e->getMessage();
- }
-
- $data['tmpl'] = $tmpl;
- $data['forms'] = $forms;
- $data['min'] = $this->min;
- $data['max'] = $this->max;
- $data['control'] = $control;
- $data['buttons'] = $this->buttons;
- $data['fieldname'] = $this->fieldname;
- $data['fieldId'] = $this->id;
- $data['groupByFieldset'] = $this->groupByFieldset;
-
- /**
- * For each rendering process of a subform element, we want to have a
- * separate unique subform id present to could distinguish the eventhandlers
- * regarding adding/moving/removing rows from nested subforms from their parents.
- */
- static $unique_subform_id = 0;
- $data['unique_subform_id'] = ('sr-' . ($unique_subform_id++));
-
- // Prepare renderer
- $renderer = $this->getRenderer($this->layout);
-
- // Allow to define some Layout options as attribute of the element
- if ($this->element['component'])
- {
- $renderer->setComponent((string) $this->element['component']);
- }
-
- if ($this->element['client'])
- {
- $renderer->setClient((string) $this->element['client']);
- }
-
- // Render
- $html = $renderer->render($data);
-
- // Add hidden input on front of the subform inputs, in multiple mode
- // for allow to submit an empty value
- if ($this->multiple)
- {
- $html = ' ' . $html;
- }
-
- return $html;
- }
-
- /**
- * Method to get the name used for the field input tag.
- *
- * @param string $fieldName The field element name.
- *
- * @return string The name to be used for the field input tag.
- *
- * @since 3.6
- */
- protected function getName($fieldName)
- {
- $name = '';
-
- // If there is a form control set for the attached form add it first.
- if ($this->formControl)
- {
- $name .= $this->formControl;
- }
-
- // If the field is in a group add the group control to the field name.
- if ($this->group)
- {
- // If we already have a name segment add the group control as another level.
- $groups = explode('.', $this->group);
-
- if ($name)
- {
- foreach ($groups as $group)
- {
- $name .= '[' . $group . ']';
- }
- }
- else
- {
- $name .= array_shift($groups);
-
- foreach ($groups as $group)
- {
- $name .= '[' . $group . ']';
- }
- }
- }
-
- // If we already have a name segment add the field name as another level.
- if ($name)
- {
- $name .= '[' . $fieldName . ']';
- }
- else
- {
- $name .= $fieldName;
- }
-
- return $name;
- }
-
- /**
- * Loads the form instance for the subform.
- *
- * @return Form The form instance.
- *
- * @throws \InvalidArgumentException if no form provided.
- * @throws \RuntimeException if the form could not be loaded.
- *
- * @since 3.9.7
- */
- public function loadSubForm()
- {
- $control = $this->name;
-
- if ($this->multiple)
- {
- $control .= '[' . $this->fieldname . 'X]';
- }
-
- // Prepare the form template
- $formname = 'subform.' . str_replace(array('jform[', '[', ']'), array('', '.', ''), $this->name);
- $tmpl = Form::getInstance($formname, $this->formsource, array('control' => $control));
-
- return $tmpl;
- }
-
- /**
- * Binds given data to the subform and its elements.
- *
- * @param Form $subForm Form instance of the subform.
- *
- * @return Form[] Array of Form instances for the rows.
- *
- * @since 3.9.7
- */
- protected function loadSubFormData(Form $subForm)
- {
- $value = $this->value ? (array) $this->value : array();
-
- // Simple form, just bind the data and return one row.
- if (!$this->multiple)
- {
- $subForm->bind($value);
-
- return array($subForm);
- }
-
- // Multiple rows possible: Construct array and bind values to their respective forms.
- $forms = array();
- $value = array_values($value);
-
- // Show as many rows as we have values, but at least min and at most max.
- $c = max($this->min, min(\count($value), $this->max));
-
- for ($i = 0; $i < $c; $i++)
- {
- $control = $this->name . '[' . $this->fieldname . $i . ']';
- $itemForm = Form::getInstance($subForm->getName() . $i, $this->formsource, array('control' => $control));
-
- if (!empty($value[$i]))
- {
- $itemForm->bind($value[$i]);
- }
-
- $forms[] = $itemForm;
- }
-
- return $forms;
- }
-
- /**
- * Method to filter a field value.
- *
- * @param mixed $value The optional value to use as the default for the field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param Registry $input An optional Registry object with the entire data set to filter
- * against the entire form.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- * @throws \UnexpectedValueException
- */
- public function filter($value, $group = null, Registry $input = null)
- {
- // Make sure there is a valid SimpleXMLElement.
- if (!($this->element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
- }
-
- // Get the field filter type.
- $filter = (string) $this->element['filter'];
-
- if ($filter !== '')
- {
- return parent::filter($value, $group, $input);
- }
-
- // Dirty way of ensuring required fields in subforms are submitted and filtered the way other fields are
- $subForm = $this->loadSubForm();
-
- // Subform field may have a default value, that is a JSON string
- if ($value && is_string($value))
- {
- $value = json_decode($value, true);
-
- // The string is invalid json
- if (!$value)
- {
- return null;
- }
- }
-
- if ($this->multiple)
- {
- $return = [];
-
- if ($value)
- {
- foreach ($value as $key => $val)
- {
- $return[$key] = $subForm->filter($val);
- }
- }
- }
- else
- {
- $return = $subForm->filter($value);
- }
-
- return $return;
- }
+ /**
+ * The form field type.
+ * @var string
+ */
+ protected $type = 'Subform';
+
+ /**
+ * Form source
+ * @var string
+ */
+ protected $formsource;
+
+ /**
+ * Minimum items in repeat mode
+ * @var integer
+ */
+ protected $min = 0;
+
+ /**
+ * Maximum items in repeat mode
+ * @var integer
+ */
+ protected $max = 1000;
+
+ /**
+ * Layout to render the form
+ * @var string
+ */
+ protected $layout = 'joomla.form.field.subform.default';
+
+ /**
+ * Whether group subform fields by it`s fieldset
+ * @var boolean
+ */
+ protected $groupByFieldset = false;
+
+ /**
+ * Which buttons to show in multiple mode
+ * @var array $buttons
+ */
+ protected $buttons = array('add' => true, 'remove' => true, 'move' => true);
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.6
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'formsource':
+ case 'min':
+ case 'max':
+ case 'layout':
+ case 'groupByFieldset':
+ case 'buttons':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.6
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'formsource':
+ $this->formsource = (string) $value;
+
+ // Add root path if we have a path to XML file
+ if (strrpos($this->formsource, '.xml') === \strlen($this->formsource) - 4) {
+ $this->formsource = Path::clean(JPATH_ROOT . '/' . $this->formsource);
+ }
+
+ break;
+
+ case 'min':
+ $this->min = (int) $value;
+ break;
+
+ case 'max':
+ if ($value) {
+ $this->max = max(1, (int) $value);
+ }
+ break;
+
+ case 'groupByFieldset':
+ if ($value !== null) {
+ $value = (string) $value;
+ $this->groupByFieldset = !($value === 'false' || $value === 'off' || $value === '0');
+ }
+ break;
+
+ case 'layout':
+ $this->layout = (string) $value;
+
+ // Make sure the layout is not empty.
+ if (!$this->layout) {
+ // Set default value depend from "multiple" mode
+ $this->layout = !$this->multiple ? 'joomla.form.field.subform.default' : 'joomla.form.field.subform.repeatable';
+ }
+
+ break;
+
+ case 'buttons':
+ if (!$this->multiple) {
+ $this->buttons = array();
+ break;
+ }
+
+ if ($value && !\is_array($value)) {
+ $value = explode(',', (string) $value);
+ $value = array_fill_keys(array_filter($value), true);
+ }
+
+ if ($value) {
+ $value = array_merge(array('add' => false, 'remove' => false, 'move' => false), $value);
+ $this->buttons = $value;
+ }
+
+ break;
+
+ case 'value':
+ // We allow a json encoded string or an array
+ if (is_string($value)) {
+ $value = json_decode($value, true);
+ }
+
+ $this->value = $value !== null ? (array) $value : null;
+
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value.
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.6
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ if (!parent::setup($element, $value, $group)) {
+ return false;
+ }
+
+ foreach (array('formsource', 'min', 'max', 'layout', 'groupByFieldset', 'buttons') as $attributeName) {
+ $this->__set($attributeName, $element[$attributeName]);
+ }
+
+ if ((string) $element['fieldname']) {
+ $this->__set('fieldname', $element['fieldname']);
+ }
+
+ if ($this->value && \is_string($this->value)) {
+ // Guess here is the JSON string from 'default' attribute
+ $this->value = json_decode($this->value, true);
+ }
+
+ if (!$this->formsource && $element->form) {
+ // Set the formsource parameter from the content of the node
+ $this->formsource = $element->form->saveXML();
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.6
+ */
+ protected function getInput()
+ {
+ // Prepare data for renderer
+ $data = $this->getLayoutData();
+ $tmpl = null;
+ $control = $this->name;
+
+ try {
+ $tmpl = $this->loadSubForm();
+ $forms = $this->loadSubFormData($tmpl);
+ } catch (\Exception $e) {
+ return $e->getMessage();
+ }
+
+ $data['tmpl'] = $tmpl;
+ $data['forms'] = $forms;
+ $data['min'] = $this->min;
+ $data['max'] = $this->max;
+ $data['control'] = $control;
+ $data['buttons'] = $this->buttons;
+ $data['fieldname'] = $this->fieldname;
+ $data['fieldId'] = $this->id;
+ $data['groupByFieldset'] = $this->groupByFieldset;
+
+ /**
+ * For each rendering process of a subform element, we want to have a
+ * separate unique subform id present to could distinguish the eventhandlers
+ * regarding adding/moving/removing rows from nested subforms from their parents.
+ */
+ static $unique_subform_id = 0;
+ $data['unique_subform_id'] = ('sr-' . ($unique_subform_id++));
+
+ // Prepare renderer
+ $renderer = $this->getRenderer($this->layout);
+
+ // Allow to define some Layout options as attribute of the element
+ if ($this->element['component']) {
+ $renderer->setComponent((string) $this->element['component']);
+ }
+
+ if ($this->element['client']) {
+ $renderer->setClient((string) $this->element['client']);
+ }
+
+ // Render
+ $html = $renderer->render($data);
+
+ // Add hidden input on front of the subform inputs, in multiple mode
+ // for allow to submit an empty value
+ if ($this->multiple) {
+ $html = ' ' . $html;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Method to get the name used for the field input tag.
+ *
+ * @param string $fieldName The field element name.
+ *
+ * @return string The name to be used for the field input tag.
+ *
+ * @since 3.6
+ */
+ protected function getName($fieldName)
+ {
+ $name = '';
+
+ // If there is a form control set for the attached form add it first.
+ if ($this->formControl) {
+ $name .= $this->formControl;
+ }
+
+ // If the field is in a group add the group control to the field name.
+ if ($this->group) {
+ // If we already have a name segment add the group control as another level.
+ $groups = explode('.', $this->group);
+
+ if ($name) {
+ foreach ($groups as $group) {
+ $name .= '[' . $group . ']';
+ }
+ } else {
+ $name .= array_shift($groups);
+
+ foreach ($groups as $group) {
+ $name .= '[' . $group . ']';
+ }
+ }
+ }
+
+ // If we already have a name segment add the field name as another level.
+ if ($name) {
+ $name .= '[' . $fieldName . ']';
+ } else {
+ $name .= $fieldName;
+ }
+
+ return $name;
+ }
+
+ /**
+ * Loads the form instance for the subform.
+ *
+ * @return Form The form instance.
+ *
+ * @throws \InvalidArgumentException if no form provided.
+ * @throws \RuntimeException if the form could not be loaded.
+ *
+ * @since 3.9.7
+ */
+ public function loadSubForm()
+ {
+ $control = $this->name;
+
+ if ($this->multiple) {
+ $control .= '[' . $this->fieldname . 'X]';
+ }
+
+ // Prepare the form template
+ $formname = 'subform.' . str_replace(array('jform[', '[', ']'), array('', '.', ''), $this->name);
+ $tmpl = Form::getInstance($formname, $this->formsource, array('control' => $control));
+
+ return $tmpl;
+ }
+
+ /**
+ * Binds given data to the subform and its elements.
+ *
+ * @param Form $subForm Form instance of the subform.
+ *
+ * @return Form[] Array of Form instances for the rows.
+ *
+ * @since 3.9.7
+ */
+ protected function loadSubFormData(Form $subForm)
+ {
+ $value = $this->value ? (array) $this->value : array();
+
+ // Simple form, just bind the data and return one row.
+ if (!$this->multiple) {
+ $subForm->bind($value);
+
+ return array($subForm);
+ }
+
+ // Multiple rows possible: Construct array and bind values to their respective forms.
+ $forms = array();
+ $value = array_values($value);
+
+ // Show as many rows as we have values, but at least min and at most max.
+ $c = max($this->min, min(\count($value), $this->max));
+
+ for ($i = 0; $i < $c; $i++) {
+ $control = $this->name . '[' . $this->fieldname . $i . ']';
+ $itemForm = Form::getInstance($subForm->getName() . $i, $this->formsource, array('control' => $control));
+
+ if (!empty($value[$i])) {
+ $itemForm->bind($value[$i]);
+ }
+
+ $forms[] = $itemForm;
+ }
+
+ return $forms;
+ }
+
+ /**
+ * Method to filter a field value.
+ *
+ * @param mixed $value The optional value to use as the default for the field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param Registry $input An optional Registry object with the entire data set to filter
+ * against the entire form.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ * @throws \UnexpectedValueException
+ */
+ public function filter($value, $group = null, Registry $input = null)
+ {
+ // Make sure there is a valid SimpleXMLElement.
+ if (!($this->element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
+ }
+
+ // Get the field filter type.
+ $filter = (string) $this->element['filter'];
+
+ if ($filter !== '') {
+ return parent::filter($value, $group, $input);
+ }
+
+ // Dirty way of ensuring required fields in subforms are submitted and filtered the way other fields are
+ $subForm = $this->loadSubForm();
+
+ // Subform field may have a default value, that is a JSON string
+ if ($value && is_string($value)) {
+ $value = json_decode($value, true);
+
+ // The string is invalid json
+ if (!$value) {
+ return null;
+ }
+ }
+
+ if ($this->multiple) {
+ $return = [];
+
+ if ($value) {
+ foreach ($value as $key => $val) {
+ $return[$key] = $subForm->filter($val);
+ }
+ }
+ } else {
+ $return = $subForm->filter($value);
+ }
+
+ return $return;
+ }
}
diff --git a/libraries/src/Form/Field/TagField.php b/libraries/src/Form/Field/TagField.php
index 50adf5d78fe7a..ac7ad727c9446 100644
--- a/libraries/src/Form/Field/TagField.php
+++ b/libraries/src/Form/Field/TagField.php
@@ -1,4 +1,5 @@
comParams = ComponentHelper::getParams('com_tags');
- }
-
- /**
- * Method to get the field input for a tag field.
- *
- * @return string The field input.
- *
- * @since 3.1
- */
- protected function getInput()
- {
- $data = $this->getLayoutData();
-
- if (!\is_array($this->value) && !empty($this->value))
- {
- if ($this->value instanceof TagsHelper)
- {
- if (empty($this->value->tags))
- {
- $this->value = array();
- }
- else
- {
- $this->value = $this->value->tags;
- }
- }
-
- // String in format 2,5,4
- if (\is_string($this->value))
- {
- $this->value = explode(',', $this->value);
- }
-
- // Integer is given
- if (\is_int($this->value))
- {
- $this->value = array($this->value);
- }
-
- $data['value'] = $this->value;
- }
-
- $data['remoteSearch'] = $this->isRemoteSearch();
- $data['options'] = $this->getOptions();
- $data['isNested'] = $this->isNested();
- $data['allowCustom'] = $this->allowCustom();
- $data['minTermLength'] = (int) $this->comParams->get('min_term_length', 3);
-
- return $this->getRenderer($this->layout)->render($data);
- }
-
- /**
- * Method to get a list of tags
- *
- * @return array The field option objects.
- *
- * @since 3.1
- */
- protected function getOptions()
- {
- $published = (string) $this->element['published'] ?: array(0, 1);
- $app = Factory::getApplication();
- $language = null;
- $options = [];
-
- // This limit is only used with isRemoteSearch
- $prefillLimit = 30;
- $isRemoteSearch = $this->isRemoteSearch();
-
- $db = $this->getDatabase();
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('a.id', 'value'),
- $db->quoteName('a.path'),
- $db->quoteName('a.title', 'text'),
- $db->quoteName('a.level'),
- $db->quoteName('a.published'),
- $db->quoteName('a.lft'),
- ]
- )
- ->from($db->quoteName('#__tags', 'a'));
-
- // Limit Options in multilanguage
- if ($app->isClient('site') && Multilanguage::isEnabled())
- {
- if (ComponentHelper::getParams('com_tags')->get('tag_list_language_filter') === 'current_language')
- {
- $language = [$app->getLanguage()->getTag(), '*'];
- }
- }
- // Filter language
- elseif (!empty($this->element['language']))
- {
- if (strpos($this->element['language'], ',') !== false)
- {
- $language = explode(',', $this->element['language']);
- }
- else
- {
- $language = [$this->element['language']];
- }
- }
-
- if ($language)
- {
- $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
- }
-
- $query->where($db->quoteName('a.lft') . ' > 0');
-
- // Filter on the published state
- if (is_numeric($published))
- {
- $published = (int) $published;
- $query->where($db->quoteName('a.published') . ' = :published')
- ->bind(':published', $published, ParameterType::INTEGER);
- }
- elseif (\is_array($published))
- {
- $published = ArrayHelper::toInteger($published);
- $query->whereIn($db->quoteName('a.published'), $published);
- }
-
- $query->order($db->quoteName('a.lft') . ' ASC');
-
- // Preload only active values and 30 most used tags or fill up
- if ($isRemoteSearch)
- {
- // Load the most $prefillLimit used tags
- $topQuery = $db->getQuery(true)
- ->select($db->quoteName('tag_id'))
- ->from($db->quoteName('#__contentitem_tag_map'))
- ->group($db->quoteName('tag_id'))
- ->order('count(*)')
- ->setLimit($prefillLimit);
-
- $db->setQuery($topQuery);
- $topIds = $db->loadColumn();
-
- // Merge the used values into the most used tags
- if (!empty($this->value) && is_array($this->value))
- {
- $topIds = array_merge($topIds, $this->value);
- $topIds = array_keys(array_flip($topIds));
- }
-
- // Set the default limit for the main query
- $query->setLimit($prefillLimit);
-
- if (!empty($topIds))
- {
- // Filter the ids to the most used tags and the selected tags
- $preQuery = clone $query;
- $preQuery->whereIn($db->quoteName('a.id'), $topIds);
-
- $db->setQuery($preQuery);
-
- try
- {
- $options = $db->loadObjectList();
- }
- catch (\RuntimeException $e)
- {
- return array();
- }
-
- // Limit the main query to the missing amount of tags
- $count = count($options);
- $prefillLimit = $prefillLimit - $count;
- $query->setLimit($prefillLimit);
-
- // Exclude the already loaded tags from the main query
- if ($count > 0)
- {
- $query->whereNotIn($db->quoteName('a.id'), ArrayHelper::getColumn($options, 'value'));
- }
- }
- }
-
- // Only execute the query if we need more tags not already loaded by the $preQuery query
- if (!$isRemoteSearch || $prefillLimit > 0)
- {
- // Get the options.
- $db->setQuery($query);
-
- try
- {
- $options = array_merge($options, $db->loadObjectList());
- }
- catch (\RuntimeException $e)
- {
- return array();
- }
- }
-
- // Block the possibility to set a tag as it own parent
- if ($this->form->getName() === 'com_tags.tag')
- {
- $id = (int) $this->form->getValue('id', 0);
-
- foreach ($options as $option)
- {
- if ($option->value == $id)
- {
- $option->disable = true;
- }
- }
- }
-
- // Merge any additional options in the XML definition.
- $options = array_merge(parent::getOptions(), $options);
-
- // Prepare nested data
- if ($this->isNested())
- {
- $this->prepareOptionsNested($options);
- }
- else
- {
- $options = TagsHelper::convertPathsToNames($options);
- }
-
- return $options;
- }
-
- /**
- * Add "-" before nested tags, depending on level
- *
- * @param array &$options Array of tags
- *
- * @return array The field option objects.
- *
- * @since 3.1
- */
- protected function prepareOptionsNested(&$options)
- {
- if ($options)
- {
- foreach ($options as &$option)
- {
- $repeat = (isset($option->level) && $option->level - 1 >= 0) ? $option->level - 1 : 0;
- $option->text = str_repeat('- ', $repeat) . $option->text;
- }
- }
-
- return $options;
- }
-
- /**
- * Determine if the field has to be tagnested
- *
- * @return boolean
- *
- * @since 3.1
- */
- public function isNested()
- {
- if ($this->isNested === null)
- {
- // If mode="nested" || ( mode not set & config = nested )
- if (isset($this->element['mode']) && (string) $this->element['mode'] === 'nested'
- || !isset($this->element['mode']) && $this->comParams->get('tag_field_ajax_mode', 1) == 0)
- {
- $this->isNested = true;
- }
- }
-
- return $this->isNested;
- }
-
- /**
- * Determines if the field allows or denies custom values
- *
- * @return boolean
- */
- public function allowCustom()
- {
- if ($this->element['custom'] && \in_array((string) $this->element['custom'], array('0', 'false', 'deny')))
- {
- return false;
- }
-
- return Factory::getUser()->authorise('core.create', 'com_tags');
- }
-
- /**
- * Check whether need to enable AJAX search
- *
- * @return boolean
- *
- * @since 4.0.0
- */
- public function isRemoteSearch()
- {
- if ($this->element['remote-search'])
- {
- return !\in_array((string) $this->element['remote-search'], array('0', 'false', ''));
- }
-
- return $this->comParams->get('tag_field_ajax_mode', 1) == 1;
- }
+ /**
+ * A flexible tag list that respects access controls
+ *
+ * @var string
+ * @since 3.1
+ */
+ public $type = 'Tag';
+
+ /**
+ * Flag to work with nested tag field
+ *
+ * @var boolean
+ * @since 3.1
+ */
+ public $isNested = null;
+
+ /**
+ * com_tags parameters
+ *
+ * @var \Joomla\Registry\Registry
+ * @since 3.1
+ */
+ protected $comParams = null;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.tag';
+
+ /**
+ * Constructor
+ *
+ * @since 3.1
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ // Load com_tags config
+ $this->comParams = ComponentHelper::getParams('com_tags');
+ }
+
+ /**
+ * Method to get the field input for a tag field.
+ *
+ * @return string The field input.
+ *
+ * @since 3.1
+ */
+ protected function getInput()
+ {
+ $data = $this->getLayoutData();
+
+ if (!\is_array($this->value) && !empty($this->value)) {
+ if ($this->value instanceof TagsHelper) {
+ if (empty($this->value->tags)) {
+ $this->value = array();
+ } else {
+ $this->value = $this->value->tags;
+ }
+ }
+
+ // String in format 2,5,4
+ if (\is_string($this->value)) {
+ $this->value = explode(',', $this->value);
+ }
+
+ // Integer is given
+ if (\is_int($this->value)) {
+ $this->value = array($this->value);
+ }
+
+ $data['value'] = $this->value;
+ }
+
+ $data['remoteSearch'] = $this->isRemoteSearch();
+ $data['options'] = $this->getOptions();
+ $data['isNested'] = $this->isNested();
+ $data['allowCustom'] = $this->allowCustom();
+ $data['minTermLength'] = (int) $this->comParams->get('min_term_length', 3);
+
+ return $this->getRenderer($this->layout)->render($data);
+ }
+
+ /**
+ * Method to get a list of tags
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.1
+ */
+ protected function getOptions()
+ {
+ $published = (string) $this->element['published'] ?: array(0, 1);
+ $app = Factory::getApplication();
+ $language = null;
+ $options = [];
+
+ // This limit is only used with isRemoteSearch
+ $prefillLimit = 30;
+ $isRemoteSearch = $this->isRemoteSearch();
+
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('a.id', 'value'),
+ $db->quoteName('a.path'),
+ $db->quoteName('a.title', 'text'),
+ $db->quoteName('a.level'),
+ $db->quoteName('a.published'),
+ $db->quoteName('a.lft'),
+ ]
+ )
+ ->from($db->quoteName('#__tags', 'a'));
+
+ // Limit Options in multilanguage
+ if ($app->isClient('site') && Multilanguage::isEnabled()) {
+ if (ComponentHelper::getParams('com_tags')->get('tag_list_language_filter') === 'current_language') {
+ $language = [$app->getLanguage()->getTag(), '*'];
+ }
+ } elseif (!empty($this->element['language'])) {
+ // Filter language
+ if (strpos($this->element['language'], ',') !== false) {
+ $language = explode(',', $this->element['language']);
+ } else {
+ $language = [$this->element['language']];
+ }
+ }
+
+ if ($language) {
+ $query->whereIn($db->quoteName('a.language'), $language, ParameterType::STRING);
+ }
+
+ $query->where($db->quoteName('a.lft') . ' > 0');
+
+ // Filter on the published state
+ if (is_numeric($published)) {
+ $published = (int) $published;
+ $query->where($db->quoteName('a.published') . ' = :published')
+ ->bind(':published', $published, ParameterType::INTEGER);
+ } elseif (\is_array($published)) {
+ $published = ArrayHelper::toInteger($published);
+ $query->whereIn($db->quoteName('a.published'), $published);
+ }
+
+ $query->order($db->quoteName('a.lft') . ' ASC');
+
+ // Preload only active values and 30 most used tags or fill up
+ if ($isRemoteSearch) {
+ // Load the most $prefillLimit used tags
+ $topQuery = $db->getQuery(true)
+ ->select($db->quoteName('tag_id'))
+ ->from($db->quoteName('#__contentitem_tag_map'))
+ ->group($db->quoteName('tag_id'))
+ ->order('count(*)')
+ ->setLimit($prefillLimit);
+
+ $db->setQuery($topQuery);
+ $topIds = $db->loadColumn();
+
+ // Merge the used values into the most used tags
+ if (!empty($this->value) && is_array($this->value)) {
+ $topIds = array_merge($topIds, $this->value);
+ $topIds = array_keys(array_flip($topIds));
+ }
+
+ // Set the default limit for the main query
+ $query->setLimit($prefillLimit);
+
+ if (!empty($topIds)) {
+ // Filter the ids to the most used tags and the selected tags
+ $preQuery = clone $query;
+ $preQuery->whereIn($db->quoteName('a.id'), $topIds);
+
+ $db->setQuery($preQuery);
+
+ try {
+ $options = $db->loadObjectList();
+ } catch (\RuntimeException $e) {
+ return array();
+ }
+
+ // Limit the main query to the missing amount of tags
+ $count = count($options);
+ $prefillLimit = $prefillLimit - $count;
+ $query->setLimit($prefillLimit);
+
+ // Exclude the already loaded tags from the main query
+ if ($count > 0) {
+ $query->whereNotIn($db->quoteName('a.id'), ArrayHelper::getColumn($options, 'value'));
+ }
+ }
+ }
+
+ // Only execute the query if we need more tags not already loaded by the $preQuery query
+ if (!$isRemoteSearch || $prefillLimit > 0) {
+ // Get the options.
+ $db->setQuery($query);
+
+ try {
+ $options = array_merge($options, $db->loadObjectList());
+ } catch (\RuntimeException $e) {
+ return array();
+ }
+ }
+
+ // Block the possibility to set a tag as it own parent
+ if ($this->form->getName() === 'com_tags.tag') {
+ $id = (int) $this->form->getValue('id', 0);
+
+ foreach ($options as $option) {
+ if ($option->value == $id) {
+ $option->disable = true;
+ }
+ }
+ }
+
+ // Merge any additional options in the XML definition.
+ $options = array_merge(parent::getOptions(), $options);
+
+ // Prepare nested data
+ if ($this->isNested()) {
+ $this->prepareOptionsNested($options);
+ } else {
+ $options = TagsHelper::convertPathsToNames($options);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Add "-" before nested tags, depending on level
+ *
+ * @param array &$options Array of tags
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.1
+ */
+ protected function prepareOptionsNested(&$options)
+ {
+ if ($options) {
+ foreach ($options as &$option) {
+ $repeat = (isset($option->level) && $option->level - 1 >= 0) ? $option->level - 1 : 0;
+ $option->text = str_repeat('- ', $repeat) . $option->text;
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Determine if the field has to be tagnested
+ *
+ * @return boolean
+ *
+ * @since 3.1
+ */
+ public function isNested()
+ {
+ if ($this->isNested === null) {
+ // If mode="nested" || ( mode not set & config = nested )
+ if (
+ isset($this->element['mode']) && (string) $this->element['mode'] === 'nested'
+ || !isset($this->element['mode']) && $this->comParams->get('tag_field_ajax_mode', 1) == 0
+ ) {
+ $this->isNested = true;
+ }
+ }
+
+ return $this->isNested;
+ }
+
+ /**
+ * Determines if the field allows or denies custom values
+ *
+ * @return boolean
+ */
+ public function allowCustom()
+ {
+ if ($this->element['custom'] && \in_array((string) $this->element['custom'], array('0', 'false', 'deny'))) {
+ return false;
+ }
+
+ return Factory::getUser()->authorise('core.create', 'com_tags');
+ }
+
+ /**
+ * Check whether need to enable AJAX search
+ *
+ * @return boolean
+ *
+ * @since 4.0.0
+ */
+ public function isRemoteSearch()
+ {
+ if ($this->element['remote-search']) {
+ return !\in_array((string) $this->element['remote-search'], array('0', 'false', ''));
+ }
+
+ return $this->comParams->get('tag_field_ajax_mode', 1) == 1;
+ }
}
diff --git a/libraries/src/Form/Field/TelephoneField.php b/libraries/src/Form/Field/TelephoneField.php
index 07ae47a865007..7d828f5ff5a27 100644
--- a/libraries/src/Form/Field/TelephoneField.php
+++ b/libraries/src/Form/Field/TelephoneField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.2
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- // Initialize some field attributes.
- $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
+ // Initialize some field attributes.
+ $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
- $extraData = array(
- 'maxLength' => $maxLength,
- );
+ $extraData = array(
+ 'maxLength' => $maxLength,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/TemplatestyleField.php b/libraries/src/Form/Field/TemplatestyleField.php
index 2b09c4d8f7343..c89e4fcf7445a 100644
--- a/libraries/src/Form/Field/TemplatestyleField.php
+++ b/libraries/src/Form/Field/TemplatestyleField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'clientName':
- case 'template':
- $this->$name = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result === true)
- {
- // Get the clientName template.
- $this->clientName = $this->element['client'] ? (string) $this->element['client'] : 'site';
- $this->template = (string) $this->element['template'];
- }
-
- return $result;
- }
-
- /**
- * Method to get the list of template style options grouped by template.
- * Use the client attribute to specify a specific client.
- * Use the template attribute to specify a specific template
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 1.6
- */
- protected function getGroups()
- {
- $groups = array();
- $lang = Factory::getLanguage();
-
- // Get the client and client_id.
- $client = ApplicationHelper::getClientInfo($this->clientName, true);
-
- // Get the template.
- $template = $this->template;
-
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Build the query.
- $query->select(
- [
- $db->quoteName('s.id'),
- $db->quoteName('s.title'),
- $db->quoteName('e.name'),
- $db->quoteName('s.template'),
- ]
- )
- ->from($db->quoteName('#__template_styles', 's'))
- ->where($db->quoteName('s.client_id') . ' = :clientId')
- ->bind(':clientId', $client->id, ParameterType::INTEGER)
- ->order(
- [
- $db->quoteName('template'),
- $db->quoteName('title'),
- ]
- );
-
- if ($template)
- {
- $query->where('s.template = ' . $db->quote($template));
- }
-
- $query->join('LEFT', '#__extensions as e on e.element=s.template')
- ->where('e.enabled = 1')
- ->where($db->quoteName('e.type') . ' = ' . $db->quote('template'));
-
- // Set the query and load the styles.
- $db->setQuery($query);
- $styles = $db->loadObjectList();
-
- // Build the grouped list array.
- if ($styles)
- {
- foreach ($styles as $style)
- {
- $template = $style->template;
- $lang->load('tpl_' . $template . '.sys', $client->path)
- || $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template);
- $name = Text::_($style->name);
-
- // Initialize the group if necessary.
- if (!isset($groups[$name]))
- {
- $groups[$name] = array();
- }
-
- $groups[$name][] = HTMLHelper::_('select.option', $style->id, $style->title);
- }
- }
-
- // Merge any additional groups in the XML definition.
- $groups = array_merge(parent::getGroups(), $groups);
-
- return $groups;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'TemplateStyle';
+
+ /**
+ * The client name.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $clientName;
+
+ /**
+ * The template.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $template;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'clientName':
+ case 'template':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'clientName':
+ case 'template':
+ $this->$name = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result === true) {
+ // Get the clientName template.
+ $this->clientName = $this->element['client'] ? (string) $this->element['client'] : 'site';
+ $this->template = (string) $this->element['template'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the list of template style options grouped by template.
+ * Use the client attribute to specify a specific client.
+ * Use the template attribute to specify a specific template
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 1.6
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+ $lang = Factory::getLanguage();
+
+ // Get the client and client_id.
+ $client = ApplicationHelper::getClientInfo($this->clientName, true);
+
+ // Get the template.
+ $template = $this->template;
+
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Build the query.
+ $query->select(
+ [
+ $db->quoteName('s.id'),
+ $db->quoteName('s.title'),
+ $db->quoteName('e.name'),
+ $db->quoteName('s.template'),
+ ]
+ )
+ ->from($db->quoteName('#__template_styles', 's'))
+ ->where($db->quoteName('s.client_id') . ' = :clientId')
+ ->bind(':clientId', $client->id, ParameterType::INTEGER)
+ ->order(
+ [
+ $db->quoteName('template'),
+ $db->quoteName('title'),
+ ]
+ );
+
+ if ($template) {
+ $query->where('s.template = ' . $db->quote($template));
+ }
+
+ $query->join('LEFT', '#__extensions as e on e.element=s.template')
+ ->where('e.enabled = 1')
+ ->where($db->quoteName('e.type') . ' = ' . $db->quote('template'));
+
+ // Set the query and load the styles.
+ $db->setQuery($query);
+ $styles = $db->loadObjectList();
+
+ // Build the grouped list array.
+ if ($styles) {
+ foreach ($styles as $style) {
+ $template = $style->template;
+ $lang->load('tpl_' . $template . '.sys', $client->path)
+ || $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template);
+ $name = Text::_($style->name);
+
+ // Initialize the group if necessary.
+ if (!isset($groups[$name])) {
+ $groups[$name] = array();
+ }
+
+ $groups[$name][] = HTMLHelper::_('select.option', $style->id, $style->title);
+ }
+ }
+
+ // Merge any additional groups in the XML definition.
+ $groups = array_merge(parent::getGroups(), $groups);
+
+ return $groups;
+ }
}
diff --git a/libraries/src/Form/Field/TextField.php b/libraries/src/Form/Field/TextField.php
index 09dbc42b05030..46d77f1b21afe 100644
--- a/libraries/src/Form/Field/TextField.php
+++ b/libraries/src/Form/Field/TextField.php
@@ -1,13 +1,13 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
-namespace Joomla\CMS\Form\Field;
-\defined('JPATH_PLATFORM') or die;
+namespace Joomla\CMS\Form\Field;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
@@ -25,283 +25,274 @@
*/
class TextField extends FormField
{
- /**
- * The form field type.
- *
- * @var string
- * @since 1.7.0
- */
- protected $type = 'Text';
-
- /**
- * The allowable maxlength of the field.
- *
- * @var integer
- * @since 3.2
- */
- protected $maxLength;
-
- /**
- * The mode of input associated with the field.
- *
- * @var mixed
- * @since 3.2
- */
- protected $inputmode;
-
- /**
- * The name of the form field direction (ltr or rtl).
- *
- * @var string
- * @since 3.2
- */
- protected $dirname;
-
- /**
- * Input addon before
- *
- * @var string
- * @since 4.0.0
- */
- protected $addonBefore;
-
- /**
- * Input addon after
- *
- * @var string
- * @since 4.0.0
- */
- protected $addonAfter;
-
- /**
- * Name of the layout being used to render the field
- *
- * @var string
- * @since 3.7
- */
- protected $layout = 'joomla.form.field.text';
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 3.2
- */
- public function __get($name)
- {
- switch ($name)
- {
- case 'maxLength':
- case 'dirname':
- case 'addonBefore':
- case 'addonAfter':
- case 'inputmode':
- return $this->$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'maxLength':
- $this->maxLength = (int) $value;
- break;
-
- case 'dirname':
- $value = (string) $value;
- $this->dirname = ($value == $name || $value === 'true' || $value === '1');
- break;
-
- case 'inputmode':
- $this->inputmode = (string) $value;
- break;
-
- case 'addonBefore':
- $this->addonBefore = (string) $value;
- break;
-
- case 'addonAfter':
- $this->addonAfter = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result == true)
- {
- $inputmode = (string) $this->element['inputmode'];
- $dirname = (string) $this->element['dirname'];
-
- $this->inputmode = '';
- $inputmode = preg_replace('/\s+/', ' ', trim($inputmode));
- $inputmode = explode(' ', $inputmode);
-
- if (!empty($inputmode))
- {
- $defaultInputmode = \in_array('default', $inputmode) ? Text::_('JLIB_FORM_INPUTMODE') . ' ' : '';
-
- foreach (array_keys($inputmode, 'default') as $key)
- {
- unset($inputmode[$key]);
- }
-
- $this->inputmode = $defaultInputmode . implode(' ', $inputmode);
- }
-
- // Set the dirname.
- $dirname = ($dirname === 'dirname' || $dirname === 'true' || $dirname === '1');
- $this->dirname = $dirname ? $this->getName($this->fieldname . '_dir') : false;
-
- $this->maxLength = (int) $this->element['maxlength'];
-
- $this->addonBefore = (string) $this->element['addonBefore'];
- $this->addonAfter = (string) $this->element['addonAfter'];
- }
-
- return $result;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- if ($this->element['useglobal'])
- {
- $component = Factory::getApplication()->input->getCmd('option');
-
- // Get correct component for menu items
- if ($component === 'com_menus')
- {
- $link = $this->form->getData()->get('link');
- $uri = new Uri($link);
- $component = $uri->getVar('option', 'com_menus');
- }
-
- $params = ComponentHelper::getParams($component);
- $value = $params->get($this->fieldname);
-
- // Try with global configuration
- if (\is_null($value))
- {
- $value = Factory::getApplication()->get($this->fieldname);
- }
-
- // Try with menu configuration
- if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus')
- {
- $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
- }
-
- if (!\is_null($value))
- {
- $value = (string) $value;
-
- $this->hint = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
- }
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 3.4
- */
- protected function getOptions()
- {
- $options = array();
-
- foreach ($this->element->children() as $option)
- {
- // Only add elements.
- if ($option->getName() !== 'option')
- {
- continue;
- }
-
- // Create a new option object based on the element.
- $options[] = HTMLHelper::_(
- 'select.option', (string) $option['value'],
- Text::alt(trim((string) $option), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)), 'value', 'text'
- );
- }
-
- return $options;
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // Initialize some field attributes.
- $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
- $inputmode = !empty($this->inputmode) ? ' inputmode="' . $this->inputmode . '"' : '';
- $dirname = !empty($this->dirname) ? ' dirname="' . $this->dirname . '"' : '';
-
- // Get the field options for the datalist.
- $options = (array) $this->getOptions();
-
- $extraData = array(
- 'maxLength' => $maxLength,
- 'pattern' => $this->pattern,
- 'inputmode' => $inputmode,
- 'dirname' => $dirname,
- 'addonBefore' => $this->addonBefore,
- 'addonAfter' => $this->addonAfter,
- 'options' => $options,
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Text';
+
+ /**
+ * The allowable maxlength of the field.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $maxLength;
+
+ /**
+ * The mode of input associated with the field.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $inputmode;
+
+ /**
+ * The name of the form field direction (ltr or rtl).
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $dirname;
+
+ /**
+ * Input addon before
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $addonBefore;
+
+ /**
+ * Input addon after
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $addonAfter;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7
+ */
+ protected $layout = 'joomla.form.field.text';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'maxLength':
+ case 'dirname':
+ case 'addonBefore':
+ case 'addonAfter':
+ case 'inputmode':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'maxLength':
+ $this->maxLength = (int) $value;
+ break;
+
+ case 'dirname':
+ $value = (string) $value;
+ $this->dirname = ($value == $name || $value === 'true' || $value === '1');
+ break;
+
+ case 'inputmode':
+ $this->inputmode = (string) $value;
+ break;
+
+ case 'addonBefore':
+ $this->addonBefore = (string) $value;
+ break;
+
+ case 'addonAfter':
+ $this->addonAfter = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result == true) {
+ $inputmode = (string) $this->element['inputmode'];
+ $dirname = (string) $this->element['dirname'];
+
+ $this->inputmode = '';
+ $inputmode = preg_replace('/\s+/', ' ', trim($inputmode));
+ $inputmode = explode(' ', $inputmode);
+
+ if (!empty($inputmode)) {
+ $defaultInputmode = \in_array('default', $inputmode) ? Text::_('JLIB_FORM_INPUTMODE') . ' ' : '';
+
+ foreach (array_keys($inputmode, 'default') as $key) {
+ unset($inputmode[$key]);
+ }
+
+ $this->inputmode = $defaultInputmode . implode(' ', $inputmode);
+ }
+
+ // Set the dirname.
+ $dirname = ($dirname === 'dirname' || $dirname === 'true' || $dirname === '1');
+ $this->dirname = $dirname ? $this->getName($this->fieldname . '_dir') : false;
+
+ $this->maxLength = (int) $this->element['maxlength'];
+
+ $this->addonBefore = (string) $this->element['addonBefore'];
+ $this->addonAfter = (string) $this->element['addonAfter'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ if ($this->element['useglobal']) {
+ $component = Factory::getApplication()->input->getCmd('option');
+
+ // Get correct component for menu items
+ if ($component === 'com_menus') {
+ $link = $this->form->getData()->get('link');
+ $uri = new Uri($link);
+ $component = $uri->getVar('option', 'com_menus');
+ }
+
+ $params = ComponentHelper::getParams($component);
+ $value = $params->get($this->fieldname);
+
+ // Try with global configuration
+ if (\is_null($value)) {
+ $value = Factory::getApplication()->get($this->fieldname);
+ }
+
+ // Try with menu configuration
+ if (\is_null($value) && Factory::getApplication()->input->getCmd('option') === 'com_menus') {
+ $value = ComponentHelper::getParams('com_menus')->get($this->fieldname);
+ }
+
+ if (!\is_null($value)) {
+ $value = (string) $value;
+
+ $this->hint = Text::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
+ }
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.4
+ */
+ protected function getOptions()
+ {
+ $options = array();
+
+ foreach ($this->element->children() as $option) {
+ // Only add elements.
+ if ($option->getName() !== 'option') {
+ continue;
+ }
+
+ // Create a new option object based on the element.
+ $options[] = HTMLHelper::_(
+ 'select.option',
+ (string) $option['value'],
+ Text::alt(trim((string) $option), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)),
+ 'value',
+ 'text'
+ );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // Initialize some field attributes.
+ $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
+ $inputmode = !empty($this->inputmode) ? ' inputmode="' . $this->inputmode . '"' : '';
+ $dirname = !empty($this->dirname) ? ' dirname="' . $this->dirname . '"' : '';
+
+ // Get the field options for the datalist.
+ $options = (array) $this->getOptions();
+
+ $extraData = array(
+ 'maxLength' => $maxLength,
+ 'pattern' => $this->pattern,
+ 'inputmode' => $inputmode,
+ 'dirname' => $dirname,
+ 'addonBefore' => $this->addonBefore,
+ 'addonAfter' => $this->addonAfter,
+ 'options' => $options,
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/TextareaField.php b/libraries/src/Form/Field/TextareaField.php
index 62a8faf28296f..1a83f6f08dd7f 100644
--- a/libraries/src/Form/Field/TextareaField.php
+++ b/libraries/src/Form/Field/TextareaField.php
@@ -1,4 +1,5 @@
$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'rows':
- case 'columns':
- case 'maxlength':
- $this->$name = (int) $value;
- break;
-
- case 'charcounter':
- $this->charcounter = strtolower($value) === 'true';
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->rows = isset($this->element['rows']) ? (int) $this->element['rows'] : false;
- $this->columns = isset($this->element['cols']) ? (int) $this->element['cols'] : false;
- $this->maxlength = isset($this->element['maxlength']) ? (int) $this->element['maxlength'] : false;
- $this->charcounter = isset($this->element['charcounter']) ? strtolower($this->element['charcounter']) === 'true' : false;
- }
-
- return $return;
- }
-
- /**
- * Method to get the textarea field input markup.
- * Use the rows and columns attributes to specify the dimensions of the area.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- // Trim the trailing line in the layout file
- return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- // Initialize some field attributes.
- $columns = $this->columns ? ' cols="' . $this->columns . '"' : '';
- $rows = $this->rows ? ' rows="' . $this->rows . '"' : '';
- $maxlength = $this->maxlength ? ' maxlength="' . $this->maxlength . '"' : '';
-
- $extraData = array(
- 'maxlength' => $maxlength,
- 'rows' => $rows,
- 'columns' => $columns,
- 'charcounter' => $this->charcounter
- );
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Textarea';
+
+ /**
+ * The number of rows in textarea.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $rows;
+
+ /**
+ * The number of columns in textarea.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $columns;
+
+ /**
+ * The maximum number of characters in textarea.
+ *
+ * @var mixed
+ * @since 3.4
+ */
+ protected $maxlength;
+
+ /**
+ * Does this field support a character counter?
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $charcounter = false;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.7
+ */
+ protected $layout = 'joomla.form.field.textarea';
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'rows':
+ case 'columns':
+ case 'maxlength':
+ case 'charcounter':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'rows':
+ case 'columns':
+ case 'maxlength':
+ $this->$name = (int) $value;
+ break;
+
+ case 'charcounter':
+ $this->charcounter = strtolower($value) === 'true';
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->rows = isset($this->element['rows']) ? (int) $this->element['rows'] : false;
+ $this->columns = isset($this->element['cols']) ? (int) $this->element['cols'] : false;
+ $this->maxlength = isset($this->element['maxlength']) ? (int) $this->element['maxlength'] : false;
+ $this->charcounter = isset($this->element['charcounter']) ? strtolower($this->element['charcounter']) === 'true' : false;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the textarea field input markup.
+ * Use the rows and columns attributes to specify the dimensions of the area.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ // Initialize some field attributes.
+ $columns = $this->columns ? ' cols="' . $this->columns . '"' : '';
+ $rows = $this->rows ? ' rows="' . $this->rows . '"' : '';
+ $maxlength = $this->maxlength ? ' maxlength="' . $this->maxlength . '"' : '';
+
+ $extraData = array(
+ 'maxlength' => $maxlength,
+ 'rows' => $rows,
+ 'columns' => $columns,
+ 'charcounter' => $this->charcounter
+ );
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/TimeField.php b/libraries/src/Form/Field/TimeField.php
index 1fb9f05d7b106..fc65be1213517 100644
--- a/libraries/src/Form/Field/TimeField.php
+++ b/libraries/src/Form/Field/TimeField.php
@@ -1,4 +1,5 @@
min = (string) $value;
- break;
-
- case 'max':
- $this->max = (string) $value;
- break;
-
- case 'step':
- $this->step = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @see FormField::setup()
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- // It is better not to force any default limits if none is specified
- $this->max = isset($this->element['max']) ? (string) $this->element['max'] : null;
- $this->min = isset($this->element['min']) ? (string) $this->element['min'] : null;
- $this->step = isset($this->element['step']) ? (float) $this->element['step'] : null;
- }
-
- return $return;
- }
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 4.0.0
- */
- public function __get($name)
- {
- switch ($name)
- {
- case 'min':
- case 'max':
- case 'step':
- return $this->$name;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 4.0.0
- */
- protected function getInput()
- {
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 4.0.0
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
-
- $extraData = [
- 'min' => $this->min,
- 'max' => $this->max,
- 'step' => $this->step,
- ];
-
- return array_merge($data, $extraData);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $type = 'Time';
+
+ /**
+ * The allowable minimal value of the field.
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $min;
+
+ /**
+ * The allowable maximal value of the field.
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $max;
+
+ /**
+ * Steps between different values
+ *
+ * @var integer
+ * @since 4.0.0
+ */
+ protected $step;
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $layout = 'joomla.form.field.time';
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 4.0.0
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'min':
+ $this->min = (string) $value;
+ break;
+
+ case 'max':
+ $this->max = (string) $value;
+ break;
+
+ case 'step':
+ $this->step = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @see FormField::setup()
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ // It is better not to force any default limits if none is specified
+ $this->max = isset($this->element['max']) ? (string) $this->element['max'] : null;
+ $this->min = isset($this->element['min']) ? (string) $this->element['min'] : null;
+ $this->step = isset($this->element['step']) ? (float) $this->element['step'] : null;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 4.0.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'min':
+ case 'max':
+ case 'step':
+ return $this->$name;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 4.0.0
+ */
+ protected function getInput()
+ {
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 4.0.0
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ $extraData = [
+ 'min' => $this->min,
+ 'max' => $this->max,
+ 'step' => $this->step,
+ ];
+
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/TimezoneField.php b/libraries/src/Form/Field/TimezoneField.php
index 0beb4030383cd..31071905ef8b4 100644
--- a/libraries/src/Form/Field/TimezoneField.php
+++ b/libraries/src/Form/Field/TimezoneField.php
@@ -1,4 +1,5 @@
keyField;
- }
-
- return parent::__get($name);
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'keyField':
- $this->keyField = (string) $value;
- break;
-
- default:
- parent::__set($name, $value);
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @see FormField::setup()
- * @since 3.2
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- if ($return)
- {
- $this->keyField = (string) $this->element['key_field'];
- }
-
- return $return;
- }
-
- /**
- * Method to get the time zone field option groups.
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 1.7.0
- */
- protected function getGroups()
- {
- $groups = array();
-
- // Get the list of time zones from the server.
- $zones = \DateTimeZone::listIdentifiers();
-
- // Build the group lists.
- foreach ($zones as $zone)
- {
- // Time zones not in a group we will ignore.
- if (strpos($zone, '/') === false)
- {
- continue;
- }
-
- // Get the group/locale from the timezone.
- list ($group, $locale) = explode('/', $zone, 2);
-
- // Only use known groups.
- if (\in_array($group, self::$zones))
- {
- // Initialize the group if necessary.
- if (!isset($groups[$group]))
- {
- $groups[$group] = array();
- }
-
- // Only add options where a locale exists.
- if (!empty($locale))
- {
- $groups[$group][$zone] = HTMLHelper::_('select.option', $zone, str_replace('_', ' ', $locale), 'value', 'text', false);
- }
- }
- }
-
- // Sort the group lists.
- ksort($groups);
-
- foreach ($groups as &$location)
- {
- sort($location);
- }
-
- // Merge any additional groups in the XML definition.
- $groups = array_merge(parent::getGroups(), $groups);
-
- return $groups;
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type = 'Timezone';
+
+ /**
+ * The list of available timezone groups to use.
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected static $zones = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific');
+
+ /**
+ * The keyField of timezone field.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $keyField;
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 3.2
+ */
+ public function __get($name)
+ {
+ if ($name === 'keyField') {
+ return $this->keyField;
+ }
+
+ return parent::__get($name);
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'keyField':
+ $this->keyField = (string) $value;
+ break;
+
+ default:
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @see FormField::setup()
+ * @since 3.2
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ if ($return) {
+ $this->keyField = (string) $this->element['key_field'];
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the time zone field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 1.7.0
+ */
+ protected function getGroups()
+ {
+ $groups = array();
+
+ // Get the list of time zones from the server.
+ $zones = \DateTimeZone::listIdentifiers();
+
+ // Build the group lists.
+ foreach ($zones as $zone) {
+ // Time zones not in a group we will ignore.
+ if (strpos($zone, '/') === false) {
+ continue;
+ }
+
+ // Get the group/locale from the timezone.
+ list ($group, $locale) = explode('/', $zone, 2);
+
+ // Only use known groups.
+ if (\in_array($group, self::$zones)) {
+ // Initialize the group if necessary.
+ if (!isset($groups[$group])) {
+ $groups[$group] = array();
+ }
+
+ // Only add options where a locale exists.
+ if (!empty($locale)) {
+ $groups[$group][$zone] = HTMLHelper::_('select.option', $zone, str_replace('_', ' ', $locale), 'value', 'text', false);
+ }
+ }
+ }
+
+ // Sort the group lists.
+ ksort($groups);
+
+ foreach ($groups as &$location) {
+ sort($location);
+ }
+
+ // Merge any additional groups in the XML definition.
+ $groups = array_merge(parent::getGroups(), $groups);
+
+ return $groups;
+ }
}
diff --git a/libraries/src/Form/Field/TransitionField.php b/libraries/src/Form/Field/TransitionField.php
index b3f87f871dda3..e1c04fb3bc15a 100644
--- a/libraries/src/Form/Field/TransitionField.php
+++ b/libraries/src/Form/Field/TransitionField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result)
- {
- $input = Factory::getApplication()->input;
-
- if (\strlen($element['extension']))
- {
- $this->extension = (string) $element['extension'];
- }
- else
- {
- $this->extension = $input->getCmd('extension');
- }
-
- if (\strlen($element['workflow_stage']))
- {
- $this->workflowStage = (int) $element['workflow_stage'];
- }
- else
- {
- $this->workflowStage = $input->getInt('id');
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get a list of options for a list input.
- *
- * @return array An array of HTMLHelper options.
- *
- * @since 4.0.0
- */
- protected function getOptions()
- {
- // Let's get the id for the current item, either category or content item.
- $jinput = Factory::getApplication()->input;
-
- // Initialise variable.
- $db = $this->getDatabase();
- $extension = $this->extension;
- $workflowStage = (int) $this->workflowStage;
-
- $query = $db->getQuery(true)
- ->select(
- [
- $db->quoteName('t.id', 'value'),
- $db->quoteName('t.title', 'text'),
- ]
- )
- ->from(
- [
- $db->quoteName('#__workflow_transitions', 't'),
- $db->quoteName('#__workflow_stages', 's'),
- $db->quoteName('#__workflow_stages', 's2'),
- ]
- )
- ->whereIn($db->quoteName('t.from_stage_id'), [-1, $workflowStage])
- ->where(
- [
- $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id'),
- $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('s2.workflow_id'),
- $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('t.workflow_id'),
- $db->quoteName('s2.id') . ' = :stage1',
- $db->quoteName('t.published') . ' = 1',
- $db->quoteName('s.published') . ' = 1',
- ]
- )
- ->bind(':stage1', $workflowStage, ParameterType::INTEGER)
- ->order($db->quoteName('t.ordering'));
-
- $items = $db->setQuery($query)->loadObjectList();
-
- Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
-
- $parts = explode('.', $extension);
-
- $component = reset($parts);
-
- if (\count($items))
- {
- $user = Factory::getUser();
-
- $items = array_filter(
- $items,
- function ($item) use ($user, $component)
- {
- return $user->authorise('core.execute.transition', $component . '.transition.' . $item->value);
- }
- );
-
- foreach ($items as $item)
- {
- $item->text = Text::_($item->text);
- }
- }
-
- // Get workflow stage title
- $query = $db->getQuery(true)
- ->select($db->quoteName('title'))
- ->from($db->quoteName('#__workflow_stages'))
- ->where($db->quoteName('id') . ' = :stage')
- ->bind(':stage', $workflowStage, ParameterType::INTEGER);
-
- $workflowName = $db->setQuery($query)->loadResult();
-
- $default = [HTMLHelper::_('select.option', '', Text::_($workflowName))];
-
- $options = array_merge(parent::getOptions(), $items);
-
- if (\count($options))
- {
- $default[] = HTMLHelper::_('select.option', '-1', '--------', ['disable' => true]);
- }
-
- // Merge with defaults
- return array_merge($default, $options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $type = 'Transition';
+
+ /**
+ * The component and section separated by ".".
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension;
+
+ /**
+ * The workflow stage to use.
+ *
+ * @var integer
+ */
+ protected $workflowStage;
+
+ /**
+ * Method to setup the extension
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result) {
+ $input = Factory::getApplication()->input;
+
+ if (\strlen($element['extension'])) {
+ $this->extension = (string) $element['extension'];
+ } else {
+ $this->extension = $input->getCmd('extension');
+ }
+
+ if (\strlen($element['workflow_stage'])) {
+ $this->workflowStage = (int) $element['workflow_stage'];
+ } else {
+ $this->workflowStage = $input->getInt('id');
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get a list of options for a list input.
+ *
+ * @return array An array of HTMLHelper options.
+ *
+ * @since 4.0.0
+ */
+ protected function getOptions()
+ {
+ // Let's get the id for the current item, either category or content item.
+ $jinput = Factory::getApplication()->input;
+
+ // Initialise variable.
+ $db = $this->getDatabase();
+ $extension = $this->extension;
+ $workflowStage = (int) $this->workflowStage;
+
+ $query = $db->getQuery(true)
+ ->select(
+ [
+ $db->quoteName('t.id', 'value'),
+ $db->quoteName('t.title', 'text'),
+ ]
+ )
+ ->from(
+ [
+ $db->quoteName('#__workflow_transitions', 't'),
+ $db->quoteName('#__workflow_stages', 's'),
+ $db->quoteName('#__workflow_stages', 's2'),
+ ]
+ )
+ ->whereIn($db->quoteName('t.from_stage_id'), [-1, $workflowStage])
+ ->where(
+ [
+ $db->quoteName('t.to_stage_id') . ' = ' . $db->quoteName('s.id'),
+ $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('s2.workflow_id'),
+ $db->quoteName('s.workflow_id') . ' = ' . $db->quoteName('t.workflow_id'),
+ $db->quoteName('s2.id') . ' = :stage1',
+ $db->quoteName('t.published') . ' = 1',
+ $db->quoteName('s.published') . ' = 1',
+ ]
+ )
+ ->bind(':stage1', $workflowStage, ParameterType::INTEGER)
+ ->order($db->quoteName('t.ordering'));
+
+ $items = $db->setQuery($query)->loadObjectList();
+
+ Factory::getLanguage()->load('com_workflow', JPATH_ADMINISTRATOR);
+
+ $parts = explode('.', $extension);
+
+ $component = reset($parts);
+
+ if (\count($items)) {
+ $user = Factory::getUser();
+
+ $items = array_filter(
+ $items,
+ function ($item) use ($user, $component) {
+ return $user->authorise('core.execute.transition', $component . '.transition.' . $item->value);
+ }
+ );
+
+ foreach ($items as $item) {
+ $item->text = Text::_($item->text);
+ }
+ }
+
+ // Get workflow stage title
+ $query = $db->getQuery(true)
+ ->select($db->quoteName('title'))
+ ->from($db->quoteName('#__workflow_stages'))
+ ->where($db->quoteName('id') . ' = :stage')
+ ->bind(':stage', $workflowStage, ParameterType::INTEGER);
+
+ $workflowName = $db->setQuery($query)->loadResult();
+
+ $default = [HTMLHelper::_('select.option', '', Text::_($workflowName))];
+
+ $options = array_merge(parent::getOptions(), $items);
+
+ if (\count($options)) {
+ $default[] = HTMLHelper::_('select.option', '-1', '--------', ['disable' => true]);
+ }
+
+ // Merge with defaults
+ return array_merge($default, $options);
+ }
}
diff --git a/libraries/src/Form/Field/UrlField.php b/libraries/src/Form/Field/UrlField.php
index 03095946df759..3db5db5e2687b 100644
--- a/libraries/src/Form/Field/UrlField.php
+++ b/libraries/src/Form/Field/UrlField.php
@@ -1,4 +1,5 @@
getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
- }
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 3.1.2 (CMS)
+ */
+ protected function getInput()
+ {
+ // Trim the trailing line in the layout file
+ return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
+ }
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.7
- */
- protected function getLayoutData()
- {
- $data = parent::getLayoutData();
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.7
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
- // Initialize some field attributes.
- $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
+ // Initialize some field attributes.
+ $maxLength = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
- // Note that the input type "url" is suitable only for external URLs, so if internal URLs are allowed
- // we have to use the input type "text" instead.
- $inputType = $this->element['relative'] ? 'type="text"' : 'type="url"';
+ // Note that the input type "url" is suitable only for external URLs, so if internal URLs are allowed
+ // we have to use the input type "text" instead.
+ $inputType = $this->element['relative'] ? 'type="text"' : 'type="url"';
- $extraData = array(
- 'maxLength' => $maxLength,
- 'inputType' => $inputType,
- );
+ $extraData = array(
+ 'maxLength' => $maxLength,
+ 'inputType' => $inputType,
+ );
- return array_merge($data, $extraData);
- }
+ return array_merge($data, $extraData);
+ }
}
diff --git a/libraries/src/Form/Field/UserField.php b/libraries/src/Form/Field/UserField.php
index e7574a08e77de..4b898f80b1339 100644
--- a/libraries/src/Form/Field/UserField.php
+++ b/libraries/src/Form/Field/UserField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 3.7.0
- *
- * @see FormField::setup()
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $return = parent::setup($element, $value, $group);
-
- // If user can't access com_users the field should be readonly.
- if ($return && !$this->readonly)
- {
- $this->readonly = !Factory::getUser()->authorise('core.manage', 'com_users');
- }
-
- return $return;
- }
-
- /**
- * Method to get the user field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.6
- */
- protected function getInput()
- {
- if (empty($this->layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
-
- }
-
- /**
- * Get the data that is going to be passed to the layout
- *
- * @return array
- *
- * @since 3.5
- */
- public function getLayoutData()
- {
- // Get the basic field data
- $data = parent::getLayoutData();
-
- // Initialize value
- $name = Text::_('JLIB_FORM_SELECT_USER');
-
- if (is_numeric($this->value))
- {
- $name = User::getInstance($this->value)->name;
- }
- // Handle the special case for "current".
- elseif (strtoupper($this->value) === 'CURRENT')
- {
- // 'CURRENT' is not a reasonable value to be placed in the html
- $current = Factory::getUser();
-
- $this->value = $current->id;
-
- $data['value'] = $this->value;
-
- $name = $current->name;
- }
-
- // User lookup went wrong, we assign the value instead.
- if ($name === null && $this->value)
- {
- $name = $this->value;
- }
-
- $extraData = array(
- 'userName' => $name,
- 'groups' => $this->getGroups(),
- 'excluded' => $this->getExcluded(),
- );
-
- return array_merge($data, $extraData);
- }
-
- /**
- * Method to get the filtering groups (null means no filtering)
- *
- * @return mixed Array of filtering groups or null.
- *
- * @since 1.6
- */
- protected function getGroups()
- {
- if (isset($this->element['groups']))
- {
- return explode(',', $this->element['groups']);
- }
- }
-
- /**
- * Method to get the users to exclude from the list of users
- *
- * @return mixed Array of users to exclude or null to to not exclude them
- *
- * @since 1.6
- */
- protected function getExcluded()
- {
- if (isset($this->element['exclude']))
- {
- return explode(',', $this->element['exclude']);
- }
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.6
+ */
+ public $type = 'User';
+
+ /**
+ * Filtering groups
+ *
+ * @var array
+ * @since 3.5
+ */
+ protected $groups = null;
+
+ /**
+ * Users to exclude from the list of users
+ *
+ * @var array
+ * @since 3.5
+ */
+ protected $excluded = null;
+
+ /**
+ * Layout to render
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $layout = 'joomla.form.field.user';
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 3.7.0
+ *
+ * @see FormField::setup()
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $return = parent::setup($element, $value, $group);
+
+ // If user can't access com_users the field should be readonly.
+ if ($return && !$this->readonly) {
+ $this->readonly = !Factory::getUser()->authorise('core.manage', 'com_users');
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get the user field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.6
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Get the data that is going to be passed to the layout
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ public function getLayoutData()
+ {
+ // Get the basic field data
+ $data = parent::getLayoutData();
+
+ // Initialize value
+ $name = Text::_('JLIB_FORM_SELECT_USER');
+
+ if (is_numeric($this->value)) {
+ $name = User::getInstance($this->value)->name;
+ } elseif (strtoupper($this->value) === 'CURRENT') {
+ // Handle the special case for "current".
+ // 'CURRENT' is not a reasonable value to be placed in the html
+ $current = Factory::getUser();
+
+ $this->value = $current->id;
+
+ $data['value'] = $this->value;
+
+ $name = $current->name;
+ }
+
+ // User lookup went wrong, we assign the value instead.
+ if ($name === null && $this->value) {
+ $name = $this->value;
+ }
+
+ $extraData = array(
+ 'userName' => $name,
+ 'groups' => $this->getGroups(),
+ 'excluded' => $this->getExcluded(),
+ );
+
+ return array_merge($data, $extraData);
+ }
+
+ /**
+ * Method to get the filtering groups (null means no filtering)
+ *
+ * @return mixed Array of filtering groups or null.
+ *
+ * @since 1.6
+ */
+ protected function getGroups()
+ {
+ if (isset($this->element['groups'])) {
+ return explode(',', $this->element['groups']);
+ }
+ }
+
+ /**
+ * Method to get the users to exclude from the list of users
+ *
+ * @return mixed Array of users to exclude or null to to not exclude them
+ *
+ * @since 1.6
+ */
+ protected function getExcluded()
+ {
+ if (isset($this->element['exclude'])) {
+ return explode(',', $this->element['exclude']);
+ }
+ }
}
diff --git a/libraries/src/Form/Field/UseractiveField.php b/libraries/src/Form/Field/UseractiveField.php
index ceb1780ecddb7..0796130711142 100644
--- a/libraries/src/Form/Field/UseractiveField.php
+++ b/libraries/src/Form/Field/UseractiveField.php
@@ -1,4 +1,5 @@
'COM_USERS_ACTIVATED',
- '1' => 'COM_USERS_UNACTIVATED',
- );
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $predefinedOptions = array(
+ '0' => 'COM_USERS_ACTIVATED',
+ '1' => 'COM_USERS_UNACTIVATED',
+ );
- /**
- * Method to instantiate the form field object.
- *
- * @param Form $form The form to attach to the form field object.
- *
- * @since 1.7.0
- */
- public function __construct($form = null)
- {
- parent::__construct($form);
+ /**
+ * Method to instantiate the form field object.
+ *
+ * @param Form $form The form to attach to the form field object.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($form = null)
+ {
+ parent::__construct($form);
- // Load the required language
- $lang = Factory::getLanguage();
- $lang->load('com_users', JPATH_ADMINISTRATOR);
- }
+ // Load the required language
+ $lang = Factory::getLanguage();
+ $lang->load('com_users', JPATH_ADMINISTRATOR);
+ }
}
diff --git a/libraries/src/Form/Field/UsergrouplistField.php b/libraries/src/Form/Field/UsergrouplistField.php
index 5bf439bf59d34..499a6df52bf09 100644
--- a/libraries/src/Form/Field/UsergrouplistField.php
+++ b/libraries/src/Form/Field/UsergrouplistField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- if (\is_string($value) && strpos($value, ',') !== false)
- {
- $value = explode(',', $value);
- }
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ if (\is_string($value) && strpos($value, ',') !== false) {
+ $value = explode(',', $value);
+ }
- return parent::setup($element, $value, $group);
- }
+ return parent::setup($element, $value, $group);
+ }
- /**
- * Method to get the options to populate list
- *
- * @return array The field option objects.
- *
- * @since 3.2
- */
- protected function getOptions()
- {
- $options = parent::getOptions();
- $checkSuperUser = (int) $this->getAttribute('checksuperusergroup', 0);
+ /**
+ * Method to get the options to populate list
+ *
+ * @return array The field option objects.
+ *
+ * @since 3.2
+ */
+ protected function getOptions()
+ {
+ $options = parent::getOptions();
+ $checkSuperUser = (int) $this->getAttribute('checksuperusergroup', 0);
- // Cache user groups base on checksuperusergroup attribute value
- if (!isset(static::$options[$checkSuperUser]))
- {
- $groups = UserGroupsHelper::getInstance()->getAll();
- $cacheOptions = array();
+ // Cache user groups base on checksuperusergroup attribute value
+ if (!isset(static::$options[$checkSuperUser])) {
+ $groups = UserGroupsHelper::getInstance()->getAll();
+ $cacheOptions = array();
- foreach ($groups as $group)
- {
- // Don't list super user groups.
- if ($checkSuperUser && Access::checkGroup($group->id, 'core.admin'))
- {
- continue;
- }
+ foreach ($groups as $group) {
+ // Don't list super user groups.
+ if ($checkSuperUser && Access::checkGroup($group->id, 'core.admin')) {
+ continue;
+ }
- $cacheOptions[] = (object) array(
- 'text' => str_repeat('- ', $group->level) . $group->title,
- 'value' => $group->id,
- 'level' => $group->level,
- );
- }
+ $cacheOptions[] = (object) array(
+ 'text' => str_repeat('- ', $group->level) . $group->title,
+ 'value' => $group->id,
+ 'level' => $group->level,
+ );
+ }
- static::$options[$checkSuperUser] = $cacheOptions;
- }
+ static::$options[$checkSuperUser] = $cacheOptions;
+ }
- return array_merge($options, static::$options[$checkSuperUser]);
- }
+ return array_merge($options, static::$options[$checkSuperUser]);
+ }
}
diff --git a/libraries/src/Form/Field/UserstateField.php b/libraries/src/Form/Field/UserstateField.php
index e10bd00aa0257..f2784595d12c4 100644
--- a/libraries/src/Form/Field/UserstateField.php
+++ b/libraries/src/Form/Field/UserstateField.php
@@ -1,4 +1,5 @@
'JENABLED',
- '1' => 'JDISABLED',
- );
+ /**
+ * Available statuses
+ *
+ * @var array
+ * @since 3.2
+ */
+ protected $predefinedOptions = array(
+ '0' => 'JENABLED',
+ '1' => 'JDISABLED',
+ );
}
diff --git a/libraries/src/Form/Field/WorkflowComponentSectionsField.php b/libraries/src/Form/Field/WorkflowComponentSectionsField.php
index 9946bc8be3165..99ca2053ff47b 100644
--- a/libraries/src/Form/Field/WorkflowComponentSectionsField.php
+++ b/libraries/src/Form/Field/WorkflowComponentSectionsField.php
@@ -1,4 +1,5 @@
value, 0, 4) !== 'com_')
- {
- continue;
- }
+ foreach ($items as $item) {
+ if (substr($item->value, 0, 4) !== 'com_') {
+ continue;
+ }
- $component = $app->bootComponent($item->value);
+ $component = $app->bootComponent($item->value);
- if (!($component instanceof WorkflowServiceInterface))
- {
- continue;
- }
+ if (!($component instanceof WorkflowServiceInterface)) {
+ continue;
+ }
- foreach ($component->getWorkflowContexts() as $extension => $text)
- {
- $options[] = HTMLHelper::_('select.option', $extension, Text::sprintf('JWORKFLOW_FIELD_COMPONENT_SECTIONS_TEXT', $item->text, $text));
- }
- }
+ foreach ($component->getWorkflowContexts() as $extension => $text) {
+ $options[] = HTMLHelper::_('select.option', $extension, Text::sprintf('JWORKFLOW_FIELD_COMPONENT_SECTIONS_TEXT', $item->text, $text));
+ }
+ }
- return $options;
- }
+ return $options;
+ }
}
diff --git a/libraries/src/Form/Field/WorkflowconditionField.php b/libraries/src/Form/Field/WorkflowconditionField.php
index 8a8fcde92c885..8bbbbbb885a16 100644
--- a/libraries/src/Form/Field/WorkflowconditionField.php
+++ b/libraries/src/Form/Field/WorkflowconditionField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $success = parent::setup($element, $value, $group);
-
- if ($success)
- {
- if (\strlen($element['extension']))
- {
- $this->extension = (string) $element['extension'];
- }
- else
- {
- $this->extension = Factory::getApplication()->input->getCmd('extension');
- }
-
- if (\strlen($element['hide_all']))
- {
- $this->hideAll = (string) $element['hide_all'] === 'true' || (string) $element['hide_all'] === 'yes';
- }
- }
-
- return $success;
- }
-
- /**
- * Method to get the field options.
- *
- * @return array The field option objects.
- *
- * @since 4.0.0
- */
- protected function getOptions()
- {
- $fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
- $options = [];
- $conditions = [];
-
- $parts = explode('.', $this->extension);
-
- $component = Factory::getApplication()->bootComponent($parts[0]);
-
- if ($component instanceof WorkflowServiceInterface)
- {
- $conditions = $component->getConditions($this->extension);
- }
-
- foreach ($conditions as $value => $option)
- {
- $text = trim((string) $option) != '' ? trim((string) $option) : $value;
-
- $selected = ((int) $this->value === $value);
-
- $tmp = array(
- 'value' => $value,
- 'text' => Text::alt($text, $fieldname),
- 'selected' => $selected,
- 'checked' => $selected,
- );
-
- // Add the option object to the result set.
- $options[] = (object) $tmp;
- }
-
- if (!$this->hideAll)
- {
- $options[] = (object) array(
- 'value' => '*',
- 'text' => Text::_('JALL'),
- 'selected' => $this->value === '*',
- 'checked' => $this->value === '*',
- );
- }
-
- // Merge any additional options in the XML definition.
- return array_merge(parent::getOptions(), $options);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $type = 'Workflowcondition';
+
+ /**
+ * The component and section separated by ".".
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension = '';
+
+ /**
+ * Determinate if the "All" value should be added
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $hideAll = false;
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $success = parent::setup($element, $value, $group);
+
+ if ($success) {
+ if (\strlen($element['extension'])) {
+ $this->extension = (string) $element['extension'];
+ } else {
+ $this->extension = Factory::getApplication()->input->getCmd('extension');
+ }
+
+ if (\strlen($element['hide_all'])) {
+ $this->hideAll = (string) $element['hide_all'] === 'true' || (string) $element['hide_all'] === 'yes';
+ }
+ }
+
+ return $success;
+ }
+
+ /**
+ * Method to get the field options.
+ *
+ * @return array The field option objects.
+ *
+ * @since 4.0.0
+ */
+ protected function getOptions()
+ {
+ $fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
+ $options = [];
+ $conditions = [];
+
+ $parts = explode('.', $this->extension);
+
+ $component = Factory::getApplication()->bootComponent($parts[0]);
+
+ if ($component instanceof WorkflowServiceInterface) {
+ $conditions = $component->getConditions($this->extension);
+ }
+
+ foreach ($conditions as $value => $option) {
+ $text = trim((string) $option) != '' ? trim((string) $option) : $value;
+
+ $selected = ((int) $this->value === $value);
+
+ $tmp = array(
+ 'value' => $value,
+ 'text' => Text::alt($text, $fieldname),
+ 'selected' => $selected,
+ 'checked' => $selected,
+ );
+
+ // Add the option object to the result set.
+ $options[] = (object) $tmp;
+ }
+
+ if (!$this->hideAll) {
+ $options[] = (object) array(
+ 'value' => '*',
+ 'text' => Text::_('JALL'),
+ 'selected' => $this->value === '*',
+ 'checked' => $this->value === '*',
+ );
+ }
+
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getOptions(), $options);
+ }
}
diff --git a/libraries/src/Form/Field/WorkflowstageField.php b/libraries/src/Form/Field/WorkflowstageField.php
index 2760a7d5ac583..186876b4e3968 100644
--- a/libraries/src/Form/Field/WorkflowstageField.php
+++ b/libraries/src/Form/Field/WorkflowstageField.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 4.0.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- $result = parent::setup($element, $value, $group);
-
- if ($result)
- {
- if (\strlen($element['extension']))
- {
- $this->extension = (string) $element['extension'];
- }
- else
- {
- $this->extension = Factory::getApplication()->input->getCmd('extension');
- }
-
- if ((string) $element['activeonly'] === '1' || (string) $element['activeonly'] === 'true')
- {
- $this->activeonly = true;
- }
- }
-
- return $result;
- }
-
- /**
- * Method to get the field option groups.
- *
- * @return array The field option objects as a nested array in groups.
- *
- * @since 4.0.0
- * @throws \UnexpectedValueException
- */
- protected function getGroups()
- {
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Select distinct stages for existing articles
- $query
- ->select(
- [
- 'DISTINCT ' . $db->quoteName('ws.id', 'workflow_stage_id'),
- $db->quoteName('ws.title', 'workflow_stage_title'),
- $db->quoteName('w.title', 'workflow_title'),
- $db->quoteName('w.id', 'workflow_id'),
- $db->quoteName('w.ordering', 'ordering'),
- $db->quoteName('ws.ordering', 'workflow_stage_ordering'),
- ]
- )
- ->from($db->quoteName('#__workflow_stages', 'ws'))
- ->from($db->quoteName('#__workflows', 'w'))
- ->where(
- [
- $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'),
- $db->quoteName('w.extension') . ' = :extension',
- ]
- )
- ->bind(':extension', $this->extension)
- ->order(
- [
- $db->quoteName('w.ordering'),
- $db->quoteName('ws.ordering'),
- ]
- );
-
- if ($this->activeonly)
- {
- $query
- ->from($db->quoteName('#__workflow_associations', 'wa'))
- ->where(
- [
- $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws.id'),
- $db->quoteName('wa.extension') . ' = :associationExtension',
- ]
- )
- ->bind(':associationExtension', $this->extension);
- }
-
- $stages = $db->setQuery($query)->loadObjectList();
-
- $workflowStages = array();
-
- // Grouping the stages by workflow
- foreach ($stages as $stage)
- {
- // Using workflow ID to differentiate workflows having same title
- $workflowStageKey = Text::_($stage->workflow_title) . ' (' . $stage->workflow_id . ')';
-
- if (!\array_key_exists($workflowStageKey, $workflowStages))
- {
- $workflowStages[$workflowStageKey] = array();
- }
-
- $workflowStages[$workflowStageKey][] = HTMLHelper::_('select.option', $stage->workflow_stage_id, Text::_($stage->workflow_stage_title));
- }
-
- // Merge any additional options in the XML definition.
- return array_merge(parent::getGroups(), $workflowStages);
- }
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $type = 'Workflowstage';
+
+ /**
+ * The component and section separated by ".".
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $extension = '';
+
+ /**
+ * Show only the stages which has an item attached
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $activeonly = false;
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 4.0.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ $result = parent::setup($element, $value, $group);
+
+ if ($result) {
+ if (\strlen($element['extension'])) {
+ $this->extension = (string) $element['extension'];
+ } else {
+ $this->extension = Factory::getApplication()->input->getCmd('extension');
+ }
+
+ if ((string) $element['activeonly'] === '1' || (string) $element['activeonly'] === 'true') {
+ $this->activeonly = true;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Method to get the field option groups.
+ *
+ * @return array The field option objects as a nested array in groups.
+ *
+ * @since 4.0.0
+ * @throws \UnexpectedValueException
+ */
+ protected function getGroups()
+ {
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Select distinct stages for existing articles
+ $query
+ ->select(
+ [
+ 'DISTINCT ' . $db->quoteName('ws.id', 'workflow_stage_id'),
+ $db->quoteName('ws.title', 'workflow_stage_title'),
+ $db->quoteName('w.title', 'workflow_title'),
+ $db->quoteName('w.id', 'workflow_id'),
+ $db->quoteName('w.ordering', 'ordering'),
+ $db->quoteName('ws.ordering', 'workflow_stage_ordering'),
+ ]
+ )
+ ->from($db->quoteName('#__workflow_stages', 'ws'))
+ ->from($db->quoteName('#__workflows', 'w'))
+ ->where(
+ [
+ $db->quoteName('ws.workflow_id') . ' = ' . $db->quoteName('w.id'),
+ $db->quoteName('w.extension') . ' = :extension',
+ ]
+ )
+ ->bind(':extension', $this->extension)
+ ->order(
+ [
+ $db->quoteName('w.ordering'),
+ $db->quoteName('ws.ordering'),
+ ]
+ );
+
+ if ($this->activeonly) {
+ $query
+ ->from($db->quoteName('#__workflow_associations', 'wa'))
+ ->where(
+ [
+ $db->quoteName('wa.stage_id') . ' = ' . $db->quoteName('ws.id'),
+ $db->quoteName('wa.extension') . ' = :associationExtension',
+ ]
+ )
+ ->bind(':associationExtension', $this->extension);
+ }
+
+ $stages = $db->setQuery($query)->loadObjectList();
+
+ $workflowStages = array();
+
+ // Grouping the stages by workflow
+ foreach ($stages as $stage) {
+ // Using workflow ID to differentiate workflows having same title
+ $workflowStageKey = Text::_($stage->workflow_title) . ' (' . $stage->workflow_id . ')';
+
+ if (!\array_key_exists($workflowStageKey, $workflowStages)) {
+ $workflowStages[$workflowStageKey] = array();
+ }
+
+ $workflowStages[$workflowStageKey][] = HTMLHelper::_('select.option', $stage->workflow_stage_id, Text::_($stage->workflow_stage_title));
+ }
+
+ // Merge any additional options in the XML definition.
+ return array_merge(parent::getGroups(), $workflowStages);
+ }
}
diff --git a/libraries/src/Form/Filter/IntarrayFilter.php b/libraries/src/Form/Filter/IntarrayFilter.php
index f5842cce5b180..1bc6b1e120178 100644
--- a/libraries/src/Form/Filter/IntarrayFilter.php
+++ b/libraries/src/Form/Filter/IntarrayFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- if (strtoupper((string) $element['filter']) === 'INT_ARRAY')
- {
- @trigger_error('`INT_ARRAY` form filter is deprecated and will be removed in 5.0. Use `Intarray` instead', E_USER_DEPRECATED);
- }
-
- if (\is_object($value))
- {
- $value = get_object_vars($value);
- }
-
- $value = \is_array($value) ? $value : array($value);
-
- $value = ArrayHelper::toInteger($value);
-
- return $value;
- }
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ if (strtoupper((string) $element['filter']) === 'INT_ARRAY') {
+ @trigger_error('`INT_ARRAY` form filter is deprecated and will be removed in 5.0. Use `Intarray` instead', E_USER_DEPRECATED);
+ }
+
+ if (\is_object($value)) {
+ $value = get_object_vars($value);
+ }
+
+ $value = \is_array($value) ? $value : array($value);
+
+ $value = ArrayHelper::toInteger($value);
+
+ return $value;
+ }
}
diff --git a/libraries/src/Form/Filter/RawFilter.php b/libraries/src/Form/Filter/RawFilter.php
index 3e360a5bcf57e..8bbe29ab57a8f 100644
--- a/libraries/src/Form/Filter/RawFilter.php
+++ b/libraries/src/Form/Filter/RawFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- return $value;
- }
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ return $value;
+ }
}
diff --git a/libraries/src/Form/Filter/RulesFilter.php b/libraries/src/Form/Filter/RulesFilter.php
index aaeaf1a67a1b8..88c0407d89973 100644
--- a/libraries/src/Form/Filter/RulesFilter.php
+++ b/libraries/src/Form/Filter/RulesFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $return = array();
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $return = array();
- foreach ((array) $value as $action => $ids)
- {
- // Build the rules array.
- $return[$action] = array();
+ foreach ((array) $value as $action => $ids) {
+ // Build the rules array.
+ $return[$action] = array();
- foreach ($ids as $id => $p)
- {
- if ($p !== '')
- {
- $return[$action][$id] = ($p == '1' || $p === 'true');
- }
- }
- }
+ foreach ($ids as $id => $p) {
+ if ($p !== '') {
+ $return[$action][$id] = ($p == '1' || $p === 'true');
+ }
+ }
+ }
- return $return;
- }
+ return $return;
+ }
}
diff --git a/libraries/src/Form/Filter/SafehtmlFilter.php b/libraries/src/Form/Filter/SafehtmlFilter.php
index f40b2024ae8e5..2bb0e9e855c71 100644
--- a/libraries/src/Form/Filter/SafehtmlFilter.php
+++ b/libraries/src/Form/Filter/SafehtmlFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- return InputFilter::getInstance(
- [],
- [],
- InputFilter::ONLY_BLOCK_DEFINED_TAGS,
- InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
- )->clean($value, 'html');
- }
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ return InputFilter::getInstance(
+ [],
+ [],
+ InputFilter::ONLY_BLOCK_DEFINED_TAGS,
+ InputFilter::ONLY_BLOCK_DEFINED_ATTRIBUTES
+ )->clean($value, 'html');
+ }
}
diff --git a/libraries/src/Form/Filter/TelFilter.php b/libraries/src/Form/Filter/TelFilter.php
index 681aaea86da3a..6e83bf41b11a6 100644
--- a/libraries/src/Form/Filter/TelFilter.php
+++ b/libraries/src/Form/Filter/TelFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $value = trim($value);
-
- // Does it match the NANP pattern?
- if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
- {
- $number = (string) preg_replace('/[^\d]/', '', $value);
-
- if (substr($number, 0, 1) === '1')
- {
- $number = substr($number, 1);
- }
-
- if (substr($number, 0, 2) === '+1')
- {
- $number = substr($number, 2);
- }
-
- $result = '1.' . $number;
- }
-
- // If not, does it match ITU-T?
- elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1)
- {
- $countrycode = substr($value, 0, strpos($value, ' '));
- $countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
- $number = strstr($value, ' ');
- $number = (string) preg_replace('/[^\d]/', '', $number);
- $result = $countrycode . '.' . $number;
- }
-
- // If not, does it match EPP?
- elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1)
- {
- if (strstr($value, 'x'))
- {
- $xpos = strpos($value, 'x');
- $value = substr($value, 0, $xpos);
- }
-
- $result = str_replace('+', '', $value);
- }
-
- // Maybe it is already ccc.nnnnnnn?
- elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1)
- {
- $result = $value;
- }
-
- // If not, can we make it a string of digits?
- else
- {
- $value = (string) preg_replace('/[^\d]/', '', $value);
-
- if ($value != null && \strlen($value) <= 15)
- {
- $length = \strlen($value);
-
- // If it is fewer than 13 digits assume it is a local number
- if ($length <= 12)
- {
- $result = '.' . $value;
- }
- else
- {
- // If it has 13 or more digits let's make a country code.
- $cclen = $length - 12;
- $result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
- }
- }
-
- // If not let's not save anything.
- else
- {
- $result = '';
- }
- }
-
- return $result;
- }
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $value = trim($value);
+
+ // Does it match the NANP pattern?
+ if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1) {
+ $number = (string) preg_replace('/[^\d]/', '', $value);
+
+ if (substr($number, 0, 1) === '1') {
+ $number = substr($number, 1);
+ }
+
+ if (substr($number, 0, 2) === '+1') {
+ $number = substr($number, 2);
+ }
+
+ $result = '1.' . $number;
+ } elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1) {
+ // If not, does it match ITU-T?
+ $countrycode = substr($value, 0, strpos($value, ' '));
+ $countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
+ $number = strstr($value, ' ');
+ $number = (string) preg_replace('/[^\d]/', '', $number);
+ $result = $countrycode . '.' . $number;
+ } elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1) {
+ // If not, does it match EPP?
+ if (strstr($value, 'x')) {
+ $xpos = strpos($value, 'x');
+ $value = substr($value, 0, $xpos);
+ }
+
+ $result = str_replace('+', '', $value);
+ } elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1) {
+ // Maybe it is already ccc.nnnnnnn?
+ $result = $value;
+ } else {
+ // If not, can we make it a string of digits?
+ $value = (string) preg_replace('/[^\d]/', '', $value);
+
+ if ($value != null && \strlen($value) <= 15) {
+ $length = \strlen($value);
+
+ // If it is fewer than 13 digits assume it is a local number
+ if ($length <= 12) {
+ $result = '.' . $value;
+ } else {
+ // If it has 13 or more digits let's make a country code.
+ $cclen = $length - 12;
+ $result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
+ }
+ } else {
+ // If not let's not save anything.
+ $result = '';
+ }
+ }
+
+ return $result;
+ }
}
diff --git a/libraries/src/Form/Filter/UnsetFilter.php b/libraries/src/Form/Filter/UnsetFilter.php
index df484084d08f8..92d10907f0d02 100644
--- a/libraries/src/Form/Filter/UnsetFilter.php
+++ b/libraries/src/Form/Filter/UnsetFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- return null;
- }
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ return null;
+ }
}
diff --git a/libraries/src/Form/Filter/UrlFilter.php b/libraries/src/Form/Filter/UrlFilter.php
index ce66fc0d72a3c..bfb02a76334d4 100644
--- a/libraries/src/Form/Filter/UrlFilter.php
+++ b/libraries/src/Form/Filter/UrlFilter.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- if (empty($value))
- {
- return false;
- }
-
- // This cleans some of the more dangerous characters but leaves special characters that are valid.
- $value = InputFilter::getInstance()->clean($value, 'html');
- $value = trim($value);
-
- // <>" are never valid in a uri see https://www.ietf.org/rfc/rfc1738.txt
- $value = str_replace(array('<', '>', '"'), '', $value);
-
- // Check for a protocol
- $protocol = parse_url($value, PHP_URL_SCHEME);
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ if (empty($value)) {
+ return false;
+ }
- // If there is no protocol and the relative option is not specified,
- // we assume that it is an external URL and prepend http://
- if (((string) $element['type'] === 'url' && !$protocol && !$element['relative'])
- || (!(string) $element['type'] === 'url' && !$protocol))
- {
- $protocol = 'http';
+ // This cleans some of the more dangerous characters but leaves special characters that are valid.
+ $value = InputFilter::getInstance()->clean($value, 'html');
+ $value = trim($value);
- // If it looks like an internal link, then add the root.
- if (substr($value, 0, 9) === 'index.php')
- {
- $value = Uri::root() . $value;
- }
+ // <>" are never valid in a uri see https://www.ietf.org/rfc/rfc1738.txt
+ $value = str_replace(array('<', '>', '"'), '', $value);
- // Otherwise we treat it as an external link.
- else
- {
- // Put the url back together.
- $value = $protocol . '://' . $value;
- }
- }
+ // Check for a protocol
+ $protocol = parse_url($value, PHP_URL_SCHEME);
- // If relative URLS are allowed we assume that URLs without protocols are internal.
- elseif (!$protocol && $element['relative'])
- {
- $host = Uri::getInstance('SERVER')->getHost();
+ // If there is no protocol and the relative option is not specified,
+ // we assume that it is an external URL and prepend http://
+ if (
+ ((string) $element['type'] === 'url' && !$protocol && !$element['relative'])
+ || (!(string) $element['type'] === 'url' && !$protocol)
+ ) {
+ $protocol = 'http';
- // If it starts with the host string, just prepend the protocol.
- if (substr($value, 0) === $host)
- {
- $value = 'http://' . $value;
- }
+ // If it looks like an internal link, then add the root.
+ if (substr($value, 0, 9) === 'index.php') {
+ $value = Uri::root() . $value;
+ } else {
+ // Otherwise we treat it as an external link.
+ // Put the url back together.
+ $value = $protocol . '://' . $value;
+ }
+ } elseif (!$protocol && $element['relative']) {
+ // If relative URLS are allowed we assume that URLs without protocols are internal.
+ $host = Uri::getInstance('SERVER')->getHost();
- // Otherwise if it doesn't start with "/" prepend the prefix of the current site.
- elseif (substr($value, 0, 1) !== '/')
- {
- $value = Uri::root(true) . '/' . $value;
- }
- }
+ // If it starts with the host string, just prepend the protocol.
+ if (substr($value, 0) === $host) {
+ $value = 'http://' . $value;
+ } elseif (substr($value, 0, 1) !== '/') {
+ // Otherwise if it doesn't start with "/" prepend the prefix of the current site.
+ $value = Uri::root(true) . '/' . $value;
+ }
+ }
- $value = PunycodeHelper::urlToPunycode($value);
+ $value = PunycodeHelper::urlToPunycode($value);
- return $value;
- }
+ return $value;
+ }
}
diff --git a/libraries/src/Form/Form.php b/libraries/src/Form/Form.php
index 52a25aa37fcb5..20e14660f1ae2 100644
--- a/libraries/src/Form/Form.php
+++ b/libraries/src/Form/Form.php
@@ -1,4 +1,5 @@
name = $name;
-
- // Initialise the Registry data.
- $this->data = new Registry;
-
- // Set the options if specified.
- $this->options['control'] = $options['control'] ?? false;
- }
-
- /**
- * Method to bind data to the form.
- *
- * @param mixed $data An array or object of data to bind to the form.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function bind($data)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // The data must be an object or array.
- if (!\is_object($data) && !\is_array($data))
- {
- return false;
- }
-
- $this->bindLevel(null, $data);
-
- return true;
- }
-
- /**
- * Method to bind data to the form for the group level.
- *
- * @param string $group The dot-separated form group path on which to bind the data.
- * @param mixed $data An array or object of data to bind to the form for the group level.
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected function bindLevel($group, $data)
- {
- // Ensure the input data is an array.
- if (\is_object($data))
- {
- if ($data instanceof Registry)
- {
- // Handle a Registry.
- $data = $data->toArray();
- }
- elseif ($data instanceof CMSObject)
- {
- // Handle a CMSObject.
- $data = $data->getProperties();
- }
- else
- {
- // Handle other types of objects.
- $data = (array) $data;
- }
- }
-
- // Process the input data.
- foreach ($data as $k => $v)
- {
- $level = $group ? $group . '.' . $k : $k;
-
- if ($this->findField($k, $group))
- {
- // If the field exists set the value.
- $this->data->set($level, $v);
- }
- elseif (\is_object($v) || ArrayHelper::isAssociative($v))
- {
- // If the value is an object or an associative array, hand it off to the recursive bind level method.
- $this->bindLevel($level, $v);
- }
- }
- }
-
- /**
- * Return all errors, if any.
- *
- * @return array Array of error messages or RuntimeException objects.
- *
- * @since 1.7.0
- */
- public function getErrors()
- {
- return $this->errors;
- }
-
- /**
- * Method to get a form field represented as a FormField object.
- *
- * @param string $name The name of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param mixed $value The optional value to use as the default for the field.
- *
- * @return FormField|boolean The FormField object for the field or boolean false on error.
- *
- * @since 1.7.0
- */
- public function getField($name, $group = null, $value = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Attempt to find the field by name and group.
- $element = $this->findField($name, $group);
-
- // If the field element was not found return false.
- if (!$element)
- {
- return false;
- }
-
- return $this->loadField($element, $group, $value);
- }
-
- /**
- * Method to get an attribute value from a field XML element. If the attribute doesn't exist or
- * is null then the optional default value will be used.
- *
- * @param string $name The name of the form field for which to get the attribute value.
- * @param string $attribute The name of the attribute for which to get a value.
- * @param mixed $default The optional default value to use if no attribute value exists.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return mixed The attribute value for the field.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function getFieldAttribute($name, $attribute, $default = null, $group = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Find the form field element from the definition.
- $element = $this->findField($name, $group);
-
- // If the element exists and the attribute exists for the field return the attribute value.
- if (($element instanceof \SimpleXMLElement) && \strlen((string) $element[$attribute]))
- {
- return (string) $element[$attribute];
- }
-
- // Otherwise return the given default value.
- else
- {
- return $default;
- }
- }
-
- /**
- * Method to get an array of FormField objects in a given fieldset by name. If no name is
- * given then all fields are returned.
- *
- * @param string $set The optional name of the fieldset.
- *
- * @return FormField[] The array of FormField objects in the fieldset.
- *
- * @since 1.7.0
- */
- public function getFieldset($set = null)
- {
- $fields = [];
-
- // Get all of the field elements in the fieldset.
- if ($set)
- {
- $elements = $this->findFieldsByFieldset($set);
- }
-
- // Get all fields.
- else
- {
- $elements = $this->findFieldsByGroup();
- }
-
- // If no field elements were found return empty.
- if (empty($elements))
- {
- return $fields;
- }
-
- // Build the result array from the found field elements.
- foreach ($elements as $element)
- {
- // Get the field groups for the element.
- $attrs = $element->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
- $group = implode('.', $groups);
-
- // If the field is successfully loaded add it to the result array.
- if ($field = $this->loadField($element, $group))
- {
- $fields[$field->id] = $field;
- }
- }
-
- return $fields;
- }
-
- /**
- * Method to get an array of fieldset objects optionally filtered over a given field group.
- *
- * @param string $group The dot-separated form group path on which to filter the fieldsets.
- *
- * @return array The array of fieldset objects.
- *
- * @since 1.7.0
- */
- public function getFieldsets($group = null)
- {
- $fieldsets = [];
- $sets = [];
-
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- if ($group)
- {
- // Get the fields elements for a given group.
- $elements = &$this->findGroup($group);
-
- foreach ($elements as &$element)
- {
- // Get an array of elements and fieldset attributes within the fields element.
- if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset'))
- {
- $sets = array_merge($sets, (array) $tmp);
- }
- }
- }
- else
- {
- // Get an array of elements and fieldset attributes.
- $sets = $this->xml->xpath('//fieldset[@name and not(ancestor::field/form/*)] | //field[@fieldset and not(ancestor::field/form/*)]/@fieldset');
- }
-
- // If no fieldsets are found return empty.
- if (empty($sets))
- {
- return $fieldsets;
- }
-
- // Process each found fieldset.
- foreach ($sets as $set)
- {
- if ((string) $set['hidden'] === 'true')
- {
- continue;
- }
-
- // Are we dealing with a fieldset element?
- if ((string) $set['name'])
- {
- // Only create it if it doesn't already exist.
- if (empty($fieldsets[(string) $set['name']]))
- {
- // Build the fieldset object.
- $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
-
- foreach ($set->attributes() as $name => $value)
- {
- $fieldset->$name = (string) $value;
- }
-
- // Add the fieldset object to the list.
- $fieldsets[$fieldset->name] = $fieldset;
- }
- }
-
- // Must be dealing with a fieldset attribute.
- else
- {
- // Only create it if it doesn't already exist.
- if (empty($fieldsets[(string) $set]))
- {
- // Attempt to get the fieldset element for data (throughout the entire form document).
- $tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');
-
- // If no element was found, build a very simple fieldset object.
- if (empty($tmp))
- {
- $fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
- }
-
- // Build the fieldset object from the element.
- else
- {
- $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
-
- foreach ($tmp[0]->attributes() as $name => $value)
- {
- $fieldset->$name = (string) $value;
- }
- }
-
- // Add the fieldset object to the list.
- $fieldsets[$fieldset->name] = $fieldset;
- }
- }
- }
-
- return $fieldsets;
- }
-
- /**
- * Method to get the form control. This string serves as a container for all form fields. For
- * example, if there is a field named 'foo' and a field named 'bar' and the form control is
- * empty the fields will be rendered like: ` ` and ` `. If
- * the form control is set to 'joomla' however, the fields would be rendered like:
- * ` ` and ` `.
- *
- * @return string The form control string.
- *
- * @since 1.7.0
- */
- public function getFormControl()
- {
- return (string) $this->options['control'];
- }
-
- /**
- * Method to get an array of FormField objects in a given field group by name.
- *
- * @param string $group The dot-separated form group path for which to get the form fields.
- * @param boolean $nested True to also include fields in nested groups that are inside of the
- * group for which to find fields.
- *
- * @return FormField[] The array of FormField objects in the field group.
- *
- * @since 1.7.0
- */
- public function getGroup($group, $nested = false)
- {
- $fields = [];
-
- // Get all of the field elements in the field group.
- $elements = $this->findFieldsByGroup($group, $nested);
-
- // If no field elements were found return empty.
- if (empty($elements))
- {
- return $fields;
- }
-
- // Build the result array from the found field elements.
- foreach ($elements as $element)
- {
- // Get the field groups for the element.
- $attrs = $element->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
- $group = implode('.', $groups);
-
- // If the field is successfully loaded add it to the result array.
- if ($field = $this->loadField($element, $group))
- {
- $fields[$field->id] = $field;
- }
- }
-
- return $fields;
- }
-
- /**
- * Method to get a form field markup for the field input.
- *
- * @param string $name The name of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param mixed $value The optional value to use as the default for the field.
- *
- * @return string The form field markup.
- *
- * @since 1.7.0
- */
- public function getInput($name, $group = null, $value = null)
- {
- // Attempt to get the form field.
- if ($field = $this->getField($name, $group, $value))
- {
- return $field->input;
- }
-
- return '';
- }
-
- /**
- * Method to get the label for a field input.
- *
- * @param string $name The name of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return string The form field label.
- *
- * @since 1.7.0
- */
- public function getLabel($name, $group = null)
- {
- // Attempt to get the form field.
- if ($field = $this->getField($name, $group))
- {
- return $field->label;
- }
-
- return '';
- }
-
- /**
- * Method to get the form name.
- *
- * @return string The name of the form.
- *
- * @since 1.7.0
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * Method to get the value of a field.
- *
- * @param string $name The name of the field for which to get the value.
- * @param string $group The optional dot-separated form group path on which to get the value.
- * @param mixed $default The optional default value of the field value is empty.
- *
- * @return mixed The value of the field or the default value if empty.
- *
- * @since 1.7.0
- */
- public function getValue($name, $group = null, $default = null)
- {
- // If a group is set use it.
- if ($group)
- {
- $return = $this->data->get($group . '.' . $name, $default);
- }
- else
- {
- $return = $this->data->get($name, $default);
- }
-
- return $return;
- }
-
- /**
- * Method to get a control group with label and input.
- *
- * @param string $name The name of the field for which to get the value.
- * @param string $group The optional dot-separated form group path on which to get the value.
- * @param mixed $default The optional default value of the field value is empty.
- * @param array $options Any options to be passed into the rendering of the field
- *
- * @return string A string containing the html for the control group
- *
- * @since 3.2.3
- */
- public function renderField($name, $group = null, $default = null, $options = [])
- {
- $field = $this->getField($name, $group, $default);
-
- if ($field)
- {
- return $field->renderField($options);
- }
-
- return '';
- }
-
- /**
- * Method to get all control groups with label and input of a fieldset.
- *
- * @param string $name The name of the fieldset for which to get the values.
- * @param array $options Any options to be passed into the rendering of the field
- *
- * @return string A string containing the html for the control groups
- *
- * @since 3.2.3
- */
- public function renderFieldset($name, $options = [])
- {
- $fields = $this->getFieldset($name);
- $html = [];
-
- foreach ($fields as $field)
- {
- $html[] = $field->renderField($options);
- }
-
- return implode('', $html);
- }
-
- /**
- * Method to load the form description from an XML string or object.
- *
- * The replace option works per field. If a field being loaded already exists in the current
- * form definition then the behavior or load will vary depending upon the replace flag. If it
- * is set to true, then the existing field will be replaced in its exact location by the new
- * field being loaded. If it is false, then the new field being loaded will be ignored and the
- * method will move on to the next field to load.
- *
- * @param string $data The name of an XML string or object.
- * @param boolean $replace Flag to toggle whether form fields should be replaced if a field
- * already exists with the same group/name.
- * @param string $xpath An optional xpath to search for the fields.
- *
- * @return boolean True on success, false otherwise.
- *
- * @since 1.7.0
- */
- public function load($data, $replace = true, $xpath = null)
- {
- // If the data to load isn't already an XML element or string return false.
- if ((!($data instanceof \SimpleXMLElement)) && (!\is_string($data)))
- {
- return false;
- }
-
- // Attempt to load the XML if a string.
- if (\is_string($data))
- {
- try
- {
- $data = new \SimpleXMLElement($data);
- }
- catch (\Exception $e)
- {
- return false;
- }
- }
-
- // If we have no XML definition at this point let's make sure we get one.
- if (empty($this->xml))
- {
- // If no XPath query is set to search for fields, and we have a , set it and return.
- if (!$xpath && ($data->getName() === 'form'))
- {
- $this->xml = $data;
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
-
- // Create a root element for the form.
- else
- {
- $this->xml = new \SimpleXMLElement(' ');
- }
- }
-
- // Get the XML elements to load.
- $elements = [];
-
- if ($xpath)
- {
- $elements = $data->xpath($xpath);
- }
- elseif ($data->getName() === 'form')
- {
- $elements = $data->children();
- }
-
- // If there is nothing to load return true.
- if (empty($elements))
- {
- return true;
- }
-
- // Load the found form elements.
- foreach ($elements as $element)
- {
- // Get an array of fields with the correct name.
- $fields = $element->xpath('descendant-or-self::field');
-
- foreach ($fields as $field)
- {
- // Get the group names as strings for ancestor fields elements.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
-
- // Check to see if the field exists in the current form.
- if ($current = $this->findField((string) $field['name'], implode('.', $groups)))
- {
- // If set to replace found fields, replace the data and remove the field so we don't add it twice.
- if ($replace)
- {
- $olddom = dom_import_simplexml($current);
- $loadeddom = dom_import_simplexml($field);
- $addeddom = $olddom->ownerDocument->importNode($loadeddom, true);
- $olddom->parentNode->replaceChild($addeddom, $olddom);
- $loadeddom->parentNode->removeChild($loadeddom);
- }
- else
- {
- unset($field);
- }
- }
- }
-
- // Merge the new field data into the existing XML document.
- self::addNode($this->xml, $element);
- }
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
-
- /**
- * Method to load the form description from an XML file.
- *
- * The reset option works on a group basis. If the XML file references
- * groups that have already been created they will be replaced with the
- * fields in the new XML file unless the $reset parameter has been set
- * to false.
- *
- * @param string $file The filesystem path of an XML file.
- * @param boolean $reset Flag to toggle whether form fields should be replaced if a field
- * already exists with the same group/name.
- * @param string $xpath An optional xpath to search for the fields.
- *
- * @return boolean True on success, false otherwise.
- *
- * @since 1.7.0
- */
- public function loadFile($file, $reset = true, $xpath = null)
- {
- // Check to see if the path is an absolute path.
- if (!is_file($file))
- {
- // Not an absolute path so let's attempt to find one using JPath.
- $file = Path::find(self::addFormPath(), strtolower($file) . '.xml');
-
- // If unable to find the file return false.
- if (!$file)
- {
- return false;
- }
- }
-
- // Attempt to load the XML file.
- $xml = simplexml_load_file($file);
-
- return $this->load($xml, $reset, $xpath);
- }
-
- /**
- * Method to remove a field from the form definition.
- *
- * @param string $name The name of the form field for which remove.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return boolean True on success, false otherwise.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function removeField($name, $group = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Find the form field element from the definition.
- $element = $this->findField($name, $group);
-
- // If the element exists remove it from the form definition.
- if ($element instanceof \SimpleXMLElement)
- {
- $dom = dom_import_simplexml($element);
- $dom->parentNode->removeChild($dom);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Method to remove a group from the form definition.
- *
- * @param string $group The dot-separated form group path for the group to remove.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function removeGroup($group)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Get the fields elements for a given group.
- $elements = &$this->findGroup($group);
-
- foreach ($elements as &$element)
- {
- $dom = dom_import_simplexml($element);
- $dom->parentNode->removeChild($dom);
- }
-
- return true;
- }
-
- /**
- * Method to reset the form data store and optionally the form XML definition.
- *
- * @param boolean $xml True to also reset the XML form definition.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function reset($xml = false)
- {
- unset($this->data);
- $this->data = new Registry;
-
- if ($xml)
- {
- unset($this->xml);
- $this->xml = new \SimpleXMLElement(' ');
- }
-
- return true;
- }
-
- /**
- * Method to set a field XML element to the form definition. If the replace flag is set then
- * the field will be set whether it already exists or not. If it isn't set, then the field
- * will not be replaced if it already exists.
- *
- * @param \SimpleXMLElement $element The XML element object representation of the form field.
- * @param string $group The optional dot-separated form group path on which to set the field.
- * @param boolean $replace True to replace an existing field if one already exists.
- * @param string $fieldset The name of the fieldset we are adding the field to.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function setField(\SimpleXMLElement $element, $group = null, $replace = true, $fieldset = 'default')
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Find the form field element from the definition.
- $old = $this->findField((string) $element['name'], $group);
-
- // If an existing field is found and replace flag is false do nothing and return true.
- if (!$replace && !empty($old))
- {
- return true;
- }
-
- // If an existing field is found and replace flag is true remove the old field.
- if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement))
- {
- $dom = dom_import_simplexml($old);
-
- // Get the parent element, this should be the fieldset
- $parent = $dom->parentNode;
- $fieldset = $parent->getAttribute('name');
-
- $parent->removeChild($dom);
- }
-
- // Create the search path
- $path = '//';
-
- if (!empty($group))
- {
- $path .= 'fields[@name="' . $group . '"]/';
- }
-
- $path .= 'fieldset[@name="' . $fieldset . '"]';
-
- $fs = $this->xml->xpath($path);
-
- if (isset($fs[0]) && ($fs[0] instanceof \SimpleXMLElement))
- {
- // Add field to the form.
- self::addNode($fs[0], $element);
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
-
- // We couldn't find a fieldset to add the field. Now we are checking, if we have set only a group
- if (!empty($group))
- {
- $fields = &$this->findGroup($group);
-
- // If an appropriate fields element was found for the group, add the element.
- if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement))
- {
- self::addNode($fields[0], $element);
- }
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
-
- // We couldn't find a parent so we are adding it at root level
-
- // Add field to the form.
- self::addNode($this->xml, $element);
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
-
- /**
- * Method to set an attribute value for a field XML element.
- *
- * @param string $name The name of the form field for which to set the attribute value.
- * @param string $attribute The name of the attribute for which to set a value.
- * @param mixed $value The value to set for the attribute.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function setFieldAttribute($name, $attribute, $value, $group = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Find the form field element from the definition.
- $element = $this->findField($name, $group);
-
- // If the element doesn't exist return false.
- if (!($element instanceof \SimpleXMLElement))
- {
- return false;
- }
-
- // Otherwise set the attribute and return true.
- else
- {
- $element[$attribute] = $value;
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return true;
- }
- }
-
- /**
- * Method to set some field XML elements to the form definition. If the replace flag is set then
- * the fields will be set whether they already exists or not. If it isn't set, then the fields
- * will not be replaced if they already exist.
- *
- * @param array &$elements The array of XML element object representations of the form fields.
- * @param string $group The optional dot-separated form group path on which to set the fields.
- * @param boolean $replace True to replace existing fields if they already exist.
- * @param string $fieldset The name of the fieldset we are adding the field to.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function setFields(&$elements, $group = null, $replace = true, $fieldset = 'default')
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Make sure the elements to set are valid.
- foreach ($elements as $element)
- {
- if (!($element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
- }
-
- // Set the fields.
- $return = true;
-
- foreach ($elements as $element)
- {
- if (!$this->setField($element, $group, $replace, $fieldset))
- {
- $return = false;
- }
- }
-
- // Synchronize any paths found in the load.
- $this->syncPaths();
-
- return $return;
- }
-
- /**
- * Method to set the value of a field. If the field does not exist in the form then the method
- * will return false.
- *
- * @param string $name The name of the field for which to set the value.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param mixed $value The value to set for the field.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function setValue($name, $group = null, $value = null)
- {
- // If the field does not exist return false.
- if (!$this->findField($name, $group))
- {
- return false;
- }
-
- // If a group is set use it.
- if ($group)
- {
- $this->data->set($group . '.' . $name, $value);
- }
- else
- {
- $this->data->set($name, $value);
- }
-
- return true;
- }
-
- /**
- * Method to process the form data.
- *
- * @param array $data An array of field values to filter.
- * @param string $group The dot-separated form group path on which to filter the fields.
- *
- * @return mixed Array or false.
- *
- * @since 4.0.0
- */
- public function process($data, $group = null)
- {
- $data = $this->filter($data, $group);
-
- $valid = $this->validate($data, $group);
-
- if (!$valid)
- {
- return $valid;
- }
-
- return $this->postProcess($data, $group);
- }
-
- /**
- * Method to filter the form data.
- *
- * @param array $data An array of field values to filter.
- * @param string $group The dot-separated form group path on which to filter the fields.
- *
- * @return mixed Array or false.
- *
- * @since 4.0.0
- */
- public function filter($data, $group = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- $input = new Registry($data);
- $output = new Registry;
-
- // Get the fields for which to filter the data.
- $fields = $this->findFieldsByGroup($group);
-
- if (!$fields)
- {
- // PANIC!
- return false;
- }
-
- // Filter the fields.
- foreach ($fields as $field)
- {
- $name = (string) $field['name'];
-
- // Get the field groups for the element.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
- $attrGroup = implode('.', $groups);
-
- $key = $attrGroup ? $attrGroup . '.' . $name : $name;
-
- // Filter the value if it exists.
- if ($input->exists($key))
- {
- $fieldObj = $this->loadField($field, $group);
-
- // Only set into the output if the field was supposed to render on the page (i.e. setup returned true)
- if ($fieldObj)
- {
- $output->set($key, $fieldObj->filter($input->get($key, (string) $field['default']), $group, $input));
- }
- }
- }
-
- return $output->toArray();
- }
-
- /**
- * Method to validate form data.
- *
- * Validation warnings will be pushed into JForm::errors and should be
- * retrieved with JForm::getErrors() when validate returns boolean false.
- *
- * @param array $data An array of field values to validate.
- * @param string $group The optional dot-separated form group path on which to filter the
- * fields to be validated.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function validate($data, $group = null)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- $return = true;
-
- // Create an input registry object from the data to validate.
- $input = new Registry($data);
-
- // Get the fields for which to validate the data.
- $fields = $this->findFieldsByGroup($group);
-
- if (!$fields)
- {
- // PANIC!
- return false;
- }
-
- // Validate the fields.
- foreach ($fields as $field)
- {
- $name = (string) $field['name'];
-
- // Define field name for messages
- if ($field['label'])
- {
- $fieldLabel = $field['label'];
-
- // Try to translate label if not set to false
- $translate = (string) $field['translateLabel'];
-
- if (!($translate === 'false' || $translate === 'off' || $translate === '0'))
- {
- $fieldLabel = Text::_($fieldLabel);
- }
- }
- else
- {
- $fieldLabel = Text::_($name);
- }
-
- $disabled = ((string) $field['disabled'] === 'true' || (string) $field['disabled'] === 'disabled');
-
- $fieldExistsInRequestData = $input->exists($name) || $input->exists($group . '.' . $name);
-
- // If the field is disabled but it is passed in the request this is invalid as disabled fields are not added to the request
- if ($disabled && $fieldExistsInRequestData)
- {
- throw new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel));
- }
-
- // Get the field groups for the element.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
- $attrGroup = implode('.', $groups);
-
- $key = $attrGroup ? $attrGroup . '.' . $name : $name;
-
- $fieldObj = $this->loadField($field, $attrGroup);
-
- if ($fieldObj)
- {
- $valid = $fieldObj->validate($input->get($key), $attrGroup, $input);
-
- // Check for an error.
- if ($valid instanceof \Exception)
- {
- $this->errors[] = $valid;
- $return = false;
- }
- }
- elseif (!$fieldObj && $input->exists($key))
- {
- // The field returned false from setup and shouldn't be included in the page body - yet we received
- // a value for it. This is probably some sort of injection attack and should be rejected
- $this->errors[] = new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $key));
- }
- }
-
- return $return;
- }
-
- /**
- * Method to post-process form data.
- *
- * @param array $data An array of field values to post-process.
- * @param string $group The optional dot-separated form group path on which to filter the
- * fields to be validated.
- *
- * @return mixed Array or false.
- *
- * @since 4.0.0
- */
- public function postProcess($data, $group = null)
- {
- // Make sure there is a valid SimpleXMLElement
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- $input = new Registry($data);
- $output = new Registry;
-
- // Get the fields for which to postProcess the data.
- $fields = $this->findFieldsByGroup($group);
-
- if (!$fields)
- {
- // PANIC!
- return false;
- }
-
- // Filter the fields.
- foreach ($fields as $field)
- {
- $name = (string) $field['name'];
-
- // Get the field groups for the element.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $groups = array_map('strval', $attrs ?: []);
- $attrGroup = implode('.', $groups);
-
- $key = $attrGroup ? $attrGroup . '.' . $name : $name;
-
- // Filter the value if it exists.
- if ($input->exists($key))
- {
- $fieldobj = $this->loadField($field, $group);
- $output->set($key, $fieldobj->postProcess($input->get($key, (string) $field['default']), $group, $input));
- }
- }
-
- return $output->toArray();
- }
-
- /**
- * Method to get a form field represented as an XML element object.
- *
- * @param string $name The name of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error.
- *
- * @since 1.7.0
- */
- protected function findField($name, $group = null)
- {
- $element = false;
- $fields = [];
-
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Let's get the appropriate field element based on the method arguments.
- if ($group)
- {
- // Get the fields elements for a given group.
- $elements = &$this->findGroup($group);
-
- // Get all of the field elements with the correct name for the fields elements.
- foreach ($elements as $el)
- {
- // If there are matching field elements add them to the fields array.
- if ($tmp = $el->xpath('descendant::field[@name="' . $name . '" and not(ancestor::field/form/*)]'))
- {
- $fields = array_merge($fields, $tmp);
- }
- }
-
- // Make sure something was found.
- if (!$fields)
- {
- return false;
- }
-
- // Use the first correct match in the given group.
- $groupNames = explode('.', $group);
-
- foreach ($fields as &$field)
- {
- // Get the group names as strings for ancestor fields elements.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $names = array_map('strval', $attrs ?: []);
-
- // If the field is in the exact group use it and break out of the loop.
- if ($names == (array) $groupNames)
- {
- $element = &$field;
- break;
- }
- }
- }
- else
- {
- // Get an array of fields with the correct name.
- $fields = $this->xml->xpath('//field[@name="' . $name . '" and not(ancestor::field/form/*)]');
-
- // Make sure something was found.
- if (!$fields)
- {
- return false;
- }
-
- // Search through the fields for the right one.
- foreach ($fields as &$field)
- {
- // If we find an ancestor fields element with a group name then it isn't what we want.
- if ($field->xpath('ancestor::fields[@name]'))
- {
- continue;
- }
-
- // Found it!
- else
- {
- $element = &$field;
- break;
- }
- }
- }
-
- return $element;
- }
-
- /**
- * Method to get an array of `` elements from the form XML document which are in a specified fieldset by name.
- *
- * @param string $name The name of the fieldset.
- *
- * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects.
- *
- * @since 1.7.0
- */
- protected function &findFieldsByFieldset($name)
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- /*
- * Get an array of elements that are underneath a element
- * with the appropriate name attribute, and also any elements with
- * the appropriate fieldset attribute. To allow repeatable elements only fields
- * which are not descendants of other fields are selected.
- */
- $fields = $this->xml->xpath('(//fieldset[@name="' . $name . '"]//field | //field[@fieldset="' . $name . '"])[not(ancestor::field)]');
-
- return $fields;
- }
-
- /**
- * Method to get an array of `` elements from the form XML document which are in a control group by name.
- *
- * @param mixed $group The optional dot-separated form group path on which to find the fields.
- * Null will return all fields. False will return fields not in a group.
- * @param boolean $nested True to also include fields in nested groups that are inside of the
- * group for which to find fields.
- *
- * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects.
- *
- * @since 1.7.0
- */
- protected function &findFieldsByGroup($group = null, $nested = false)
- {
- $fields = [];
-
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Get only fields in a specific group?
- if ($group)
- {
- // Get the fields elements for a given group.
- $elements = &$this->findGroup($group);
-
- // Get all of the field elements for the fields elements.
- foreach ($elements as $element)
- {
- // If there are field elements add them to the return result.
- if ($tmp = $element->xpath('descendant::field'))
- {
- // If we also want fields in nested groups then just merge the arrays.
- if ($nested)
- {
- $fields = array_merge($fields, $tmp);
- }
-
- // If we want to exclude nested groups then we need to check each field.
- else
- {
- $groupNames = explode('.', $group);
-
- foreach ($tmp as $field)
- {
- // Get the names of the groups that the field is in.
- $attrs = $field->xpath('ancestor::fields[@name]/@name');
- $names = array_map('strval', $attrs ?: []);
-
- // If the field is in the specific group then add it to the return list.
- if ($names == (array) $groupNames)
- {
- $fields = array_merge($fields, array($field));
- }
- }
- }
- }
- }
- }
- elseif ($group === false)
- {
- // Get only field elements not in a group.
- $fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
- }
- else
- {
- // Get an array of all the elements.
- $fields = $this->xml->xpath('//field[not(ancestor::field/form/*)]');
- }
-
- return $fields;
- }
-
- /**
- * Method to get a form field group represented as an XML element object.
- *
- * @param string $group The dot-separated form group path on which to find the group.
- *
- * @return \SimpleXMLElement[]|boolean An array of XML element objects for the group or boolean false on error.
- *
- * @since 1.7.0
- */
- protected function &findGroup($group)
- {
- $groups = [];
- $tmp = [];
-
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Make sure there is actually a group to find.
- $group = explode('.', $group);
-
- if (!empty($group))
- {
- // Get any fields elements with the correct group name.
- $elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '" and not(ancestor::field/form/*)]');
-
- // Check to make sure that there are no parent groups for each element.
- foreach ($elements as $element)
- {
- if (!$element->xpath('ancestor::fields[@name]'))
- {
- $tmp[] = $element;
- }
- }
-
- // Iterate through the nested groups to find any matching form field groups.
- for ($i = 1, $n = \count($group); $i < $n; $i++)
- {
- // Initialise some loop variables.
- $validNames = \array_slice($group, 0, $i + 1);
- $current = $tmp;
- $tmp = [];
-
- // Check to make sure that there are no parent groups for each element.
- foreach ($current as $element)
- {
- // Get any fields elements with the correct group name.
- $children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');
-
- // For the found fields elements validate that they are in the correct groups.
- foreach ($children as $fields)
- {
- // Get the group names as strings for ancestor fields elements.
- $attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
- $names = array_map('strval', $attrs ?: []);
-
- // If the group names for the fields element match the valid names at this
- // level add the fields element.
- if ($validNames == $names)
- {
- $tmp[] = $fields;
- }
- }
- }
- }
-
- // Only include valid XML objects.
- foreach ($tmp as $element)
- {
- if ($element instanceof \SimpleXMLElement)
- {
- $groups[] = $element;
- }
- }
- }
-
- return $groups;
- }
-
- /**
- * Method to load, setup and return a FormField object based on field data.
- *
- * @param string $element The XML element object representation of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param mixed $value The optional value to use as the default for the field.
- *
- * @return FormField|boolean The FormField object for the field or boolean false on error.
- *
- * @since 1.7.0
- */
- protected function loadField($element, $group = null, $value = null)
- {
- // Make sure there is a valid SimpleXMLElement.
- if (!($element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Get the field type.
- $type = $element['type'] ? (string) $element['type'] : 'text';
-
- // Load the FormField object for the field.
- $field = FormHelper::loadFieldType($type);
-
- if ($field instanceof DatabaseAwareInterface)
- {
- try
- {
- $field->setDatabase($this->getDatabase());
- }
- catch (DatabaseNotFoundException $e)
- {
- @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
- $field->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
- }
- }
-
- // If the object could not be loaded, get a text field object.
- if ($field === false)
- {
- $field = FormHelper::loadFieldType('text');
- }
-
- /*
- * Get the value for the form field if not set.
- * Default to the translated version of the 'default' attribute
- * if 'translate_default' attribute if set to 'true' or '1'
- * else the value of the 'default' attribute for the field.
- */
- if ($value === null)
- {
- $default = (string) ($element['default'] ? $element['default'] : $element->default);
-
- if (($translate = $element['translate_default']) && ((string) $translate === 'true' || (string) $translate === '1'))
- {
- $lang = Factory::getLanguage();
-
- if ($lang->hasKey($default))
- {
- $debug = $lang->setDebug(false);
- $default = Text::_($default);
- $lang->setDebug($debug);
- }
- else
- {
- $default = Text::_($default);
- }
- }
-
- $value = $this->getValue((string) $element['name'], $group, $default);
- }
-
- // Setup the FormField object.
- $field->setForm($this);
-
- if ($field->setup($element, $value, $group))
- {
- return $field;
- }
- else
- {
- return false;
- }
- }
-
- /**
- * Method to synchronize any field, form or rule paths contained in the XML document.
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- * @todo Maybe we should receive all addXXXpaths attributes at once?
- */
- protected function syncPaths()
- {
- // Make sure there is a valid Form XML document.
- if (!($this->xml instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
- }
-
- // Get any addfieldpath attributes from the form definition.
- $paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
- $paths = array_map('strval', $paths ?: []);
-
- // Add the field paths.
- foreach ($paths as $path)
- {
- $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
- self::addFieldPath($path);
- }
-
- // Get any addformpath attributes from the form definition.
- $paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
- $paths = array_map('strval', $paths ?: []);
-
- // Add the form paths.
- foreach ($paths as $path)
- {
- $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
- self::addFormPath($path);
- }
-
- // Get any addrulepath attributes from the form definition.
- $paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
- $paths = array_map('strval', $paths ?: []);
-
- // Add the rule paths.
- foreach ($paths as $path)
- {
- $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
- self::addRulePath($path);
- }
-
- // Get any addrulepath attributes from the form definition.
- $paths = $this->xml->xpath('//*[@addfilterpath]/@addfilterpath');
- $paths = array_map('strval', $paths ?: []);
-
- // Add the rule paths.
- foreach ($paths as $path)
- {
- $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
- self::addFilterPath($path);
- }
-
- // Get any addfieldprefix attributes from the form definition.
- $prefixes = $this->xml->xpath('//*[@addfieldprefix]/@addfieldprefix');
- $prefixes = array_map('strval', $prefixes ?: []);
-
- // Add the field prefixes.
- foreach ($prefixes as $prefix)
- {
- FormHelper::addFieldPrefix($prefix);
- }
-
- // Get any addformprefix attributes from the form definition.
- $prefixes = $this->xml->xpath('//*[@addformprefix]/@addformprefix');
- $prefixes = array_map('strval', $prefixes ?: []);
-
- // Add the field prefixes.
- foreach ($prefixes as $prefix)
- {
- FormHelper::addFormPrefix($prefix);
- }
-
- // Get any addruleprefix attributes from the form definition.
- $prefixes = $this->xml->xpath('//*[@addruleprefix]/@addruleprefix');
- $prefixes = array_map('strval', $prefixes ?: []);
-
- // Add the field prefixes.
- foreach ($prefixes as $prefix)
- {
- FormHelper::addRulePrefix($prefix);
- }
-
- // Get any addruleprefix attributes from the form definition.
- $prefixes = $this->xml->xpath('//*[@addfilterprefix]/@addfilterprefix');
- $prefixes = array_map('strval', $prefixes ?: []);
-
- // Add the field prefixes.
- foreach ($prefixes as $prefix)
- {
- FormHelper::addFilterPrefix($prefix);
- }
-
- return true;
- }
-
- /**
- * Proxy for {@link FormHelper::addFieldPath()}.
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 1.7.0
- */
- public static function addFieldPath($new = null)
- {
- return FormHelper::addFieldPath($new);
- }
-
- /**
- * Proxy for FormHelper::addFormPath().
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @see FormHelper::addFormPath()
- * @since 1.7.0
- */
- public static function addFormPath($new = null)
- {
- return FormHelper::addFormPath($new);
- }
-
- /**
- * Proxy for FormHelper::addRulePath().
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @see FormHelper::addRulePath()
- * @since 1.7.0
- */
- public static function addRulePath($new = null)
- {
- return FormHelper::addRulePath($new);
- }
-
- /**
- * Proxy for FormHelper::addFilterPath().
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @see FormHelper::addFilterPath()
- * @since 4.0.0
- */
- public static function addFilterPath($new = null)
- {
- return FormHelper::addFilterPath($new);
- }
-
- /**
- * Method to get an instance of a form.
- *
- * @param string $name The name of the form.
- * @param string $data The name of an XML file or string to load as the form definition.
- * @param array $options An array of form options.
- * @param boolean $replace Flag to toggle whether form fields should be replaced if a field
- * already exists with the same group/name.
- * @param string|boolean $xpath An optional xpath to search for the fields.
- *
- * @return Form Form instance.
- *
- * @since 1.7.0
- * @deprecated 5.0 Use the FormFactory service from the container
- * @throws \InvalidArgumentException if no data provided.
- * @throws \RuntimeException if the form could not be loaded.
- */
- public static function getInstance($name, $data = null, $options = [], $replace = true, $xpath = false)
- {
- // Reference to array with form instances
- $forms = &self::$forms;
-
- // Only instantiate the form if it does not already exist.
- if (!isset($forms[$name]))
- {
- $data = trim($data);
-
- if (empty($data))
- {
- throw new \InvalidArgumentException(sprintf('%1$s(%2$s, *%3$s*)', __METHOD__, $name, \gettype($data)));
- }
-
- // Instantiate the form.
- $forms[$name] = Factory::getContainer()->get(FormFactoryInterface::class)->createForm($name, $options);
-
- // Load the data.
- if (substr($data, 0, 1) === '<')
- {
- if ($forms[$name]->load($data, $replace, $xpath) == false)
- {
- throw new \RuntimeException(sprintf('%s() could not load form', __METHOD__));
- }
- }
- else
- {
- if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
- {
- throw new \RuntimeException(sprintf('%s() could not load file', __METHOD__));
- }
- }
- }
-
- return $forms[$name];
- }
-
- /**
- * Adds a new child SimpleXMLElement node to the source.
- *
- * @param \SimpleXMLElement $source The source element on which to append.
- * @param \SimpleXMLElement $new The new element to append.
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected static function addNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
- {
- // Add the new child node.
- $node = $source->addChild($new->getName(), htmlspecialchars(trim($new)));
-
- // Add the attributes of the child node.
- foreach ($new->attributes() as $name => $value)
- {
- $node->addAttribute($name, $value);
- }
-
- // Add any children of the new node.
- foreach ($new->children() as $child)
- {
- self::addNode($node, $child);
- }
- }
-
- /**
- * Update the attributes of a child node
- *
- * @param \SimpleXMLElement $source The source element on which to append the attributes
- * @param \SimpleXMLElement $new The new element to append
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected static function mergeNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
- {
- // Update the attributes of the child node.
- foreach ($new->attributes() as $name => $value)
- {
- if (isset($source[$name]))
- {
- $source[$name] = (string) $value;
- }
- else
- {
- $source->addAttribute($name, $value);
- }
- }
- }
-
- /**
- * Merges new elements into a source `` element.
- *
- * @param \SimpleXMLElement $source The source element.
- * @param \SimpleXMLElement $new The new element to merge.
- *
- * @return void
- *
- * @since 1.7.0
- */
- protected static function mergeNodes(\SimpleXMLElement $source, \SimpleXMLElement $new)
- {
- // The assumption is that the inputs are at the same relative level.
- // So we just have to scan the children and deal with them.
-
- // Update the attributes of the child node.
- foreach ($new->attributes() as $name => $value)
- {
- if (isset($source[$name]))
- {
- $source[$name] = (string) $value;
- }
- else
- {
- $source->addAttribute($name, $value);
- }
- }
-
- foreach ($new->children() as $child)
- {
- $type = $child->getName();
- $name = $child['name'];
-
- // Does this node exist?
- $fields = $source->xpath($type . '[@name="' . $name . '"]');
-
- if (empty($fields))
- {
- // This node does not exist, so add it.
- self::addNode($source, $child);
- }
- else
- {
- // This node does exist.
- switch ($type)
- {
- case 'field':
- self::mergeNode($fields[0], $child);
- break;
-
- default:
- self::mergeNodes($fields[0], $child);
- break;
- }
- }
- }
- }
-
- /**
- * Returns the value of an attribute of the form itself
- *
- * @param string $name Name of the attribute to get
- * @param mixed $default Optional value to return if attribute not found
- *
- * @return mixed Value of the attribute / default
- *
- * @since 3.2
- */
- public function getAttribute($name, $default = null)
- {
- if ($this->xml instanceof \SimpleXMLElement)
- {
- $value = $this->xml->attributes()->$name;
-
- if ($value !== null)
- {
- return (string) $value;
- }
- }
-
- return $default;
- }
-
- /**
- * Getter for the form data
- *
- * @return Registry Object with the data
- *
- * @since 3.2
- */
- public function getData()
- {
- return $this->data;
- }
-
- /**
- * Method to get the XML form object
- *
- * @return \SimpleXMLElement The form XML object
- *
- * @since 3.2
- */
- public function getXml()
- {
- return $this->xml;
- }
-
- /**
- * Method to get a form field represented as an XML element object.
- *
- * @param string $name The name of the form field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- *
- * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error.
- *
- * @since 3.7.0
- */
- public function getFieldXml($name, $group = null)
- {
- return $this->findField($name, $group);
- }
+ use DatabaseAwareTrait;
+
+ /**
+ * The Registry data store for form fields during display.
+ *
+ * @var Registry
+ * @since 1.7.0
+ */
+ protected $data;
+
+ /**
+ * The form object errors array.
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $errors = [];
+
+ /**
+ * The name of the form instance.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $name;
+
+ /**
+ * The form object options for use in rendering and validation.
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected $options = [];
+
+ /**
+ * The form XML definition.
+ *
+ * @var \SimpleXMLElement
+ * @since 1.7.0
+ */
+ protected $xml;
+
+ /**
+ * Form instances.
+ *
+ * @var Form[]
+ * @since 1.7.0
+ */
+ protected static $forms = [];
+
+ /**
+ * Allows extensions to implement repeating elements
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ public $repeat = false;
+
+ /**
+ * Method to instantiate the form object.
+ *
+ * @param string $name The name of the form.
+ * @param array $options An array of form options.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($name, array $options = [])
+ {
+ // Set the name for the form.
+ $this->name = $name;
+
+ // Initialise the Registry data.
+ $this->data = new Registry();
+
+ // Set the options if specified.
+ $this->options['control'] = $options['control'] ?? false;
+ }
+
+ /**
+ * Method to bind data to the form.
+ *
+ * @param mixed $data An array or object of data to bind to the form.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function bind($data)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // The data must be an object or array.
+ if (!\is_object($data) && !\is_array($data)) {
+ return false;
+ }
+
+ $this->bindLevel(null, $data);
+
+ return true;
+ }
+
+ /**
+ * Method to bind data to the form for the group level.
+ *
+ * @param string $group The dot-separated form group path on which to bind the data.
+ * @param mixed $data An array or object of data to bind to the form for the group level.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected function bindLevel($group, $data)
+ {
+ // Ensure the input data is an array.
+ if (\is_object($data)) {
+ if ($data instanceof Registry) {
+ // Handle a Registry.
+ $data = $data->toArray();
+ } elseif ($data instanceof CMSObject) {
+ // Handle a CMSObject.
+ $data = $data->getProperties();
+ } else {
+ // Handle other types of objects.
+ $data = (array) $data;
+ }
+ }
+
+ // Process the input data.
+ foreach ($data as $k => $v) {
+ $level = $group ? $group . '.' . $k : $k;
+
+ if ($this->findField($k, $group)) {
+ // If the field exists set the value.
+ $this->data->set($level, $v);
+ } elseif (\is_object($v) || ArrayHelper::isAssociative($v)) {
+ // If the value is an object or an associative array, hand it off to the recursive bind level method.
+ $this->bindLevel($level, $v);
+ }
+ }
+ }
+
+ /**
+ * Return all errors, if any.
+ *
+ * @return array Array of error messages or RuntimeException objects.
+ *
+ * @since 1.7.0
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * Method to get a form field represented as a FormField object.
+ *
+ * @param string $name The name of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param mixed $value The optional value to use as the default for the field.
+ *
+ * @return FormField|boolean The FormField object for the field or boolean false on error.
+ *
+ * @since 1.7.0
+ */
+ public function getField($name, $group = null, $value = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Attempt to find the field by name and group.
+ $element = $this->findField($name, $group);
+
+ // If the field element was not found return false.
+ if (!$element) {
+ return false;
+ }
+
+ return $this->loadField($element, $group, $value);
+ }
+
+ /**
+ * Method to get an attribute value from a field XML element. If the attribute doesn't exist or
+ * is null then the optional default value will be used.
+ *
+ * @param string $name The name of the form field for which to get the attribute value.
+ * @param string $attribute The name of the attribute for which to get a value.
+ * @param mixed $default The optional default value to use if no attribute value exists.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return mixed The attribute value for the field.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function getFieldAttribute($name, $attribute, $default = null, $group = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Find the form field element from the definition.
+ $element = $this->findField($name, $group);
+
+ // If the element exists and the attribute exists for the field return the attribute value.
+ if (($element instanceof \SimpleXMLElement) && \strlen((string) $element[$attribute])) {
+ return (string) $element[$attribute];
+ } else {
+ // Otherwise return the given default value.
+ return $default;
+ }
+ }
+
+ /**
+ * Method to get an array of FormField objects in a given fieldset by name. If no name is
+ * given then all fields are returned.
+ *
+ * @param string $set The optional name of the fieldset.
+ *
+ * @return FormField[] The array of FormField objects in the fieldset.
+ *
+ * @since 1.7.0
+ */
+ public function getFieldset($set = null)
+ {
+ $fields = [];
+
+ // Get all of the field elements in the fieldset.
+ if ($set) {
+ $elements = $this->findFieldsByFieldset($set);
+ } else {
+ // Get all fields.
+ $elements = $this->findFieldsByGroup();
+ }
+
+ // If no field elements were found return empty.
+ if (empty($elements)) {
+ return $fields;
+ }
+
+ // Build the result array from the found field elements.
+ foreach ($elements as $element) {
+ // Get the field groups for the element.
+ $attrs = $element->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+ $group = implode('.', $groups);
+
+ // If the field is successfully loaded add it to the result array.
+ if ($field = $this->loadField($element, $group)) {
+ $fields[$field->id] = $field;
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Method to get an array of fieldset objects optionally filtered over a given field group.
+ *
+ * @param string $group The dot-separated form group path on which to filter the fieldsets.
+ *
+ * @return array The array of fieldset objects.
+ *
+ * @since 1.7.0
+ */
+ public function getFieldsets($group = null)
+ {
+ $fieldsets = [];
+ $sets = [];
+
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ if ($group) {
+ // Get the fields elements for a given group.
+ $elements = &$this->findGroup($group);
+
+ foreach ($elements as &$element) {
+ // Get an array of elements and fieldset attributes within the fields element.
+ if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset')) {
+ $sets = array_merge($sets, (array) $tmp);
+ }
+ }
+ } else {
+ // Get an array of elements and fieldset attributes.
+ $sets = $this->xml->xpath('//fieldset[@name and not(ancestor::field/form/*)] | //field[@fieldset and not(ancestor::field/form/*)]/@fieldset');
+ }
+
+ // If no fieldsets are found return empty.
+ if (empty($sets)) {
+ return $fieldsets;
+ }
+
+ // Process each found fieldset.
+ foreach ($sets as $set) {
+ if ((string) $set['hidden'] === 'true') {
+ continue;
+ }
+
+ // Are we dealing with a fieldset element?
+ if ((string) $set['name']) {
+ // Only create it if it doesn't already exist.
+ if (empty($fieldsets[(string) $set['name']])) {
+ // Build the fieldset object.
+ $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
+
+ foreach ($set->attributes() as $name => $value) {
+ $fieldset->$name = (string) $value;
+ }
+
+ // Add the fieldset object to the list.
+ $fieldsets[$fieldset->name] = $fieldset;
+ }
+ } else {
+ // Must be dealing with a fieldset attribute.
+ // Only create it if it doesn't already exist.
+ if (empty($fieldsets[(string) $set])) {
+ // Attempt to get the fieldset element for data (throughout the entire form document).
+ $tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');
+
+ // If no element was found, build a very simple fieldset object.
+ if (empty($tmp)) {
+ $fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
+ } else {
+ // Build the fieldset object from the element.
+ $fieldset = (object) array('name' => '', 'label' => '', 'description' => '');
+
+ foreach ($tmp[0]->attributes() as $name => $value) {
+ $fieldset->$name = (string) $value;
+ }
+ }
+
+ // Add the fieldset object to the list.
+ $fieldsets[$fieldset->name] = $fieldset;
+ }
+ }
+ }
+
+ return $fieldsets;
+ }
+
+ /**
+ * Method to get the form control. This string serves as a container for all form fields. For
+ * example, if there is a field named 'foo' and a field named 'bar' and the form control is
+ * empty the fields will be rendered like: ` ` and ` `. If
+ * the form control is set to 'joomla' however, the fields would be rendered like:
+ * ` ` and ` `.
+ *
+ * @return string The form control string.
+ *
+ * @since 1.7.0
+ */
+ public function getFormControl()
+ {
+ return (string) $this->options['control'];
+ }
+
+ /**
+ * Method to get an array of FormField objects in a given field group by name.
+ *
+ * @param string $group The dot-separated form group path for which to get the form fields.
+ * @param boolean $nested True to also include fields in nested groups that are inside of the
+ * group for which to find fields.
+ *
+ * @return FormField[] The array of FormField objects in the field group.
+ *
+ * @since 1.7.0
+ */
+ public function getGroup($group, $nested = false)
+ {
+ $fields = [];
+
+ // Get all of the field elements in the field group.
+ $elements = $this->findFieldsByGroup($group, $nested);
+
+ // If no field elements were found return empty.
+ if (empty($elements)) {
+ return $fields;
+ }
+
+ // Build the result array from the found field elements.
+ foreach ($elements as $element) {
+ // Get the field groups for the element.
+ $attrs = $element->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+ $group = implode('.', $groups);
+
+ // If the field is successfully loaded add it to the result array.
+ if ($field = $this->loadField($element, $group)) {
+ $fields[$field->id] = $field;
+ }
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Method to get a form field markup for the field input.
+ *
+ * @param string $name The name of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param mixed $value The optional value to use as the default for the field.
+ *
+ * @return string The form field markup.
+ *
+ * @since 1.7.0
+ */
+ public function getInput($name, $group = null, $value = null)
+ {
+ // Attempt to get the form field.
+ if ($field = $this->getField($name, $group, $value)) {
+ return $field->input;
+ }
+
+ return '';
+ }
+
+ /**
+ * Method to get the label for a field input.
+ *
+ * @param string $name The name of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return string The form field label.
+ *
+ * @since 1.7.0
+ */
+ public function getLabel($name, $group = null)
+ {
+ // Attempt to get the form field.
+ if ($field = $this->getField($name, $group)) {
+ return $field->label;
+ }
+
+ return '';
+ }
+
+ /**
+ * Method to get the form name.
+ *
+ * @return string The name of the form.
+ *
+ * @since 1.7.0
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Method to get the value of a field.
+ *
+ * @param string $name The name of the field for which to get the value.
+ * @param string $group The optional dot-separated form group path on which to get the value.
+ * @param mixed $default The optional default value of the field value is empty.
+ *
+ * @return mixed The value of the field or the default value if empty.
+ *
+ * @since 1.7.0
+ */
+ public function getValue($name, $group = null, $default = null)
+ {
+ // If a group is set use it.
+ if ($group) {
+ $return = $this->data->get($group . '.' . $name, $default);
+ } else {
+ $return = $this->data->get($name, $default);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to get a control group with label and input.
+ *
+ * @param string $name The name of the field for which to get the value.
+ * @param string $group The optional dot-separated form group path on which to get the value.
+ * @param mixed $default The optional default value of the field value is empty.
+ * @param array $options Any options to be passed into the rendering of the field
+ *
+ * @return string A string containing the html for the control group
+ *
+ * @since 3.2.3
+ */
+ public function renderField($name, $group = null, $default = null, $options = [])
+ {
+ $field = $this->getField($name, $group, $default);
+
+ if ($field) {
+ return $field->renderField($options);
+ }
+
+ return '';
+ }
+
+ /**
+ * Method to get all control groups with label and input of a fieldset.
+ *
+ * @param string $name The name of the fieldset for which to get the values.
+ * @param array $options Any options to be passed into the rendering of the field
+ *
+ * @return string A string containing the html for the control groups
+ *
+ * @since 3.2.3
+ */
+ public function renderFieldset($name, $options = [])
+ {
+ $fields = $this->getFieldset($name);
+ $html = [];
+
+ foreach ($fields as $field) {
+ $html[] = $field->renderField($options);
+ }
+
+ return implode('', $html);
+ }
+
+ /**
+ * Method to load the form description from an XML string or object.
+ *
+ * The replace option works per field. If a field being loaded already exists in the current
+ * form definition then the behavior or load will vary depending upon the replace flag. If it
+ * is set to true, then the existing field will be replaced in its exact location by the new
+ * field being loaded. If it is false, then the new field being loaded will be ignored and the
+ * method will move on to the next field to load.
+ *
+ * @param string $data The name of an XML string or object.
+ * @param boolean $replace Flag to toggle whether form fields should be replaced if a field
+ * already exists with the same group/name.
+ * @param string $xpath An optional xpath to search for the fields.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function load($data, $replace = true, $xpath = null)
+ {
+ // If the data to load isn't already an XML element or string return false.
+ if ((!($data instanceof \SimpleXMLElement)) && (!\is_string($data))) {
+ return false;
+ }
+
+ // Attempt to load the XML if a string.
+ if (\is_string($data)) {
+ try {
+ $data = new \SimpleXMLElement($data);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ // If we have no XML definition at this point let's make sure we get one.
+ if (empty($this->xml)) {
+ // If no XPath query is set to search for fields, and we have a , set it and return.
+ if (!$xpath && ($data->getName() === 'form')) {
+ $this->xml = $data;
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ } else {
+ // Create a root element for the form.
+ $this->xml = new \SimpleXMLElement(' ');
+ }
+ }
+
+ // Get the XML elements to load.
+ $elements = [];
+
+ if ($xpath) {
+ $elements = $data->xpath($xpath);
+ } elseif ($data->getName() === 'form') {
+ $elements = $data->children();
+ }
+
+ // If there is nothing to load return true.
+ if (empty($elements)) {
+ return true;
+ }
+
+ // Load the found form elements.
+ foreach ($elements as $element) {
+ // Get an array of fields with the correct name.
+ $fields = $element->xpath('descendant-or-self::field');
+
+ foreach ($fields as $field) {
+ // Get the group names as strings for ancestor fields elements.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+
+ // Check to see if the field exists in the current form.
+ if ($current = $this->findField((string) $field['name'], implode('.', $groups))) {
+ // If set to replace found fields, replace the data and remove the field so we don't add it twice.
+ if ($replace) {
+ $olddom = dom_import_simplexml($current);
+ $loadeddom = dom_import_simplexml($field);
+ $addeddom = $olddom->ownerDocument->importNode($loadeddom, true);
+ $olddom->parentNode->replaceChild($addeddom, $olddom);
+ $loadeddom->parentNode->removeChild($loadeddom);
+ } else {
+ unset($field);
+ }
+ }
+ }
+
+ // Merge the new field data into the existing XML document.
+ self::addNode($this->xml, $element);
+ }
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ }
+
+ /**
+ * Method to load the form description from an XML file.
+ *
+ * The reset option works on a group basis. If the XML file references
+ * groups that have already been created they will be replaced with the
+ * fields in the new XML file unless the $reset parameter has been set
+ * to false.
+ *
+ * @param string $file The filesystem path of an XML file.
+ * @param boolean $reset Flag to toggle whether form fields should be replaced if a field
+ * already exists with the same group/name.
+ * @param string $xpath An optional xpath to search for the fields.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function loadFile($file, $reset = true, $xpath = null)
+ {
+ // Check to see if the path is an absolute path.
+ if (!is_file($file)) {
+ // Not an absolute path so let's attempt to find one using JPath.
+ $file = Path::find(self::addFormPath(), strtolower($file) . '.xml');
+
+ // If unable to find the file return false.
+ if (!$file) {
+ return false;
+ }
+ }
+
+ // Attempt to load the XML file.
+ $xml = simplexml_load_file($file);
+
+ return $this->load($xml, $reset, $xpath);
+ }
+
+ /**
+ * Method to remove a field from the form definition.
+ *
+ * @param string $name The name of the form field for which remove.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return boolean True on success, false otherwise.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function removeField($name, $group = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Find the form field element from the definition.
+ $element = $this->findField($name, $group);
+
+ // If the element exists remove it from the form definition.
+ if ($element instanceof \SimpleXMLElement) {
+ $dom = dom_import_simplexml($element);
+ $dom->parentNode->removeChild($dom);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method to remove a group from the form definition.
+ *
+ * @param string $group The dot-separated form group path for the group to remove.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function removeGroup($group)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Get the fields elements for a given group.
+ $elements = &$this->findGroup($group);
+
+ foreach ($elements as &$element) {
+ $dom = dom_import_simplexml($element);
+ $dom->parentNode->removeChild($dom);
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to reset the form data store and optionally the form XML definition.
+ *
+ * @param boolean $xml True to also reset the XML form definition.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function reset($xml = false)
+ {
+ unset($this->data);
+ $this->data = new Registry();
+
+ if ($xml) {
+ unset($this->xml);
+ $this->xml = new \SimpleXMLElement(' ');
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to set a field XML element to the form definition. If the replace flag is set then
+ * the field will be set whether it already exists or not. If it isn't set, then the field
+ * will not be replaced if it already exists.
+ *
+ * @param \SimpleXMLElement $element The XML element object representation of the form field.
+ * @param string $group The optional dot-separated form group path on which to set the field.
+ * @param boolean $replace True to replace an existing field if one already exists.
+ * @param string $fieldset The name of the fieldset we are adding the field to.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function setField(\SimpleXMLElement $element, $group = null, $replace = true, $fieldset = 'default')
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Find the form field element from the definition.
+ $old = $this->findField((string) $element['name'], $group);
+
+ // If an existing field is found and replace flag is false do nothing and return true.
+ if (!$replace && !empty($old)) {
+ return true;
+ }
+
+ // If an existing field is found and replace flag is true remove the old field.
+ if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement)) {
+ $dom = dom_import_simplexml($old);
+
+ // Get the parent element, this should be the fieldset
+ $parent = $dom->parentNode;
+ $fieldset = $parent->getAttribute('name');
+
+ $parent->removeChild($dom);
+ }
+
+ // Create the search path
+ $path = '//';
+
+ if (!empty($group)) {
+ $path .= 'fields[@name="' . $group . '"]/';
+ }
+
+ $path .= 'fieldset[@name="' . $fieldset . '"]';
+
+ $fs = $this->xml->xpath($path);
+
+ if (isset($fs[0]) && ($fs[0] instanceof \SimpleXMLElement)) {
+ // Add field to the form.
+ self::addNode($fs[0], $element);
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ }
+
+ // We couldn't find a fieldset to add the field. Now we are checking, if we have set only a group
+ if (!empty($group)) {
+ $fields = &$this->findGroup($group);
+
+ // If an appropriate fields element was found for the group, add the element.
+ if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement)) {
+ self::addNode($fields[0], $element);
+ }
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ }
+
+ // We couldn't find a parent so we are adding it at root level
+
+ // Add field to the form.
+ self::addNode($this->xml, $element);
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ }
+
+ /**
+ * Method to set an attribute value for a field XML element.
+ *
+ * @param string $name The name of the form field for which to set the attribute value.
+ * @param string $attribute The name of the attribute for which to set a value.
+ * @param mixed $value The value to set for the attribute.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function setFieldAttribute($name, $attribute, $value, $group = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Find the form field element from the definition.
+ $element = $this->findField($name, $group);
+
+ // If the element doesn't exist return false.
+ if (!($element instanceof \SimpleXMLElement)) {
+ return false;
+ } else {
+ // Otherwise set the attribute and return true.
+ $element[$attribute] = $value;
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return true;
+ }
+ }
+
+ /**
+ * Method to set some field XML elements to the form definition. If the replace flag is set then
+ * the fields will be set whether they already exists or not. If it isn't set, then the fields
+ * will not be replaced if they already exist.
+ *
+ * @param array &$elements The array of XML element object representations of the form fields.
+ * @param string $group The optional dot-separated form group path on which to set the fields.
+ * @param boolean $replace True to replace existing fields if they already exist.
+ * @param string $fieldset The name of the fieldset we are adding the field to.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function setFields(&$elements, $group = null, $replace = true, $fieldset = 'default')
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Make sure the elements to set are valid.
+ foreach ($elements as $element) {
+ if (!($element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+ }
+
+ // Set the fields.
+ $return = true;
+
+ foreach ($elements as $element) {
+ if (!$this->setField($element, $group, $replace, $fieldset)) {
+ $return = false;
+ }
+ }
+
+ // Synchronize any paths found in the load.
+ $this->syncPaths();
+
+ return $return;
+ }
+
+ /**
+ * Method to set the value of a field. If the field does not exist in the form then the method
+ * will return false.
+ *
+ * @param string $name The name of the field for which to set the value.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param mixed $value The value to set for the field.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function setValue($name, $group = null, $value = null)
+ {
+ // If the field does not exist return false.
+ if (!$this->findField($name, $group)) {
+ return false;
+ }
+
+ // If a group is set use it.
+ if ($group) {
+ $this->data->set($group . '.' . $name, $value);
+ } else {
+ $this->data->set($name, $value);
+ }
+
+ return true;
+ }
+
+ /**
+ * Method to process the form data.
+ *
+ * @param array $data An array of field values to filter.
+ * @param string $group The dot-separated form group path on which to filter the fields.
+ *
+ * @return mixed Array or false.
+ *
+ * @since 4.0.0
+ */
+ public function process($data, $group = null)
+ {
+ $data = $this->filter($data, $group);
+
+ $valid = $this->validate($data, $group);
+
+ if (!$valid) {
+ return $valid;
+ }
+
+ return $this->postProcess($data, $group);
+ }
+
+ /**
+ * Method to filter the form data.
+ *
+ * @param array $data An array of field values to filter.
+ * @param string $group The dot-separated form group path on which to filter the fields.
+ *
+ * @return mixed Array or false.
+ *
+ * @since 4.0.0
+ */
+ public function filter($data, $group = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ $input = new Registry($data);
+ $output = new Registry();
+
+ // Get the fields for which to filter the data.
+ $fields = $this->findFieldsByGroup($group);
+
+ if (!$fields) {
+ // PANIC!
+ return false;
+ }
+
+ // Filter the fields.
+ foreach ($fields as $field) {
+ $name = (string) $field['name'];
+
+ // Get the field groups for the element.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+ $attrGroup = implode('.', $groups);
+
+ $key = $attrGroup ? $attrGroup . '.' . $name : $name;
+
+ // Filter the value if it exists.
+ if ($input->exists($key)) {
+ $fieldObj = $this->loadField($field, $group);
+
+ // Only set into the output if the field was supposed to render on the page (i.e. setup returned true)
+ if ($fieldObj) {
+ $output->set($key, $fieldObj->filter($input->get($key, (string) $field['default']), $group, $input));
+ }
+ }
+ }
+
+ return $output->toArray();
+ }
+
+ /**
+ * Method to validate form data.
+ *
+ * Validation warnings will be pushed into JForm::errors and should be
+ * retrieved with JForm::getErrors() when validate returns boolean false.
+ *
+ * @param array $data An array of field values to validate.
+ * @param string $group The optional dot-separated form group path on which to filter the
+ * fields to be validated.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function validate($data, $group = null)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ $return = true;
+
+ // Create an input registry object from the data to validate.
+ $input = new Registry($data);
+
+ // Get the fields for which to validate the data.
+ $fields = $this->findFieldsByGroup($group);
+
+ if (!$fields) {
+ // PANIC!
+ return false;
+ }
+
+ // Validate the fields.
+ foreach ($fields as $field) {
+ $name = (string) $field['name'];
+
+ // Define field name for messages
+ if ($field['label']) {
+ $fieldLabel = $field['label'];
+
+ // Try to translate label if not set to false
+ $translate = (string) $field['translateLabel'];
+
+ if (!($translate === 'false' || $translate === 'off' || $translate === '0')) {
+ $fieldLabel = Text::_($fieldLabel);
+ }
+ } else {
+ $fieldLabel = Text::_($name);
+ }
+
+ $disabled = ((string) $field['disabled'] === 'true' || (string) $field['disabled'] === 'disabled');
+
+ $fieldExistsInRequestData = $input->exists($name) || $input->exists($group . '.' . $name);
+
+ // If the field is disabled but it is passed in the request this is invalid as disabled fields are not added to the request
+ if ($disabled && $fieldExistsInRequestData) {
+ throw new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel));
+ }
+
+ // Get the field groups for the element.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+ $attrGroup = implode('.', $groups);
+
+ $key = $attrGroup ? $attrGroup . '.' . $name : $name;
+
+ $fieldObj = $this->loadField($field, $attrGroup);
+
+ if ($fieldObj) {
+ $valid = $fieldObj->validate($input->get($key), $attrGroup, $input);
+
+ // Check for an error.
+ if ($valid instanceof \Exception) {
+ $this->errors[] = $valid;
+ $return = false;
+ }
+ } elseif (!$fieldObj && $input->exists($key)) {
+ // The field returned false from setup and shouldn't be included in the page body - yet we received
+ // a value for it. This is probably some sort of injection attack and should be rejected
+ $this->errors[] = new \RuntimeException(Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $key));
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Method to post-process form data.
+ *
+ * @param array $data An array of field values to post-process.
+ * @param string $group The optional dot-separated form group path on which to filter the
+ * fields to be validated.
+ *
+ * @return mixed Array or false.
+ *
+ * @since 4.0.0
+ */
+ public function postProcess($data, $group = null)
+ {
+ // Make sure there is a valid SimpleXMLElement
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ $input = new Registry($data);
+ $output = new Registry();
+
+ // Get the fields for which to postProcess the data.
+ $fields = $this->findFieldsByGroup($group);
+
+ if (!$fields) {
+ // PANIC!
+ return false;
+ }
+
+ // Filter the fields.
+ foreach ($fields as $field) {
+ $name = (string) $field['name'];
+
+ // Get the field groups for the element.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $groups = array_map('strval', $attrs ?: []);
+ $attrGroup = implode('.', $groups);
+
+ $key = $attrGroup ? $attrGroup . '.' . $name : $name;
+
+ // Filter the value if it exists.
+ if ($input->exists($key)) {
+ $fieldobj = $this->loadField($field, $group);
+ $output->set($key, $fieldobj->postProcess($input->get($key, (string) $field['default']), $group, $input));
+ }
+ }
+
+ return $output->toArray();
+ }
+
+ /**
+ * Method to get a form field represented as an XML element object.
+ *
+ * @param string $name The name of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error.
+ *
+ * @since 1.7.0
+ */
+ protected function findField($name, $group = null)
+ {
+ $element = false;
+ $fields = [];
+
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Let's get the appropriate field element based on the method arguments.
+ if ($group) {
+ // Get the fields elements for a given group.
+ $elements = &$this->findGroup($group);
+
+ // Get all of the field elements with the correct name for the fields elements.
+ foreach ($elements as $el) {
+ // If there are matching field elements add them to the fields array.
+ if ($tmp = $el->xpath('descendant::field[@name="' . $name . '" and not(ancestor::field/form/*)]')) {
+ $fields = array_merge($fields, $tmp);
+ }
+ }
+
+ // Make sure something was found.
+ if (!$fields) {
+ return false;
+ }
+
+ // Use the first correct match in the given group.
+ $groupNames = explode('.', $group);
+
+ foreach ($fields as &$field) {
+ // Get the group names as strings for ancestor fields elements.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $names = array_map('strval', $attrs ?: []);
+
+ // If the field is in the exact group use it and break out of the loop.
+ if ($names == (array) $groupNames) {
+ $element = &$field;
+ break;
+ }
+ }
+ } else {
+ // Get an array of fields with the correct name.
+ $fields = $this->xml->xpath('//field[@name="' . $name . '" and not(ancestor::field/form/*)]');
+
+ // Make sure something was found.
+ if (!$fields) {
+ return false;
+ }
+
+ // Search through the fields for the right one.
+ foreach ($fields as &$field) {
+ // If we find an ancestor fields element with a group name then it isn't what we want.
+ if ($field->xpath('ancestor::fields[@name]')) {
+ continue;
+ } else {
+ // Found it!
+ $element = &$field;
+ break;
+ }
+ }
+ }
+
+ return $element;
+ }
+
+ /**
+ * Method to get an array of `` elements from the form XML document which are in a specified fieldset by name.
+ *
+ * @param string $name The name of the fieldset.
+ *
+ * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects.
+ *
+ * @since 1.7.0
+ */
+ protected function &findFieldsByFieldset($name)
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ /*
+ * Get an array of elements that are underneath a element
+ * with the appropriate name attribute, and also any elements with
+ * the appropriate fieldset attribute. To allow repeatable elements only fields
+ * which are not descendants of other fields are selected.
+ */
+ $fields = $this->xml->xpath('(//fieldset[@name="' . $name . '"]//field | //field[@fieldset="' . $name . '"])[not(ancestor::field)]');
+
+ return $fields;
+ }
+
+ /**
+ * Method to get an array of `` elements from the form XML document which are in a control group by name.
+ *
+ * @param mixed $group The optional dot-separated form group path on which to find the fields.
+ * Null will return all fields. False will return fields not in a group.
+ * @param boolean $nested True to also include fields in nested groups that are inside of the
+ * group for which to find fields.
+ *
+ * @return \SimpleXMLElement[]|boolean Boolean false on error or array of SimpleXMLElement objects.
+ *
+ * @since 1.7.0
+ */
+ protected function &findFieldsByGroup($group = null, $nested = false)
+ {
+ $fields = [];
+
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Get only fields in a specific group?
+ if ($group) {
+ // Get the fields elements for a given group.
+ $elements = &$this->findGroup($group);
+
+ // Get all of the field elements for the fields elements.
+ foreach ($elements as $element) {
+ // If there are field elements add them to the return result.
+ if ($tmp = $element->xpath('descendant::field')) {
+ // If we also want fields in nested groups then just merge the arrays.
+ if ($nested) {
+ $fields = array_merge($fields, $tmp);
+ } else {
+ // If we want to exclude nested groups then we need to check each field.
+ $groupNames = explode('.', $group);
+
+ foreach ($tmp as $field) {
+ // Get the names of the groups that the field is in.
+ $attrs = $field->xpath('ancestor::fields[@name]/@name');
+ $names = array_map('strval', $attrs ?: []);
+
+ // If the field is in the specific group then add it to the return list.
+ if ($names == (array) $groupNames) {
+ $fields = array_merge($fields, array($field));
+ }
+ }
+ }
+ }
+ }
+ } elseif ($group === false) {
+ // Get only field elements not in a group.
+ $fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
+ } else {
+ // Get an array of all the elements.
+ $fields = $this->xml->xpath('//field[not(ancestor::field/form/*)]');
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Method to get a form field group represented as an XML element object.
+ *
+ * @param string $group The dot-separated form group path on which to find the group.
+ *
+ * @return \SimpleXMLElement[]|boolean An array of XML element objects for the group or boolean false on error.
+ *
+ * @since 1.7.0
+ */
+ protected function &findGroup($group)
+ {
+ $groups = [];
+ $tmp = [];
+
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Make sure there is actually a group to find.
+ $group = explode('.', $group);
+
+ if (!empty($group)) {
+ // Get any fields elements with the correct group name.
+ $elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '" and not(ancestor::field/form/*)]');
+
+ // Check to make sure that there are no parent groups for each element.
+ foreach ($elements as $element) {
+ if (!$element->xpath('ancestor::fields[@name]')) {
+ $tmp[] = $element;
+ }
+ }
+
+ // Iterate through the nested groups to find any matching form field groups.
+ for ($i = 1, $n = \count($group); $i < $n; $i++) {
+ // Initialise some loop variables.
+ $validNames = \array_slice($group, 0, $i + 1);
+ $current = $tmp;
+ $tmp = [];
+
+ // Check to make sure that there are no parent groups for each element.
+ foreach ($current as $element) {
+ // Get any fields elements with the correct group name.
+ $children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');
+
+ // For the found fields elements validate that they are in the correct groups.
+ foreach ($children as $fields) {
+ // Get the group names as strings for ancestor fields elements.
+ $attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
+ $names = array_map('strval', $attrs ?: []);
+
+ // If the group names for the fields element match the valid names at this
+ // level add the fields element.
+ if ($validNames == $names) {
+ $tmp[] = $fields;
+ }
+ }
+ }
+ }
+
+ // Only include valid XML objects.
+ foreach ($tmp as $element) {
+ if ($element instanceof \SimpleXMLElement) {
+ $groups[] = $element;
+ }
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Method to load, setup and return a FormField object based on field data.
+ *
+ * @param string $element The XML element object representation of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param mixed $value The optional value to use as the default for the field.
+ *
+ * @return FormField|boolean The FormField object for the field or boolean false on error.
+ *
+ * @since 1.7.0
+ */
+ protected function loadField($element, $group = null, $value = null)
+ {
+ // Make sure there is a valid SimpleXMLElement.
+ if (!($element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Get the field type.
+ $type = $element['type'] ? (string) $element['type'] : 'text';
+
+ // Load the FormField object for the field.
+ $field = FormHelper::loadFieldType($type);
+
+ if ($field instanceof DatabaseAwareInterface) {
+ try {
+ $field->setDatabase($this->getDatabase());
+ } catch (DatabaseNotFoundException $e) {
+ @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
+ $field->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
+ }
+ }
+
+ // If the object could not be loaded, get a text field object.
+ if ($field === false) {
+ $field = FormHelper::loadFieldType('text');
+ }
+
+ /*
+ * Get the value for the form field if not set.
+ * Default to the translated version of the 'default' attribute
+ * if 'translate_default' attribute if set to 'true' or '1'
+ * else the value of the 'default' attribute for the field.
+ */
+ if ($value === null) {
+ $default = (string) ($element['default'] ? $element['default'] : $element->default);
+
+ if (($translate = $element['translate_default']) && ((string) $translate === 'true' || (string) $translate === '1')) {
+ $lang = Factory::getLanguage();
+
+ if ($lang->hasKey($default)) {
+ $debug = $lang->setDebug(false);
+ $default = Text::_($default);
+ $lang->setDebug($debug);
+ } else {
+ $default = Text::_($default);
+ }
+ }
+
+ $value = $this->getValue((string) $element['name'], $group, $default);
+ }
+
+ // Setup the FormField object.
+ $field->setForm($this);
+
+ if ($field->setup($element, $value, $group)) {
+ return $field;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Method to synchronize any field, form or rule paths contained in the XML document.
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ * @todo Maybe we should receive all addXXXpaths attributes at once?
+ */
+ protected function syncPaths()
+ {
+ // Make sure there is a valid Form XML document.
+ if (!($this->xml instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::%s `xml` is not an instance of SimpleXMLElement', \get_class($this), __METHOD__));
+ }
+
+ // Get any addfieldpath attributes from the form definition.
+ $paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
+ $paths = array_map('strval', $paths ?: []);
+
+ // Add the field paths.
+ foreach ($paths as $path) {
+ $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
+ self::addFieldPath($path);
+ }
+
+ // Get any addformpath attributes from the form definition.
+ $paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
+ $paths = array_map('strval', $paths ?: []);
+
+ // Add the form paths.
+ foreach ($paths as $path) {
+ $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
+ self::addFormPath($path);
+ }
+
+ // Get any addrulepath attributes from the form definition.
+ $paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
+ $paths = array_map('strval', $paths ?: []);
+
+ // Add the rule paths.
+ foreach ($paths as $path) {
+ $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
+ self::addRulePath($path);
+ }
+
+ // Get any addrulepath attributes from the form definition.
+ $paths = $this->xml->xpath('//*[@addfilterpath]/@addfilterpath');
+ $paths = array_map('strval', $paths ?: []);
+
+ // Add the rule paths.
+ foreach ($paths as $path) {
+ $path = JPATH_ROOT . '/' . ltrim($path, '/\\');
+ self::addFilterPath($path);
+ }
+
+ // Get any addfieldprefix attributes from the form definition.
+ $prefixes = $this->xml->xpath('//*[@addfieldprefix]/@addfieldprefix');
+ $prefixes = array_map('strval', $prefixes ?: []);
+
+ // Add the field prefixes.
+ foreach ($prefixes as $prefix) {
+ FormHelper::addFieldPrefix($prefix);
+ }
+
+ // Get any addformprefix attributes from the form definition.
+ $prefixes = $this->xml->xpath('//*[@addformprefix]/@addformprefix');
+ $prefixes = array_map('strval', $prefixes ?: []);
+
+ // Add the field prefixes.
+ foreach ($prefixes as $prefix) {
+ FormHelper::addFormPrefix($prefix);
+ }
+
+ // Get any addruleprefix attributes from the form definition.
+ $prefixes = $this->xml->xpath('//*[@addruleprefix]/@addruleprefix');
+ $prefixes = array_map('strval', $prefixes ?: []);
+
+ // Add the field prefixes.
+ foreach ($prefixes as $prefix) {
+ FormHelper::addRulePrefix($prefix);
+ }
+
+ // Get any addruleprefix attributes from the form definition.
+ $prefixes = $this->xml->xpath('//*[@addfilterprefix]/@addfilterprefix');
+ $prefixes = array_map('strval', $prefixes ?: []);
+
+ // Add the field prefixes.
+ foreach ($prefixes as $prefix) {
+ FormHelper::addFilterPrefix($prefix);
+ }
+
+ return true;
+ }
+
+ /**
+ * Proxy for {@link FormHelper::addFieldPath()}.
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 1.7.0
+ */
+ public static function addFieldPath($new = null)
+ {
+ return FormHelper::addFieldPath($new);
+ }
+
+ /**
+ * Proxy for FormHelper::addFormPath().
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @see FormHelper::addFormPath()
+ * @since 1.7.0
+ */
+ public static function addFormPath($new = null)
+ {
+ return FormHelper::addFormPath($new);
+ }
+
+ /**
+ * Proxy for FormHelper::addRulePath().
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @see FormHelper::addRulePath()
+ * @since 1.7.0
+ */
+ public static function addRulePath($new = null)
+ {
+ return FormHelper::addRulePath($new);
+ }
+
+ /**
+ * Proxy for FormHelper::addFilterPath().
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @see FormHelper::addFilterPath()
+ * @since 4.0.0
+ */
+ public static function addFilterPath($new = null)
+ {
+ return FormHelper::addFilterPath($new);
+ }
+
+ /**
+ * Method to get an instance of a form.
+ *
+ * @param string $name The name of the form.
+ * @param string $data The name of an XML file or string to load as the form definition.
+ * @param array $options An array of form options.
+ * @param boolean $replace Flag to toggle whether form fields should be replaced if a field
+ * already exists with the same group/name.
+ * @param string|boolean $xpath An optional xpath to search for the fields.
+ *
+ * @return Form Form instance.
+ *
+ * @since 1.7.0
+ * @deprecated 5.0 Use the FormFactory service from the container
+ * @throws \InvalidArgumentException if no data provided.
+ * @throws \RuntimeException if the form could not be loaded.
+ */
+ public static function getInstance($name, $data = null, $options = [], $replace = true, $xpath = false)
+ {
+ // Reference to array with form instances
+ $forms = &self::$forms;
+
+ // Only instantiate the form if it does not already exist.
+ if (!isset($forms[$name])) {
+ $data = trim($data);
+
+ if (empty($data)) {
+ throw new \InvalidArgumentException(sprintf('%1$s(%2$s, *%3$s*)', __METHOD__, $name, \gettype($data)));
+ }
+
+ // Instantiate the form.
+ $forms[$name] = Factory::getContainer()->get(FormFactoryInterface::class)->createForm($name, $options);
+
+ // Load the data.
+ if (substr($data, 0, 1) === '<') {
+ if ($forms[$name]->load($data, $replace, $xpath) == false) {
+ throw new \RuntimeException(sprintf('%s() could not load form', __METHOD__));
+ }
+ } else {
+ if ($forms[$name]->loadFile($data, $replace, $xpath) == false) {
+ throw new \RuntimeException(sprintf('%s() could not load file', __METHOD__));
+ }
+ }
+ }
+
+ return $forms[$name];
+ }
+
+ /**
+ * Adds a new child SimpleXMLElement node to the source.
+ *
+ * @param \SimpleXMLElement $source The source element on which to append.
+ * @param \SimpleXMLElement $new The new element to append.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected static function addNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
+ {
+ // Add the new child node.
+ $node = $source->addChild($new->getName(), htmlspecialchars(trim($new)));
+
+ // Add the attributes of the child node.
+ foreach ($new->attributes() as $name => $value) {
+ $node->addAttribute($name, $value);
+ }
+
+ // Add any children of the new node.
+ foreach ($new->children() as $child) {
+ self::addNode($node, $child);
+ }
+ }
+
+ /**
+ * Update the attributes of a child node
+ *
+ * @param \SimpleXMLElement $source The source element on which to append the attributes
+ * @param \SimpleXMLElement $new The new element to append
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected static function mergeNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
+ {
+ // Update the attributes of the child node.
+ foreach ($new->attributes() as $name => $value) {
+ if (isset($source[$name])) {
+ $source[$name] = (string) $value;
+ } else {
+ $source->addAttribute($name, $value);
+ }
+ }
+ }
+
+ /**
+ * Merges new elements into a source `` element.
+ *
+ * @param \SimpleXMLElement $source The source element.
+ * @param \SimpleXMLElement $new The new element to merge.
+ *
+ * @return void
+ *
+ * @since 1.7.0
+ */
+ protected static function mergeNodes(\SimpleXMLElement $source, \SimpleXMLElement $new)
+ {
+ // The assumption is that the inputs are at the same relative level.
+ // So we just have to scan the children and deal with them.
+
+ // Update the attributes of the child node.
+ foreach ($new->attributes() as $name => $value) {
+ if (isset($source[$name])) {
+ $source[$name] = (string) $value;
+ } else {
+ $source->addAttribute($name, $value);
+ }
+ }
+
+ foreach ($new->children() as $child) {
+ $type = $child->getName();
+ $name = $child['name'];
+
+ // Does this node exist?
+ $fields = $source->xpath($type . '[@name="' . $name . '"]');
+
+ if (empty($fields)) {
+ // This node does not exist, so add it.
+ self::addNode($source, $child);
+ } else {
+ // This node does exist.
+ switch ($type) {
+ case 'field':
+ self::mergeNode($fields[0], $child);
+ break;
+
+ default:
+ self::mergeNodes($fields[0], $child);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the value of an attribute of the form itself
+ *
+ * @param string $name Name of the attribute to get
+ * @param mixed $default Optional value to return if attribute not found
+ *
+ * @return mixed Value of the attribute / default
+ *
+ * @since 3.2
+ */
+ public function getAttribute($name, $default = null)
+ {
+ if ($this->xml instanceof \SimpleXMLElement) {
+ $value = $this->xml->attributes()->$name;
+
+ if ($value !== null) {
+ return (string) $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Getter for the form data
+ *
+ * @return Registry Object with the data
+ *
+ * @since 3.2
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Method to get the XML form object
+ *
+ * @return \SimpleXMLElement The form XML object
+ *
+ * @since 3.2
+ */
+ public function getXml()
+ {
+ return $this->xml;
+ }
+
+ /**
+ * Method to get a form field represented as an XML element object.
+ *
+ * @param string $name The name of the form field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ *
+ * @return \SimpleXMLElement|boolean The XML element object for the field or boolean false on error.
+ *
+ * @since 3.7.0
+ */
+ public function getFieldXml($name, $group = null)
+ {
+ return $this->findField($name, $group);
+ }
}
diff --git a/libraries/src/Form/FormFactory.php b/libraries/src/Form/FormFactory.php
index 27fc62af3ee36..604ffba838d54 100644
--- a/libraries/src/Form/FormFactory.php
+++ b/libraries/src/Form/FormFactory.php
@@ -1,4 +1,5 @@
setDatabase($this->getDatabase());
+ $form->setDatabase($this->getDatabase());
- return $form;
- }
+ return $form;
+ }
}
diff --git a/libraries/src/Form/FormFactoryAwareInterface.php b/libraries/src/Form/FormFactoryAwareInterface.php
index b686b995ffa2b..9bdaaeab92487 100644
--- a/libraries/src/Form/FormFactoryAwareInterface.php
+++ b/libraries/src/Form/FormFactoryAwareInterface.php
@@ -1,4 +1,5 @@
formFactory)
- {
- return $this->formFactory;
- }
+ /**
+ * Get the FormFactoryInterface.
+ *
+ * @return FormFactoryInterface
+ *
+ * @since 4.0.0
+ * @throws \UnexpectedValueException May be thrown if the FormFactory has not been set.
+ */
+ public function getFormFactory(): FormFactoryInterface
+ {
+ if ($this->formFactory) {
+ return $this->formFactory;
+ }
- throw new \UnexpectedValueException('FormFactory not set in ' . __CLASS__);
- }
+ throw new \UnexpectedValueException('FormFactory not set in ' . __CLASS__);
+ }
- /**
- * Set the form factory to use.
- *
- * @param FormFactoryInterface $formFactory The form factory to use.
- *
- * @return $this
- *
- * @since 4.0.0
- */
- public function setFormFactory(FormFactoryInterface $formFactory = null)
- {
- $this->formFactory = $formFactory;
+ /**
+ * Set the form factory to use.
+ *
+ * @param FormFactoryInterface $formFactory The form factory to use.
+ *
+ * @return $this
+ *
+ * @since 4.0.0
+ */
+ public function setFormFactory(FormFactoryInterface $formFactory = null)
+ {
+ $this->formFactory = $formFactory;
- return $this;
- }
+ return $this;
+ }
}
diff --git a/libraries/src/Form/FormFactoryInterface.php b/libraries/src/Form/FormFactoryInterface.php
index 7850f5165f5f0..0027d09533c47 100644
--- a/libraries/src/Form/FormFactoryInterface.php
+++ b/libraries/src/Form/FormFactoryInterface.php
@@ -1,4 +1,5 @@
` XML element that describes the form field.
- *
- * @var \SimpleXMLElement
- * @since 1.7.0
- */
- protected $element;
-
- /**
- * The Form object of the form attached to the form field.
- *
- * @var Form
- * @since 1.7.0
- */
- protected $form;
-
- /**
- * The form control prefix for field names from the Form object attached to the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $formControl;
-
- /**
- * The hidden state for the form field.
- *
- * @var boolean
- * @since 1.7.0
- */
- protected $hidden = false;
-
- /**
- * Should the label be hidden when rendering the form field? This may be useful if you have the
- * label rendering in a legend in your form field itself for radio buttons in a fieldset etc.
- * If you use this flag you should ensure you display the label in your form (for a11y etc.)
- *
- * @var boolean
- * @since 4.0.0
- */
- protected $hiddenLabel = false;
-
- /**
- * Should the description be hidden when rendering the form field? This may be useful if you have the
- * description rendering in your form field itself for e.g. note fields.
- *
- * @var boolean
- * @since 4.0.0
- */
- protected $hiddenDescription = false;
-
- /**
- * True to translate the field label string.
- *
- * @var boolean
- * @since 1.7.0
- */
- protected $translateLabel = true;
-
- /**
- * True to translate the field description string.
- *
- * @var boolean
- * @since 1.7.0
- */
- protected $translateDescription = true;
-
- /**
- * True to translate the field hint string.
- *
- * @var boolean
- * @since 3.2
- */
- protected $translateHint = true;
-
- /**
- * The document id for the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $id;
-
- /**
- * The input for the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $input;
-
- /**
- * The label for the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $label;
-
- /**
- * The multiple state for the form field. If true then multiple values are allowed for the
- * field. Most often used for list field types.
- *
- * @var boolean
- * @since 1.7.0
- */
- protected $multiple = false;
-
- /**
- * Allows extensions to create repeat elements
- *
- * @var mixed
- * @since 3.2
- */
- public $repeat = false;
-
- /**
- * The pattern (Reg Ex) of value of the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $pattern;
-
- /**
- * The validation text of invalid value of the form field.
- *
- * @var string
- * @since 4.0.0
- */
- protected $validationtext;
-
- /**
- * The name of the form field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $name;
-
- /**
- * The name of the field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $fieldname;
-
- /**
- * The group of the field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $group;
-
- /**
- * The required state for the form field. If true then there must be a value for the field to
- * be considered valid.
- *
- * @var boolean
- * @since 1.7.0
- */
- protected $required = false;
-
- /**
- * The disabled state for the form field. If true then the field will be disabled and user can't
- * interact with the field.
- *
- * @var boolean
- * @since 3.2
- */
- protected $disabled = false;
-
- /**
- * The readonly state for the form field. If true then the field will be readonly.
- *
- * @var boolean
- * @since 3.2
- */
- protected $readonly = false;
-
- /**
- * The form field type.
- *
- * @var string
- * @since 1.7.0
- */
- protected $type;
-
- /**
- * The validation method for the form field. This value will determine which method is used
- * to validate the value for a field.
- *
- * @var string
- * @since 1.7.0
- */
- protected $validate;
-
- /**
- * The value of the form field.
- *
- * @var mixed
- * @since 1.7.0
- */
- protected $value;
-
- /**
- * The default value of the form field.
- *
- * @var mixed
- * @since 1.7.0
- */
- protected $default;
-
- /**
- * The size of the form field.
- *
- * @var integer
- * @since 3.2
- */
- protected $size;
-
- /**
- * The class of the form field
- *
- * @var mixed
- * @since 3.2
- */
- protected $class;
-
- /**
- * The label's CSS class of the form field
- *
- * @var mixed
- * @since 1.7.0
- */
- protected $labelclass;
-
- /**
- * The javascript onchange of the form field.
- *
- * @var string
- * @since 3.2
- */
- protected $onchange;
-
- /**
- * The javascript onclick of the form field.
- *
- * @var string
- * @since 3.2
- */
- protected $onclick;
-
- /**
- * The conditions to show/hide the field.
- *
- * @var string
- * @since 3.7.0
- */
- protected $showon;
-
- /**
- * The parent class of the field
- *
- * @var string
- * @since 4.0.0
- */
- protected $parentclass;
-
- /**
- * The count value for generated name field
- *
- * @var integer
- * @since 1.7.0
- */
- protected static $count = 0;
-
- /**
- * The string used for generated fields names
- *
- * @var string
- * @since 1.7.0
- */
- protected static $generated_fieldname = '__field';
-
- /**
- * Name of the layout being used to render the field
- *
- * @var string
- * @since 3.5
- */
- protected $layout;
-
- /**
- * Layout to render the form field
- *
- * @var string
- */
- protected $renderLayout = 'joomla.form.renderfield';
-
- /**
- * Layout to render the label
- *
- * @var string
- */
- protected $renderLabelLayout = 'joomla.form.renderlabel';
-
- /**
- * The data-attribute name and values of the form field.
- * For example, data-action-type="click" data-action-type="change"
- *
- * @var array
- *
- * @since 4.0.0
- */
- protected $dataAttributes = array();
-
- /**
- * Method to instantiate the form field object.
- *
- * @param Form $form The form to attach to the form field object.
- *
- * @since 1.7.0
- */
- public function __construct($form = null)
- {
- // If there is a form passed into the constructor set the form and form control properties.
- if ($form instanceof Form)
- {
- $this->form = $form;
- $this->formControl = $form->getFormControl();
- }
-
- // Detect the field type if not set
- if (!isset($this->type))
- {
- $parts = Normalise::fromCamelCase(\get_called_class(), true);
-
- if ($parts[0] === 'J')
- {
- $this->type = StringHelper::ucfirst($parts[\count($parts) - 1], '_');
- }
- else
- {
- $this->type = StringHelper::ucfirst($parts[0], '_') . StringHelper::ucfirst($parts[\count($parts) - 1], '_');
- }
- }
- }
-
- /**
- * Method to get certain otherwise inaccessible properties from the form field object.
- *
- * @param string $name The property name for which to get the value.
- *
- * @return mixed The property value or null.
- *
- * @since 1.7.0
- */
- public function __get($name)
- {
- switch ($name)
- {
- case 'description':
- case 'hint':
- case 'formControl':
- case 'hidden':
- case 'id':
- case 'multiple':
- case 'name':
- case 'required':
- case 'type':
- case 'validate':
- case 'value':
- case 'class':
- case 'layout':
- case 'labelclass':
- case 'size':
- case 'onchange':
- case 'onclick':
- case 'fieldname':
- case 'group':
- case 'disabled':
- case 'readonly':
- case 'autofocus':
- case 'autocomplete':
- case 'spellcheck':
- case 'validationtext':
- case 'showon':
- case 'parentclass':
- return $this->$name;
-
- case 'input':
- // If the input hasn't yet been generated, generate it.
- if (empty($this->input))
- {
- $this->input = $this->getInput();
- }
-
- return $this->input;
-
- case 'label':
- // If the label hasn't yet been generated, generate it.
- if (empty($this->label))
- {
- $this->label = $this->getLabel();
- }
-
- return $this->label;
-
- case 'title':
- return $this->getTitle();
-
- default:
- // Check for data attribute
- if (strpos($name, 'data-') === 0 && array_key_exists($name, $this->dataAttributes))
- {
- return $this->dataAttributes[$name];
- }
- }
- }
-
- /**
- * Method to set certain otherwise inaccessible properties of the form field object.
- *
- * @param string $name The property name for which to set the value.
- * @param mixed $value The value of the property.
- *
- * @return void
- *
- * @since 3.2
- */
- public function __set($name, $value)
- {
- switch ($name)
- {
- case 'class':
- // Removes spaces from left & right and extra spaces from middle
- $value = preg_replace('/\s+/', ' ', trim((string) $value));
-
- case 'description':
- case 'hint':
- case 'value':
- case 'labelclass':
- case 'layout':
- case 'onchange':
- case 'onclick':
- case 'validate':
- case 'pattern':
- case 'validationtext':
- case 'group':
- case 'showon':
- case 'parentclass':
- case 'default':
- case 'autocomplete':
- $this->$name = (string) $value;
- break;
-
- case 'id':
- $this->id = $this->getId((string) $value, $this->fieldname);
- break;
-
- case 'fieldname':
- $this->fieldname = $this->getFieldName((string) $value);
- break;
-
- case 'name':
- $this->fieldname = $this->getFieldName((string) $value);
- $this->name = $this->getName($this->fieldname);
- break;
-
- case 'multiple':
- // Allow for field classes to force the multiple values option.
- $value = (string) $value;
- $value = $value === '' && isset($this->forceMultiple) ? (string) $this->forceMultiple : $value;
-
- case 'required':
- case 'disabled':
- case 'readonly':
- case 'autofocus':
- case 'hidden':
- $value = (string) $value;
- $this->$name = ($value === 'true' || $value === $name || $value === '1');
- break;
-
- case 'spellcheck':
- case 'translateLabel':
- case 'translateDescription':
- case 'translateHint':
- $value = (string) $value;
- $this->$name = !($value === 'false' || $value === 'off' || $value === '0');
- break;
-
- case 'translate_label':
- $value = (string) $value;
- $this->translateLabel = $this->translateLabel && !($value === 'false' || $value === 'off' || $value === '0');
- break;
-
- case 'translate_description':
- $value = (string) $value;
- $this->translateDescription = $this->translateDescription && !($value === 'false' || $value === 'off' || $value === '0');
- break;
-
- case 'size':
- $this->$name = (int) $value;
- break;
-
- default:
- // Detect data attribute(s)
- if (strpos($name, 'data-') === 0)
- {
- $this->dataAttributes[$name] = $value;
- }
- else
- {
- if (property_exists(__CLASS__, $name))
- {
- Log::add("Cannot access protected / private property $name of " . __CLASS__);
- }
- else
- {
- $this->$name = $value;
- }
- }
- }
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param Form $form The Form object to attach to the form field.
- *
- * @return FormField The form field object so that the method can be used in a chain.
- *
- * @since 1.7.0
- */
- public function setForm(Form $form)
- {
- $this->form = $form;
- $this->formControl = $form->getFormControl();
-
- return $this;
- }
-
- /**
- * Method to attach a Form object to the field.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- *
- * @return boolean True on success.
- *
- * @since 1.7.0
- */
- public function setup(\SimpleXMLElement $element, $value, $group = null)
- {
- // Make sure there is a valid FormField XML element.
- if ((string) $element->getName() !== 'field')
- {
- return false;
- }
-
- // Reset the input and label values.
- $this->input = null;
- $this->label = null;
-
- // Set the XML element object.
- $this->element = $element;
-
- // Set the group of the field.
- $this->group = $group;
-
- $attributes = array(
- 'multiple', 'name', 'id', 'hint', 'class', 'description', 'labelclass', 'onchange', 'onclick', 'validate', 'pattern', 'validationtext',
- 'default', 'required', 'disabled', 'readonly', 'autofocus', 'hidden', 'autocomplete', 'spellcheck', 'translateHint', 'translateLabel',
- 'translate_label', 'translateDescription', 'translate_description', 'size', 'showon');
-
- $this->default = isset($element['value']) ? (string) $element['value'] : $this->default;
-
- // Set the field default value.
- if ($element['multiple'] && \is_string($value) && \is_array(json_decode($value, true)))
- {
- $this->value = (array) json_decode($value);
- }
- else
- {
- $this->value = $value;
- }
-
- // Lets detect miscellaneous data attribute. For eg, data-*
- foreach ($this->element->attributes() as $key => $value)
- {
- if (strpos($key, 'data-') === 0)
- {
- // Data attribute key value pair
- $this->dataAttributes[$key] = $value;
- }
- }
-
- foreach ($attributes as $attributeName)
- {
- $this->__set($attributeName, $element[$attributeName]);
- }
-
- // Allow for repeatable elements
- $repeat = (string) $element['repeat'];
- $this->repeat = ($repeat === 'true' || $repeat === 'multiple' || (!empty($this->form->repeat) && $this->form->repeat == 1));
-
- // Set the visibility.
- $this->hidden = ($this->hidden || strtolower((string) $this->element['type']) === 'hidden');
-
- $this->layout = !empty($this->element['layout']) ? (string) $this->element['layout'] : $this->layout;
-
- $this->parentclass = isset($this->element['parentclass']) ? (string) $this->element['parentclass'] : $this->parentclass;
-
- // Add required to class list if field is required.
- if ($this->required)
- {
- $this->class = trim($this->class . ' required');
- }
-
- return true;
- }
-
- /**
- * Simple method to set the value
- *
- * @param mixed $value Value to set
- *
- * @return void
- *
- * @since 3.2
- */
- public function setValue($value)
- {
- $this->value = $value;
- }
-
- /**
- * Method to get the id used for the field input tag.
- *
- * @param string $fieldId The field element id.
- * @param string $fieldName The field element name.
- *
- * @return string The id to be used for the field input tag.
- *
- * @since 1.7.0
- */
- protected function getId($fieldId, $fieldName)
- {
- $id = '';
-
- // If there is a form control set for the attached form add it first.
- if ($this->formControl)
- {
- $id .= $this->formControl;
- }
-
- // If the field is in a group add the group control to the field id.
- if ($this->group)
- {
- // If we already have an id segment add the group control as another level.
- if ($id)
- {
- $id .= '_' . str_replace('.', '_', $this->group);
- }
- else
- {
- $id .= str_replace('.', '_', $this->group);
- }
- }
-
- // If we already have an id segment add the field id/name as another level.
- if ($id)
- {
- $id .= '_' . ($fieldId ?: $fieldName);
- }
- else
- {
- $id .= ($fieldId ?: $fieldName);
- }
-
- // Clean up any invalid characters.
- $id = preg_replace('#\W#', '_', $id);
-
- // If this is a repeatable element, add the repeat count to the ID
- if ($this->repeat)
- {
- $repeatCounter = empty($this->form->repeatCounter) ? 0 : $this->form->repeatCounter;
- $id .= '-' . $repeatCounter;
-
- if (strtolower($this->type) === 'radio')
- {
- $id .= '-';
- }
- }
-
- return $id;
- }
-
- /**
- * Method to get the field input markup.
- *
- * @return string The field input markup.
- *
- * @since 1.7.0
- */
- protected function getInput()
- {
- if (empty($this->layout))
- {
- throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
- }
-
- return $this->getRenderer($this->layout)->render($this->getLayoutData());
- }
-
- /**
- * Method to get the field title.
- *
- * @return string The field title.
- *
- * @since 1.7.0
- */
- protected function getTitle()
- {
- $title = '';
-
- if ($this->hidden)
- {
- return $title;
- }
-
- // Get the label text from the XML element, defaulting to the element name.
- $title = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
- $title = $this->translateLabel ? Text::_($title) : $title;
-
- return $title;
- }
-
- /**
- * Method to get the field label markup.
- *
- * @return string The field label markup.
- *
- * @since 1.7.0
- */
- protected function getLabel()
- {
- if ($this->hidden)
- {
- return '';
- }
-
- $data = $this->getLayoutData();
-
- // Forcing the Alias field to display the tip below
- $position = $this->element['name'] === 'alias' ? ' data-bs-placement="bottom" ' : '';
-
- // Here mainly for B/C with old layouts. This can be done in the layouts directly
- $extraData = array(
- 'text' => $data['label'],
- 'for' => $this->id,
- 'classes' => explode(' ', $data['labelclass']),
- 'position' => $position,
- );
-
- return $this->getRenderer($this->renderLabelLayout)->render(array_merge($data, $extraData));
- }
-
- /**
- * Method to get the name used for the field input tag.
- *
- * @param string $fieldName The field element name.
- *
- * @return string The name to be used for the field input tag.
- *
- * @since 1.7.0
- */
- protected function getName($fieldName)
- {
- // To support repeated element, extensions can set this in plugin->onRenderSettings
-
- $name = '';
-
- // If there is a form control set for the attached form add it first.
- if ($this->formControl)
- {
- $name .= $this->formControl;
- }
-
- // If the field is in a group add the group control to the field name.
- if ($this->group)
- {
- // If we already have a name segment add the group control as another level.
- $groups = explode('.', $this->group);
-
- if ($name)
- {
- foreach ($groups as $group)
- {
- $name .= '[' . $group . ']';
- }
- }
- else
- {
- $name .= array_shift($groups);
-
- foreach ($groups as $group)
- {
- $name .= '[' . $group . ']';
- }
- }
- }
-
- // If we already have a name segment add the field name as another level.
- if ($name)
- {
- $name .= '[' . $fieldName . ']';
- }
- else
- {
- $name .= $fieldName;
- }
-
- // If the field should support multiple values add the final array segment.
- if ($this->multiple)
- {
- switch (strtolower((string) $this->element['type']))
- {
- case 'text':
- case 'textarea':
- case 'email':
- case 'password':
- case 'radio':
- case 'calendar':
- case 'editor':
- case 'hidden':
- break;
- default:
- $name .= '[]';
- }
- }
-
- return $name;
- }
-
- /**
- * Method to get the field name used.
- *
- * @param string $fieldName The field element name.
- *
- * @return string The field name
- *
- * @since 1.7.0
- */
- protected function getFieldName($fieldName)
- {
- if ($fieldName)
- {
- return $fieldName;
- }
- else
- {
- self::$count = self::$count + 1;
-
- return self::$generated_fieldname . self::$count;
- }
- }
-
- /**
- * Method to get an attribute of the field
- *
- * @param string $name Name of the attribute to get
- * @param mixed $default Optional value to return if attribute not found
- *
- * @return mixed Value of the attribute / default
- *
- * @since 3.2
- */
- public function getAttribute($name, $default = null)
- {
- if ($this->element instanceof \SimpleXMLElement)
- {
- $attributes = $this->element->attributes();
-
- // Ensure that the attribute exists
- if ($attributes->$name !== null)
- {
- return (string) $attributes->$name;
- }
- }
-
- return $default;
- }
-
- /**
- * Method to get data attributes. For example, data-user-type
- *
- * @return array list of data attribute(s)
- *
- * @since 4.0.0
- */
- public function getDataAttributes()
- {
- return $this->dataAttributes;
- }
-
- /**
- * Method to render data attributes to html.
- *
- * @return string A HTML Tag Attribute string of data attribute(s)
- *
- * @since 4.0.0
- */
- public function renderDataAttributes()
- {
- $dataAttribute = '';
- $dataAttributes = $this->getDataAttributes();
-
- if (!empty($dataAttributes))
- {
- foreach ($dataAttributes as $key => $attrValue)
- {
- $dataAttribute .= ' ' . $key . '="' . htmlspecialchars($attrValue, ENT_COMPAT, 'UTF-8') . '"';
- }
- }
-
- return $dataAttribute;
- }
-
- /**
- * Render a layout of this field
- *
- * @param string $layoutId Layout identifier
- * @param array $data Optional data for the layout
- *
- * @return string
- *
- * @since 3.5
- */
- public function render($layoutId, $data = array())
- {
- $data = array_merge($this->getLayoutData(), $data);
-
- return $this->getRenderer($layoutId)->render($data);
- }
-
- /**
- * Method to get a control group with label and input.
- *
- * @param array $options Options to be passed into the rendering of the field
- *
- * @return string A string containing the html for the control group
- *
- * @since 3.2
- */
- public function renderField($options = array())
- {
- if ($this->hidden)
- {
- return $this->getInput();
- }
-
- if (!isset($options['class']))
- {
- $options['class'] = '';
- }
-
- $options['rel'] = '';
-
- if (empty($options['hiddenLabel']))
- {
- if ($this->getAttribute('hiddenLabel'))
- {
- $options['hiddenLabel'] = $this->getAttribute('hiddenLabel') == 'true';
- }
- else
- {
- $options['hiddenLabel'] = $this->hiddenLabel;
- }
- }
-
- if (empty($options['hiddenDescription']))
- {
- if ($this->getAttribute('hiddenDescription'))
- {
- $options['hiddenDescription'] = $this->getAttribute('hiddenDescription') == 'true';
- }
- else
- {
- $options['hiddenDescription'] = $this->hiddenDescription;
- }
- }
-
- $options['inlineHelp'] = isset($this->form->getXml()->config->inlinehelp['button'])
- ? ((string) $this->form->getXml()->config->inlinehelp['button'] == 'show' ?: false)
- : false;
-
- if ($this->showon)
- {
- $options['rel'] = ' data-showon=\'' .
- json_encode(FormHelper::parseShowOnConditions($this->showon, $this->formControl, $this->group)) . '\'';
- $options['showonEnabled'] = true;
- }
-
- $data = array(
- 'input' => $this->getInput(),
- 'label' => $this->getLabel(),
- 'options' => $options,
- );
-
- $data = array_merge($this->getLayoutData(), $data);
-
- return $this->getRenderer($this->renderLayout)->render($data);
- }
-
- /**
- * Method to filter a field value.
- *
- * @param mixed $value The optional value to use as the default for the field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param Registry $input An optional Registry object with the entire data set to filter
- * against the entire form.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- * @throws \UnexpectedValueException
- */
- public function filter($value, $group = null, Registry $input = null)
- {
- // Make sure there is a valid SimpleXMLElement.
- if (!($this->element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
- }
-
- // Get the field filter type.
- $filter = (string) $this->element['filter'];
-
- if ($filter !== '')
- {
- $required = ((string) $this->element['required'] === 'true' || (string) $this->element['required'] === 'required');
-
- if (($value === '' || $value === null) && !$required)
- {
- return '';
- }
-
- // Check for a callback filter
- if (strpos($filter, '::') !== false && \is_callable(explode('::', $filter)))
- {
- return \call_user_func(explode('::', $filter), $value);
- }
-
- // Load the FormRule object for the field. FormRule objects take precedence over PHP functions
- $obj = FormHelper::loadFilterType($filter);
-
- // Run the filter rule.
- if ($obj)
- {
- return $obj->filter($this->element, $value, $group, $input, $this->form);
- }
-
- if (\function_exists($filter))
- {
- return \call_user_func($filter, $value);
- }
-
- if ($this instanceof SubformField)
- {
- $subForm = $this->loadSubForm();
-
- // Subform field may have a default value, that is a JSON string
- if ($value && is_string($value))
- {
- $value = json_decode($value, true);
-
- // The string is invalid json
- if (!$value)
- {
- return null;
- }
- }
-
- if ($this->multiple)
- {
- $return = array();
-
- if ($value)
- {
- foreach ($value as $key => $val)
- {
- $return[$key] = $subForm->filter($val);
- }
- }
- }
- else
- {
- $return = $subForm->filter($value);
- }
-
- return $return;
- }
- }
-
- return InputFilter::getInstance()->clean($value, $filter);
- }
-
- /**
- * Method to validate a FormField object based on field data.
- *
- * @param mixed $value The optional value to use as the default for the field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param Registry $input An optional Registry object with the entire data set to validate
- * against the entire form.
- *
- * @return boolean|\Exception Boolean true if field value is valid, Exception on failure.
- *
- * @since 4.0.0
- * @throws \InvalidArgumentException
- * @throws \UnexpectedValueException
- */
- public function validate($value, $group = null, Registry $input = null)
- {
- // Make sure there is a valid SimpleXMLElement.
- if (!($this->element instanceof \SimpleXMLElement))
- {
- throw new \UnexpectedValueException(sprintf('%s::validate `element` is not an instance of SimpleXMLElement', \get_class($this)));
- }
-
- $valid = true;
-
- // Check if the field is required.
- $required = ((string) $this->element['required'] === 'true' || (string) $this->element['required'] === 'required');
-
- if ($this->element['label'])
- {
- $fieldLabel = $this->element['label'];
-
- // Try to translate label if not set to false
- $translate = (string) $this->element['translateLabel'];
-
- if (!($translate === 'false' || $translate === 'off' || $translate === '0'))
- {
- $fieldLabel = Text::_($fieldLabel);
- }
- }
- else
- {
- $fieldLabel = Text::_($this->element['name']);
- }
-
- // If the field is required and the value is empty return an error message.
- if ($required && (($value === '') || ($value === null)))
- {
- $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $fieldLabel);
-
- return new \RuntimeException($message);
- }
-
- // Get the field validation rule.
- if ($type = (string) $this->element['validate'])
- {
- // Load the FormRule object for the field.
- $rule = FormHelper::loadRuleType($type);
-
- // If the object could not be loaded return an error message.
- if ($rule === false)
- {
- throw new \UnexpectedValueException(sprintf('%s::validate() rule `%s` missing.', \get_class($this), $type));
- }
-
- if ($rule instanceof DatabaseAwareInterface)
- {
- try
- {
- $rule->setDatabase($this->getDatabase());
- }
- catch (DatabaseNotFoundException $e)
- {
- @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
- $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
- }
- }
-
- try
- {
- // Run the field validation rule test.
- $valid = $rule->test($this->element, $value, $group, $input, $this->form);
- }
- catch (\Exception $e)
- {
- return $e;
- }
- }
-
- if ($valid !== false && $this instanceof SubformField)
- {
- // Load the subform validation rule.
- $rule = FormHelper::loadRuleType('Subform');
-
- if ($rule instanceof DatabaseAwareInterface)
- {
- try
- {
- $rule->setDatabase($this->getDatabase());
- }
- catch (DatabaseNotFoundException $e)
- {
- @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
- $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
- }
- }
-
- try
- {
- // Run the field validation rule test.
- $valid = $rule->test($this->element, $value, $group, $input, $this->form);
- }
- catch (\Exception $e)
- {
- return $e;
- }
- }
-
- // Check if the field is valid.
- if ($valid === false)
- {
- // Does the field have a defined error message?
- $message = (string) $this->element['message'];
-
- if ($message)
- {
- $message = Text::_($this->element['message']);
- }
- else
- {
- $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel);
- }
-
- return new \UnexpectedValueException($message);
- }
-
- return $valid;
- }
-
- /**
- * Method to post-process a field value.
- *
- * @param mixed $value The optional value to use as the default for the field.
- * @param string $group The optional dot-separated form group path on which to find the field.
- * @param Registry $input An optional Registry object with the entire data set to filter
- * against the entire form.
- *
- * @return mixed The processed value.
- *
- * @since 4.0.0
- */
- public function postProcess($value, $group = null, Registry $input = null)
- {
- return $value;
- }
-
- /**
- * Method to get the data to be passed to the layout for rendering.
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutData()
- {
- // Label preprocess
- $label = !empty($this->element['label']) ? (string) $this->element['label'] : null;
- $label = $label && $this->translateLabel ? Text::_($label) : $label;
-
- // Description preprocess
- $description = !empty($this->description) ? $this->description : null;
- $description = !empty($description) && $this->translateDescription ? Text::_($description) : $description;
-
- $alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
-
- return [
- 'autocomplete' => $this->autocomplete,
- 'autofocus' => $this->autofocus,
- 'class' => $this->class,
- 'description' => $description,
- 'disabled' => $this->disabled,
- 'field' => $this,
- 'group' => $this->group,
- 'hidden' => $this->hidden,
- 'hint' => $this->translateHint ? Text::alt($this->hint, $alt) : $this->hint,
- 'id' => $this->id,
- 'label' => $label,
- 'labelclass' => $this->labelclass,
- 'multiple' => $this->multiple,
- 'name' => $this->name,
- 'onchange' => $this->onchange,
- 'onclick' => $this->onclick,
- 'pattern' => $this->pattern,
- 'validationtext' => $this->validationtext,
- 'readonly' => $this->readonly,
- 'repeat' => $this->repeat,
- 'required' => (bool) $this->required,
- 'size' => $this->size,
- 'spellcheck' => $this->spellcheck,
- 'validate' => $this->validate,
- 'value' => $this->value,
- 'dataAttribute' => $this->renderDataAttributes(),
- 'dataAttributes' => $this->dataAttributes,
- 'parentclass' => $this->parentclass,
- ];
- }
-
- /**
- * Allow to override renderer include paths in child fields
- *
- * @return array
- *
- * @since 3.5
- */
- protected function getLayoutPaths()
- {
- $renderer = new FileLayout('default');
-
- return $renderer->getDefaultIncludePaths();
- }
-
- /**
- * Get the renderer
- *
- * @param string $layoutId Id to load
- *
- * @return FileLayout
- *
- * @since 3.5
- */
- protected function getRenderer($layoutId = 'default')
- {
- $renderer = new FileLayout($layoutId);
-
- $renderer->setDebug($this->isDebugEnabled());
-
- $layoutPaths = $this->getLayoutPaths();
-
- if ($layoutPaths)
- {
- $renderer->setIncludePaths($layoutPaths);
- }
-
- return $renderer;
- }
-
- /**
- * Is debug enabled for this field
- *
- * @return boolean
- *
- * @since 3.5
- */
- protected function isDebugEnabled()
- {
- return $this->getAttribute('debug', 'false') === 'true';
- }
+ use DatabaseAwareTrait;
+
+ /**
+ * The description text for the form field. Usually used in tooltips.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $description;
+
+ /**
+ * The hint text for the form field used to display hint inside the field.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $hint;
+
+ /**
+ * The autocomplete state for the form field. If 'off' element will not be automatically
+ * completed by browser.
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $autocomplete = 'on';
+
+ /**
+ * The spellcheck state for the form field.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $spellcheck = true;
+
+ /**
+ * The autofocus request for the form field. If true element will be automatically
+ * focused on document load.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $autofocus = false;
+
+ /**
+ * The SimpleXMLElement object of the `` XML element that describes the form field.
+ *
+ * @var \SimpleXMLElement
+ * @since 1.7.0
+ */
+ protected $element;
+
+ /**
+ * The Form object of the form attached to the form field.
+ *
+ * @var Form
+ * @since 1.7.0
+ */
+ protected $form;
+
+ /**
+ * The form control prefix for field names from the Form object attached to the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $formControl;
+
+ /**
+ * The hidden state for the form field.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $hidden = false;
+
+ /**
+ * Should the label be hidden when rendering the form field? This may be useful if you have the
+ * label rendering in a legend in your form field itself for radio buttons in a fieldset etc.
+ * If you use this flag you should ensure you display the label in your form (for a11y etc.)
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $hiddenLabel = false;
+
+ /**
+ * Should the description be hidden when rendering the form field? This may be useful if you have the
+ * description rendering in your form field itself for e.g. note fields.
+ *
+ * @var boolean
+ * @since 4.0.0
+ */
+ protected $hiddenDescription = false;
+
+ /**
+ * True to translate the field label string.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $translateLabel = true;
+
+ /**
+ * True to translate the field description string.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $translateDescription = true;
+
+ /**
+ * True to translate the field hint string.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $translateHint = true;
+
+ /**
+ * The document id for the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $id;
+
+ /**
+ * The input for the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $input;
+
+ /**
+ * The label for the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $label;
+
+ /**
+ * The multiple state for the form field. If true then multiple values are allowed for the
+ * field. Most often used for list field types.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $multiple = false;
+
+ /**
+ * Allows extensions to create repeat elements
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ public $repeat = false;
+
+ /**
+ * The pattern (Reg Ex) of value of the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $pattern;
+
+ /**
+ * The validation text of invalid value of the form field.
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $validationtext;
+
+ /**
+ * The name of the form field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $name;
+
+ /**
+ * The name of the field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $fieldname;
+
+ /**
+ * The group of the field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $group;
+
+ /**
+ * The required state for the form field. If true then there must be a value for the field to
+ * be considered valid.
+ *
+ * @var boolean
+ * @since 1.7.0
+ */
+ protected $required = false;
+
+ /**
+ * The disabled state for the form field. If true then the field will be disabled and user can't
+ * interact with the field.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $disabled = false;
+
+ /**
+ * The readonly state for the form field. If true then the field will be readonly.
+ *
+ * @var boolean
+ * @since 3.2
+ */
+ protected $readonly = false;
+
+ /**
+ * The form field type.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $type;
+
+ /**
+ * The validation method for the form field. This value will determine which method is used
+ * to validate the value for a field.
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected $validate;
+
+ /**
+ * The value of the form field.
+ *
+ * @var mixed
+ * @since 1.7.0
+ */
+ protected $value;
+
+ /**
+ * The default value of the form field.
+ *
+ * @var mixed
+ * @since 1.7.0
+ */
+ protected $default;
+
+ /**
+ * The size of the form field.
+ *
+ * @var integer
+ * @since 3.2
+ */
+ protected $size;
+
+ /**
+ * The class of the form field
+ *
+ * @var mixed
+ * @since 3.2
+ */
+ protected $class;
+
+ /**
+ * The label's CSS class of the form field
+ *
+ * @var mixed
+ * @since 1.7.0
+ */
+ protected $labelclass;
+
+ /**
+ * The javascript onchange of the form field.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $onchange;
+
+ /**
+ * The javascript onclick of the form field.
+ *
+ * @var string
+ * @since 3.2
+ */
+ protected $onclick;
+
+ /**
+ * The conditions to show/hide the field.
+ *
+ * @var string
+ * @since 3.7.0
+ */
+ protected $showon;
+
+ /**
+ * The parent class of the field
+ *
+ * @var string
+ * @since 4.0.0
+ */
+ protected $parentclass;
+
+ /**
+ * The count value for generated name field
+ *
+ * @var integer
+ * @since 1.7.0
+ */
+ protected static $count = 0;
+
+ /**
+ * The string used for generated fields names
+ *
+ * @var string
+ * @since 1.7.0
+ */
+ protected static $generated_fieldname = '__field';
+
+ /**
+ * Name of the layout being used to render the field
+ *
+ * @var string
+ * @since 3.5
+ */
+ protected $layout;
+
+ /**
+ * Layout to render the form field
+ *
+ * @var string
+ */
+ protected $renderLayout = 'joomla.form.renderfield';
+
+ /**
+ * Layout to render the label
+ *
+ * @var string
+ */
+ protected $renderLabelLayout = 'joomla.form.renderlabel';
+
+ /**
+ * The data-attribute name and values of the form field.
+ * For example, data-action-type="click" data-action-type="change"
+ *
+ * @var array
+ *
+ * @since 4.0.0
+ */
+ protected $dataAttributes = array();
+
+ /**
+ * Method to instantiate the form field object.
+ *
+ * @param Form $form The form to attach to the form field object.
+ *
+ * @since 1.7.0
+ */
+ public function __construct($form = null)
+ {
+ // If there is a form passed into the constructor set the form and form control properties.
+ if ($form instanceof Form) {
+ $this->form = $form;
+ $this->formControl = $form->getFormControl();
+ }
+
+ // Detect the field type if not set
+ if (!isset($this->type)) {
+ $parts = Normalise::fromCamelCase(\get_called_class(), true);
+
+ if ($parts[0] === 'J') {
+ $this->type = StringHelper::ucfirst($parts[\count($parts) - 1], '_');
+ } else {
+ $this->type = StringHelper::ucfirst($parts[0], '_') . StringHelper::ucfirst($parts[\count($parts) - 1], '_');
+ }
+ }
+ }
+
+ /**
+ * Method to get certain otherwise inaccessible properties from the form field object.
+ *
+ * @param string $name The property name for which to get the value.
+ *
+ * @return mixed The property value or null.
+ *
+ * @since 1.7.0
+ */
+ public function __get($name)
+ {
+ switch ($name) {
+ case 'description':
+ case 'hint':
+ case 'formControl':
+ case 'hidden':
+ case 'id':
+ case 'multiple':
+ case 'name':
+ case 'required':
+ case 'type':
+ case 'validate':
+ case 'value':
+ case 'class':
+ case 'layout':
+ case 'labelclass':
+ case 'size':
+ case 'onchange':
+ case 'onclick':
+ case 'fieldname':
+ case 'group':
+ case 'disabled':
+ case 'readonly':
+ case 'autofocus':
+ case 'autocomplete':
+ case 'spellcheck':
+ case 'validationtext':
+ case 'showon':
+ case 'parentclass':
+ return $this->$name;
+
+ case 'input':
+ // If the input hasn't yet been generated, generate it.
+ if (empty($this->input)) {
+ $this->input = $this->getInput();
+ }
+
+ return $this->input;
+
+ case 'label':
+ // If the label hasn't yet been generated, generate it.
+ if (empty($this->label)) {
+ $this->label = $this->getLabel();
+ }
+
+ return $this->label;
+
+ case 'title':
+ return $this->getTitle();
+
+ default:
+ // Check for data attribute
+ if (strpos($name, 'data-') === 0 && array_key_exists($name, $this->dataAttributes)) {
+ return $this->dataAttributes[$name];
+ }
+ }
+ }
+
+ /**
+ * Method to set certain otherwise inaccessible properties of the form field object.
+ *
+ * @param string $name The property name for which to set the value.
+ * @param mixed $value The value of the property.
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'class':
+ // Removes spaces from left & right and extra spaces from middle
+ $value = preg_replace('/\s+/', ' ', trim((string) $value));
+
+ // No break
+
+ case 'description':
+ case 'hint':
+ case 'value':
+ case 'labelclass':
+ case 'layout':
+ case 'onchange':
+ case 'onclick':
+ case 'validate':
+ case 'pattern':
+ case 'validationtext':
+ case 'group':
+ case 'showon':
+ case 'parentclass':
+ case 'default':
+ case 'autocomplete':
+ $this->$name = (string) $value;
+ break;
+
+ case 'id':
+ $this->id = $this->getId((string) $value, $this->fieldname);
+ break;
+
+ case 'fieldname':
+ $this->fieldname = $this->getFieldName((string) $value);
+ break;
+
+ case 'name':
+ $this->fieldname = $this->getFieldName((string) $value);
+ $this->name = $this->getName($this->fieldname);
+ break;
+
+ case 'multiple':
+ // Allow for field classes to force the multiple values option.
+ $value = (string) $value;
+ $value = $value === '' && isset($this->forceMultiple) ? (string) $this->forceMultiple : $value;
+
+ // No break
+
+ case 'required':
+ case 'disabled':
+ case 'readonly':
+ case 'autofocus':
+ case 'hidden':
+ $value = (string) $value;
+ $this->$name = ($value === 'true' || $value === $name || $value === '1');
+ break;
+
+ case 'spellcheck':
+ case 'translateLabel':
+ case 'translateDescription':
+ case 'translateHint':
+ $value = (string) $value;
+ $this->$name = !($value === 'false' || $value === 'off' || $value === '0');
+ break;
+
+ case 'translate_label':
+ $value = (string) $value;
+ $this->translateLabel = $this->translateLabel && !($value === 'false' || $value === 'off' || $value === '0');
+ break;
+
+ case 'translate_description':
+ $value = (string) $value;
+ $this->translateDescription = $this->translateDescription && !($value === 'false' || $value === 'off' || $value === '0');
+ break;
+
+ case 'size':
+ $this->$name = (int) $value;
+ break;
+
+ default:
+ // Detect data attribute(s)
+ if (strpos($name, 'data-') === 0) {
+ $this->dataAttributes[$name] = $value;
+ } else {
+ if (property_exists(__CLASS__, $name)) {
+ Log::add("Cannot access protected / private property $name of " . __CLASS__);
+ } else {
+ $this->$name = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param Form $form The Form object to attach to the form field.
+ *
+ * @return FormField The form field object so that the method can be used in a chain.
+ *
+ * @since 1.7.0
+ */
+ public function setForm(Form $form)
+ {
+ $this->form = $form;
+ $this->formControl = $form->getFormControl();
+
+ return $this;
+ }
+
+ /**
+ * Method to attach a Form object to the field.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ *
+ * @return boolean True on success.
+ *
+ * @since 1.7.0
+ */
+ public function setup(\SimpleXMLElement $element, $value, $group = null)
+ {
+ // Make sure there is a valid FormField XML element.
+ if ((string) $element->getName() !== 'field') {
+ return false;
+ }
+
+ // Reset the input and label values.
+ $this->input = null;
+ $this->label = null;
+
+ // Set the XML element object.
+ $this->element = $element;
+
+ // Set the group of the field.
+ $this->group = $group;
+
+ $attributes = array(
+ 'multiple', 'name', 'id', 'hint', 'class', 'description', 'labelclass', 'onchange', 'onclick', 'validate', 'pattern', 'validationtext',
+ 'default', 'required', 'disabled', 'readonly', 'autofocus', 'hidden', 'autocomplete', 'spellcheck', 'translateHint', 'translateLabel',
+ 'translate_label', 'translateDescription', 'translate_description', 'size', 'showon');
+
+ $this->default = isset($element['value']) ? (string) $element['value'] : $this->default;
+
+ // Set the field default value.
+ if ($element['multiple'] && \is_string($value) && \is_array(json_decode($value, true))) {
+ $this->value = (array) json_decode($value);
+ } else {
+ $this->value = $value;
+ }
+
+ // Lets detect miscellaneous data attribute. For eg, data-*
+ foreach ($this->element->attributes() as $key => $value) {
+ if (strpos($key, 'data-') === 0) {
+ // Data attribute key value pair
+ $this->dataAttributes[$key] = $value;
+ }
+ }
+
+ foreach ($attributes as $attributeName) {
+ $this->__set($attributeName, $element[$attributeName]);
+ }
+
+ // Allow for repeatable elements
+ $repeat = (string) $element['repeat'];
+ $this->repeat = ($repeat === 'true' || $repeat === 'multiple' || (!empty($this->form->repeat) && $this->form->repeat == 1));
+
+ // Set the visibility.
+ $this->hidden = ($this->hidden || strtolower((string) $this->element['type']) === 'hidden');
+
+ $this->layout = !empty($this->element['layout']) ? (string) $this->element['layout'] : $this->layout;
+
+ $this->parentclass = isset($this->element['parentclass']) ? (string) $this->element['parentclass'] : $this->parentclass;
+
+ // Add required to class list if field is required.
+ if ($this->required) {
+ $this->class = trim($this->class . ' required');
+ }
+
+ return true;
+ }
+
+ /**
+ * Simple method to set the value
+ *
+ * @param mixed $value Value to set
+ *
+ * @return void
+ *
+ * @since 3.2
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Method to get the id used for the field input tag.
+ *
+ * @param string $fieldId The field element id.
+ * @param string $fieldName The field element name.
+ *
+ * @return string The id to be used for the field input tag.
+ *
+ * @since 1.7.0
+ */
+ protected function getId($fieldId, $fieldName)
+ {
+ $id = '';
+
+ // If there is a form control set for the attached form add it first.
+ if ($this->formControl) {
+ $id .= $this->formControl;
+ }
+
+ // If the field is in a group add the group control to the field id.
+ if ($this->group) {
+ // If we already have an id segment add the group control as another level.
+ if ($id) {
+ $id .= '_' . str_replace('.', '_', $this->group);
+ } else {
+ $id .= str_replace('.', '_', $this->group);
+ }
+ }
+
+ // If we already have an id segment add the field id/name as another level.
+ if ($id) {
+ $id .= '_' . ($fieldId ?: $fieldName);
+ } else {
+ $id .= ($fieldId ?: $fieldName);
+ }
+
+ // Clean up any invalid characters.
+ $id = preg_replace('#\W#', '_', $id);
+
+ // If this is a repeatable element, add the repeat count to the ID
+ if ($this->repeat) {
+ $repeatCounter = empty($this->form->repeatCounter) ? 0 : $this->form->repeatCounter;
+ $id .= '-' . $repeatCounter;
+
+ if (strtolower($this->type) === 'radio') {
+ $id .= '-';
+ }
+ }
+
+ return $id;
+ }
+
+ /**
+ * Method to get the field input markup.
+ *
+ * @return string The field input markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getInput()
+ {
+ if (empty($this->layout)) {
+ throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
+ }
+
+ return $this->getRenderer($this->layout)->render($this->getLayoutData());
+ }
+
+ /**
+ * Method to get the field title.
+ *
+ * @return string The field title.
+ *
+ * @since 1.7.0
+ */
+ protected function getTitle()
+ {
+ $title = '';
+
+ if ($this->hidden) {
+ return $title;
+ }
+
+ // Get the label text from the XML element, defaulting to the element name.
+ $title = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
+ $title = $this->translateLabel ? Text::_($title) : $title;
+
+ return $title;
+ }
+
+ /**
+ * Method to get the field label markup.
+ *
+ * @return string The field label markup.
+ *
+ * @since 1.7.0
+ */
+ protected function getLabel()
+ {
+ if ($this->hidden) {
+ return '';
+ }
+
+ $data = $this->getLayoutData();
+
+ // Forcing the Alias field to display the tip below
+ $position = $this->element['name'] === 'alias' ? ' data-bs-placement="bottom" ' : '';
+
+ // Here mainly for B/C with old layouts. This can be done in the layouts directly
+ $extraData = array(
+ 'text' => $data['label'],
+ 'for' => $this->id,
+ 'classes' => explode(' ', $data['labelclass']),
+ 'position' => $position,
+ );
+
+ return $this->getRenderer($this->renderLabelLayout)->render(array_merge($data, $extraData));
+ }
+
+ /**
+ * Method to get the name used for the field input tag.
+ *
+ * @param string $fieldName The field element name.
+ *
+ * @return string The name to be used for the field input tag.
+ *
+ * @since 1.7.0
+ */
+ protected function getName($fieldName)
+ {
+ // To support repeated element, extensions can set this in plugin->onRenderSettings
+
+ $name = '';
+
+ // If there is a form control set for the attached form add it first.
+ if ($this->formControl) {
+ $name .= $this->formControl;
+ }
+
+ // If the field is in a group add the group control to the field name.
+ if ($this->group) {
+ // If we already have a name segment add the group control as another level.
+ $groups = explode('.', $this->group);
+
+ if ($name) {
+ foreach ($groups as $group) {
+ $name .= '[' . $group . ']';
+ }
+ } else {
+ $name .= array_shift($groups);
+
+ foreach ($groups as $group) {
+ $name .= '[' . $group . ']';
+ }
+ }
+ }
+
+ // If we already have a name segment add the field name as another level.
+ if ($name) {
+ $name .= '[' . $fieldName . ']';
+ } else {
+ $name .= $fieldName;
+ }
+
+ // If the field should support multiple values add the final array segment.
+ if ($this->multiple) {
+ switch (strtolower((string) $this->element['type'])) {
+ case 'text':
+ case 'textarea':
+ case 'email':
+ case 'password':
+ case 'radio':
+ case 'calendar':
+ case 'editor':
+ case 'hidden':
+ break;
+ default:
+ $name .= '[]';
+ }
+ }
+
+ return $name;
+ }
+
+ /**
+ * Method to get the field name used.
+ *
+ * @param string $fieldName The field element name.
+ *
+ * @return string The field name
+ *
+ * @since 1.7.0
+ */
+ protected function getFieldName($fieldName)
+ {
+ if ($fieldName) {
+ return $fieldName;
+ } else {
+ self::$count = self::$count + 1;
+
+ return self::$generated_fieldname . self::$count;
+ }
+ }
+
+ /**
+ * Method to get an attribute of the field
+ *
+ * @param string $name Name of the attribute to get
+ * @param mixed $default Optional value to return if attribute not found
+ *
+ * @return mixed Value of the attribute / default
+ *
+ * @since 3.2
+ */
+ public function getAttribute($name, $default = null)
+ {
+ if ($this->element instanceof \SimpleXMLElement) {
+ $attributes = $this->element->attributes();
+
+ // Ensure that the attribute exists
+ if ($attributes->$name !== null) {
+ return (string) $attributes->$name;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Method to get data attributes. For example, data-user-type
+ *
+ * @return array list of data attribute(s)
+ *
+ * @since 4.0.0
+ */
+ public function getDataAttributes()
+ {
+ return $this->dataAttributes;
+ }
+
+ /**
+ * Method to render data attributes to html.
+ *
+ * @return string A HTML Tag Attribute string of data attribute(s)
+ *
+ * @since 4.0.0
+ */
+ public function renderDataAttributes()
+ {
+ $dataAttribute = '';
+ $dataAttributes = $this->getDataAttributes();
+
+ if (!empty($dataAttributes)) {
+ foreach ($dataAttributes as $key => $attrValue) {
+ $dataAttribute .= ' ' . $key . '="' . htmlspecialchars($attrValue, ENT_COMPAT, 'UTF-8') . '"';
+ }
+ }
+
+ return $dataAttribute;
+ }
+
+ /**
+ * Render a layout of this field
+ *
+ * @param string $layoutId Layout identifier
+ * @param array $data Optional data for the layout
+ *
+ * @return string
+ *
+ * @since 3.5
+ */
+ public function render($layoutId, $data = array())
+ {
+ $data = array_merge($this->getLayoutData(), $data);
+
+ return $this->getRenderer($layoutId)->render($data);
+ }
+
+ /**
+ * Method to get a control group with label and input.
+ *
+ * @param array $options Options to be passed into the rendering of the field
+ *
+ * @return string A string containing the html for the control group
+ *
+ * @since 3.2
+ */
+ public function renderField($options = array())
+ {
+ if ($this->hidden) {
+ return $this->getInput();
+ }
+
+ if (!isset($options['class'])) {
+ $options['class'] = '';
+ }
+
+ $options['rel'] = '';
+
+ if (empty($options['hiddenLabel'])) {
+ if ($this->getAttribute('hiddenLabel')) {
+ $options['hiddenLabel'] = $this->getAttribute('hiddenLabel') == 'true';
+ } else {
+ $options['hiddenLabel'] = $this->hiddenLabel;
+ }
+ }
+
+ if (empty($options['hiddenDescription'])) {
+ if ($this->getAttribute('hiddenDescription')) {
+ $options['hiddenDescription'] = $this->getAttribute('hiddenDescription') == 'true';
+ } else {
+ $options['hiddenDescription'] = $this->hiddenDescription;
+ }
+ }
+
+ $options['inlineHelp'] = isset($this->form->getXml()->config->inlinehelp['button'])
+ ? ((string) $this->form->getXml()->config->inlinehelp['button'] == 'show' ?: false)
+ : false;
+
+ if ($this->showon) {
+ $options['rel'] = ' data-showon=\'' .
+ json_encode(FormHelper::parseShowOnConditions($this->showon, $this->formControl, $this->group)) . '\'';
+ $options['showonEnabled'] = true;
+ }
+
+ $data = array(
+ 'input' => $this->getInput(),
+ 'label' => $this->getLabel(),
+ 'options' => $options,
+ );
+
+ $data = array_merge($this->getLayoutData(), $data);
+
+ return $this->getRenderer($this->renderLayout)->render($data);
+ }
+
+ /**
+ * Method to filter a field value.
+ *
+ * @param mixed $value The optional value to use as the default for the field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param Registry $input An optional Registry object with the entire data set to filter
+ * against the entire form.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ * @throws \UnexpectedValueException
+ */
+ public function filter($value, $group = null, Registry $input = null)
+ {
+ // Make sure there is a valid SimpleXMLElement.
+ if (!($this->element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::filter `element` is not an instance of SimpleXMLElement', \get_class($this)));
+ }
+
+ // Get the field filter type.
+ $filter = (string) $this->element['filter'];
+
+ if ($filter !== '') {
+ $required = ((string) $this->element['required'] === 'true' || (string) $this->element['required'] === 'required');
+
+ if (($value === '' || $value === null) && !$required) {
+ return '';
+ }
+
+ // Check for a callback filter
+ if (strpos($filter, '::') !== false && \is_callable(explode('::', $filter))) {
+ return \call_user_func(explode('::', $filter), $value);
+ }
+
+ // Load the FormRule object for the field. FormRule objects take precedence over PHP functions
+ $obj = FormHelper::loadFilterType($filter);
+
+ // Run the filter rule.
+ if ($obj) {
+ return $obj->filter($this->element, $value, $group, $input, $this->form);
+ }
+
+ if (\function_exists($filter)) {
+ return \call_user_func($filter, $value);
+ }
+
+ if ($this instanceof SubformField) {
+ $subForm = $this->loadSubForm();
+
+ // Subform field may have a default value, that is a JSON string
+ if ($value && is_string($value)) {
+ $value = json_decode($value, true);
+
+ // The string is invalid json
+ if (!$value) {
+ return null;
+ }
+ }
+
+ if ($this->multiple) {
+ $return = array();
+
+ if ($value) {
+ foreach ($value as $key => $val) {
+ $return[$key] = $subForm->filter($val);
+ }
+ }
+ } else {
+ $return = $subForm->filter($value);
+ }
+
+ return $return;
+ }
+ }
+
+ return InputFilter::getInstance()->clean($value, $filter);
+ }
+
+ /**
+ * Method to validate a FormField object based on field data.
+ *
+ * @param mixed $value The optional value to use as the default for the field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param Registry $input An optional Registry object with the entire data set to validate
+ * against the entire form.
+ *
+ * @return boolean|\Exception Boolean true if field value is valid, Exception on failure.
+ *
+ * @since 4.0.0
+ * @throws \InvalidArgumentException
+ * @throws \UnexpectedValueException
+ */
+ public function validate($value, $group = null, Registry $input = null)
+ {
+ // Make sure there is a valid SimpleXMLElement.
+ if (!($this->element instanceof \SimpleXMLElement)) {
+ throw new \UnexpectedValueException(sprintf('%s::validate `element` is not an instance of SimpleXMLElement', \get_class($this)));
+ }
+
+ $valid = true;
+
+ // Check if the field is required.
+ $required = ((string) $this->element['required'] === 'true' || (string) $this->element['required'] === 'required');
+
+ if ($this->element['label']) {
+ $fieldLabel = $this->element['label'];
+
+ // Try to translate label if not set to false
+ $translate = (string) $this->element['translateLabel'];
+
+ if (!($translate === 'false' || $translate === 'off' || $translate === '0')) {
+ $fieldLabel = Text::_($fieldLabel);
+ }
+ } else {
+ $fieldLabel = Text::_($this->element['name']);
+ }
+
+ // If the field is required and the value is empty return an error message.
+ if ($required && (($value === '') || ($value === null))) {
+ $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $fieldLabel);
+
+ return new \RuntimeException($message);
+ }
+
+ // Get the field validation rule.
+ if ($type = (string) $this->element['validate']) {
+ // Load the FormRule object for the field.
+ $rule = FormHelper::loadRuleType($type);
+
+ // If the object could not be loaded return an error message.
+ if ($rule === false) {
+ throw new \UnexpectedValueException(sprintf('%s::validate() rule `%s` missing.', \get_class($this), $type));
+ }
+
+ if ($rule instanceof DatabaseAwareInterface) {
+ try {
+ $rule->setDatabase($this->getDatabase());
+ } catch (DatabaseNotFoundException $e) {
+ @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
+ $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
+ }
+ }
+
+ try {
+ // Run the field validation rule test.
+ $valid = $rule->test($this->element, $value, $group, $input, $this->form);
+ } catch (\Exception $e) {
+ return $e;
+ }
+ }
+
+ if ($valid !== false && $this instanceof SubformField) {
+ // Load the subform validation rule.
+ $rule = FormHelper::loadRuleType('Subform');
+
+ if ($rule instanceof DatabaseAwareInterface) {
+ try {
+ $rule->setDatabase($this->getDatabase());
+ } catch (DatabaseNotFoundException $e) {
+ @trigger_error(sprintf('Database must be set, this will not be caught anymore in 5.0.'), E_USER_DEPRECATED);
+ $rule->setDatabase(Factory::getContainer()->get(DatabaseInterface::class));
+ }
+ }
+
+ try {
+ // Run the field validation rule test.
+ $valid = $rule->test($this->element, $value, $group, $input, $this->form);
+ } catch (\Exception $e) {
+ return $e;
+ }
+ }
+
+ // Check if the field is valid.
+ if ($valid === false) {
+ // Does the field have a defined error message?
+ $message = (string) $this->element['message'];
+
+ if ($message) {
+ $message = Text::_($this->element['message']);
+ } else {
+ $message = Text::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $fieldLabel);
+ }
+
+ return new \UnexpectedValueException($message);
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Method to post-process a field value.
+ *
+ * @param mixed $value The optional value to use as the default for the field.
+ * @param string $group The optional dot-separated form group path on which to find the field.
+ * @param Registry $input An optional Registry object with the entire data set to filter
+ * against the entire form.
+ *
+ * @return mixed The processed value.
+ *
+ * @since 4.0.0
+ */
+ public function postProcess($value, $group = null, Registry $input = null)
+ {
+ return $value;
+ }
+
+ /**
+ * Method to get the data to be passed to the layout for rendering.
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutData()
+ {
+ // Label preprocess
+ $label = !empty($this->element['label']) ? (string) $this->element['label'] : null;
+ $label = $label && $this->translateLabel ? Text::_($label) : $label;
+
+ // Description preprocess
+ $description = !empty($this->description) ? $this->description : null;
+ $description = !empty($description) && $this->translateDescription ? Text::_($description) : $description;
+
+ $alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
+
+ return [
+ 'autocomplete' => $this->autocomplete,
+ 'autofocus' => $this->autofocus,
+ 'class' => $this->class,
+ 'description' => $description,
+ 'disabled' => $this->disabled,
+ 'field' => $this,
+ 'group' => $this->group,
+ 'hidden' => $this->hidden,
+ 'hint' => $this->translateHint ? Text::alt($this->hint, $alt) : $this->hint,
+ 'id' => $this->id,
+ 'label' => $label,
+ 'labelclass' => $this->labelclass,
+ 'multiple' => $this->multiple,
+ 'name' => $this->name,
+ 'onchange' => $this->onchange,
+ 'onclick' => $this->onclick,
+ 'pattern' => $this->pattern,
+ 'validationtext' => $this->validationtext,
+ 'readonly' => $this->readonly,
+ 'repeat' => $this->repeat,
+ 'required' => (bool) $this->required,
+ 'size' => $this->size,
+ 'spellcheck' => $this->spellcheck,
+ 'validate' => $this->validate,
+ 'value' => $this->value,
+ 'dataAttribute' => $this->renderDataAttributes(),
+ 'dataAttributes' => $this->dataAttributes,
+ 'parentclass' => $this->parentclass,
+ ];
+ }
+
+ /**
+ * Allow to override renderer include paths in child fields
+ *
+ * @return array
+ *
+ * @since 3.5
+ */
+ protected function getLayoutPaths()
+ {
+ $renderer = new FileLayout('default');
+
+ return $renderer->getDefaultIncludePaths();
+ }
+
+ /**
+ * Get the renderer
+ *
+ * @param string $layoutId Id to load
+ *
+ * @return FileLayout
+ *
+ * @since 3.5
+ */
+ protected function getRenderer($layoutId = 'default')
+ {
+ $renderer = new FileLayout($layoutId);
+
+ $renderer->setDebug($this->isDebugEnabled());
+
+ $layoutPaths = $this->getLayoutPaths();
+
+ if ($layoutPaths) {
+ $renderer->setIncludePaths($layoutPaths);
+ }
+
+ return $renderer;
+ }
+
+ /**
+ * Is debug enabled for this field
+ *
+ * @return boolean
+ *
+ * @since 3.5
+ */
+ protected function isDebugEnabled()
+ {
+ return $this->getAttribute('debug', 'false') === 'true';
+ }
}
diff --git a/libraries/src/Form/FormFilterInterface.php b/libraries/src/Form/FormFilterInterface.php
index 3ba5c82a564e6..e51362f1cc7d1 100644
--- a/libraries/src/Form/FormFilterInterface.php
+++ b/libraries/src/Form/FormFilterInterface.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed The filtered value.
- *
- * @since 4.0.0
- */
- public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null);
+ /**
+ * Method to filter a field value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed The filtered value.
+ *
+ * @since 4.0.0
+ */
+ public function filter(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null);
}
diff --git a/libraries/src/Form/FormHelper.php b/libraries/src/Form/FormHelper.php
index 0d16d385f296e..a42c6f580ac05 100644
--- a/libraries/src/Form/FormHelper.php
+++ b/libraries/src/Form/FormHelper.php
@@ -1,4 +1,5 @@
array(), 'form' => array(), 'rule' => array(), 'filter' => array());
-
- /**
- * Static array of Form's entity objects for re-use.
- * Prototypes for all fields and rules are here.
- *
- * Array's structure:
- * entities:
- * {ENTITY_NAME}:
- * {KEY}: {OBJECT}
- *
- * @var array
- * @since 1.7.0
- */
- protected static $entities = array('field' => array(), 'form' => array(), 'rule' => array(), 'filter' => array());
-
- /**
- * Method to load a form field object given a type.
- *
- * @param string $type The field type.
- * @param boolean $new Flag to toggle whether we should get a new instance of the object.
- *
- * @return FormField|boolean FormField object on success, false otherwise.
- *
- * @since 1.7.0
- */
- public static function loadFieldType($type, $new = true)
- {
- return self::loadType('field', $type, $new);
- }
-
- /**
- * Method to load a form rule object given a type.
- *
- * @param string $type The rule type.
- * @param boolean $new Flag to toggle whether we should get a new instance of the object.
- *
- * @return FormRule|boolean FormRule object on success, false otherwise.
- *
- * @since 1.7.0
- */
- public static function loadRuleType($type, $new = true)
- {
- return self::loadType('rule', $type, $new);
- }
-
- /**
- * Method to load a form filter object given a type.
- *
- * @param string $type The rule type.
- * @param boolean $new Flag to toggle whether we should get a new instance of the object.
- *
- * @return FormFilterInterface|boolean FormRule object on success, false otherwise.
- *
- * @since 4.0.0
- */
- public static function loadFilterType($type, $new = true)
- {
- return self::loadType('filter', $type, $new);
- }
-
- /**
- * Method to load a form entity object given a type.
- * Each type is loaded only once and then used as a prototype for other objects of same type.
- * Please, use this method only with those entities which support types (forms don't support them).
- *
- * @param string $entity The entity.
- * @param string $type The entity type.
- * @param boolean $new Flag to toggle whether we should get a new instance of the object.
- *
- * @return mixed Entity object on success, false otherwise.
- *
- * @since 1.7.0
- */
- protected static function loadType($entity, $type, $new = true)
- {
- // Reference to an array with current entity's type instances
- $types = &self::$entities[$entity];
-
- $key = md5($type);
-
- // Return an entity object if it already exists and we don't need a new one.
- if (isset($types[$key]) && $new === false)
- {
- return $types[$key];
- }
-
- $class = self::loadClass($entity, $type);
-
- if ($class === false)
- {
- return false;
- }
-
- // Instantiate a new type object.
- $types[$key] = new $class;
-
- return $types[$key];
- }
-
- /**
- * Attempt to import the FormField class file if it isn't already imported.
- * You can use this method outside of Form for loading a field for inheritance or composition.
- *
- * @param string $type Type of a field whose class should be loaded.
- *
- * @return string|boolean Class name on success or false otherwise.
- *
- * @since 1.7.0
- */
- public static function loadFieldClass($type)
- {
- return self::loadClass('field', $type);
- }
-
- /**
- * Attempt to import the FormRule class file if it isn't already imported.
- * You can use this method outside of Form for loading a rule for inheritance or composition.
- *
- * @param string $type Type of a rule whose class should be loaded.
- *
- * @return string|boolean Class name on success or false otherwise.
- *
- * @since 1.7.0
- */
- public static function loadRuleClass($type)
- {
- return self::loadClass('rule', $type);
- }
-
- /**
- * Attempt to import the FormFilter class file if it isn't already imported.
- * You can use this method outside of Form for loading a filter for inheritance or composition.
- *
- * @param string $type Type of a filter whose class should be loaded.
- *
- * @return string|boolean Class name on success or false otherwise.
- *
- * @since 4.0.0
- */
- public static function loadFilterClass($type)
- {
- return self::loadClass('filter', $type);
- }
-
- /**
- * Load a class for one of the form's entities of a particular type.
- * Currently, it makes sense to use this method for the "field" and "rule" entities
- * (but you can support more entities in your subclass).
- *
- * @param string $entity One of the form entities (field or rule).
- * @param string $type Type of an entity.
- *
- * @return string|boolean Class name on success or false otherwise.
- *
- * @since 1.7.0
- */
- protected static function loadClass($entity, $type)
- {
- // Check if there is a class in the registered namespaces
- foreach (self::addPrefix($entity) as $prefix)
- {
- // Treat underscores as namespace
- $name = Normalise::toSpaceSeparated($type);
- $name = str_ireplace(' ', '\\', ucwords($name));
-
- $subPrefix = '';
-
- if (strpos($name, '.'))
- {
- list($subPrefix, $name) = explode('.', $name);
- $subPrefix = ucfirst($subPrefix) . '\\';
- }
-
- // Compile the classname
- $class = rtrim($prefix, '\\') . '\\' . $subPrefix . ucfirst($name) . ucfirst($entity);
-
- // Check if the class exists
- if (class_exists($class))
- {
- return $class;
- }
- }
-
- $prefix = 'J';
-
- if (strpos($type, '.'))
- {
- list($prefix, $type) = explode('.', $type);
- }
-
- $class = StringHelper::ucfirst($prefix, '_') . 'Form' . StringHelper::ucfirst($entity, '_') . StringHelper::ucfirst($type, '_');
-
- if (class_exists($class))
- {
- return $class;
- }
-
- // Get the field search path array.
- $paths = self::addPath($entity);
-
- // If the type is complex, add the base type to the paths.
- if ($pos = strpos($type, '_'))
- {
- // Add the complex type prefix to the paths.
- for ($i = 0, $n = \count($paths); $i < $n; $i++)
- {
- // Derive the new path.
- $path = $paths[$i] . '/' . strtolower(substr($type, 0, $pos));
-
- // If the path does not exist, add it.
- if (!\in_array($path, $paths))
- {
- $paths[] = $path;
- }
- }
-
- // Break off the end of the complex type.
- $type = substr($type, $pos + 1);
- }
-
- // Try to find the class file.
- $type = strtolower($type) . '.php';
-
- foreach ($paths as $path)
- {
- $file = Path::find($path, $type);
-
- if (!$file)
- {
- continue;
- }
-
- require_once $file;
-
- if (class_exists($class))
- {
- break;
- }
- }
-
- // Check for all if the class exists.
- return class_exists($class) ? $class : false;
- }
-
- /**
- * Method to add a path to the list of field include paths.
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 1.7.0
- */
- public static function addFieldPath($new = null)
- {
- return self::addPath('field', $new);
- }
-
- /**
- * Method to add a path to the list of form include paths.
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 1.7.0
- */
- public static function addFormPath($new = null)
- {
- return self::addPath('form', $new);
- }
-
- /**
- * Method to add a path to the list of rule include paths.
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 1.7.0
- */
- public static function addRulePath($new = null)
- {
- return self::addPath('rule', $new);
- }
-
- /**
- * Method to add a path to the list of filter include paths.
- *
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 4.0.0
- */
- public static function addFilterPath($new = null)
- {
- return self::addPath('filter', $new);
- }
-
- /**
- * Method to add a path to the list of include paths for one of the form's entities.
- * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
- *
- * @param string $entity Form's entity name for which paths will be added.
- * @param mixed $new A path or array of paths to add.
- *
- * @return array The list of paths that have been added.
- *
- * @since 1.7.0
- */
- protected static function addPath($entity, $new = null)
- {
- if (!isset(self::$paths[$entity]))
- {
- self::$paths[$entity] = [];
- }
-
- // Reference to an array with paths for current entity
- $paths = &self::$paths[$entity];
-
- // Force the new path(s) to an array.
- settype($new, 'array');
-
- // Add the new paths to the stack if not already there.
- foreach ($new as $path)
- {
- $path = \trim($path);
-
- if (!\in_array($path, $paths))
- {
- \array_unshift($paths, $path);
- }
- }
-
- return $paths;
- }
-
- /**
- * Method to add a namespace prefix to the list of field lookups.
- *
- * @param mixed $new A namespaces or array of namespaces to add.
- *
- * @return array The list of namespaces that have been added.
- *
- * @since 3.8.0
- */
- public static function addFieldPrefix($new = null)
- {
- return self::addPrefix('field', $new);
- }
-
- /**
- * Method to add a namespace to the list of form lookups.
- *
- * @param mixed $new A namespace or array of namespaces to add.
- *
- * @return array The list of namespaces that have been added.
- *
- * @since 3.8.0
- */
- public static function addFormPrefix($new = null)
- {
- return self::addPrefix('form', $new);
- }
-
- /**
- * Method to add a namespace to the list of rule lookups.
- *
- * @param mixed $new A namespace or array of namespaces to add.
- *
- * @return array The list of namespaces that have been added.
- *
- * @since 3.8.0
- */
- public static function addRulePrefix($new = null)
- {
- return self::addPrefix('rule', $new);
- }
-
- /**
- * Method to add a namespace to the list of filter lookups.
- *
- * @param mixed $new A namespace or array of namespaces to add.
- *
- * @return array The list of namespaces that have been added.
- *
- * @since 4.0.0
- */
- public static function addFilterPrefix($new = null)
- {
- return self::addPrefix('filter', $new);
- }
-
- /**
- * Method to add a namespace to the list of namespaces for one of the form's entities.
- * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
- *
- * @param string $entity Form's entity name for which paths will be added.
- * @param mixed $new A namespace or array of namespaces to add.
- *
- * @return array The list of namespaces that have been added.
- *
- * @since 3.8.0
- */
- protected static function addPrefix($entity, $new = null)
- {
- // Reference to an array with namespaces for current entity
- $prefixes = &self::$prefixes[$entity];
-
- // Add the default entity's search namespace if not set.
- if (empty($prefixes))
- {
- $prefixes[] = __NAMESPACE__ . '\\' . ucfirst($entity);
- }
-
- // Force the new namespace(s) to an array.
- settype($new, 'array');
-
- // Add the new paths to the stack if not already there.
- foreach ($new as $prefix)
- {
- $prefix = trim($prefix);
-
- if (\in_array($prefix, $prefixes))
- {
- continue;
- }
-
- array_unshift($prefixes, $prefix);
- }
-
- return $prefixes;
- }
-
- /**
- * Parse the show on conditions
- *
- * @param string $showOn Show on conditions.
- * @param string $formControl Form name.
- * @param string $group The dot-separated form group path.
- *
- * @return array Array with show on conditions.
- *
- * @since 3.7.0
- */
- public static function parseShowOnConditions($showOn, $formControl = null, $group = null)
- {
- // Process the showon data.
- if (!$showOn)
- {
- return array();
- }
-
- $formPath = $formControl ?: '';
-
- if ($group)
- {
- $groups = explode('.', $group);
-
- // An empty formControl leads to invalid shown property
- // Use the 1st part of the group instead to avoid.
- if (empty($formPath) && isset($groups[0]))
- {
- $formPath = $groups[0];
- array_shift($groups);
- }
-
- foreach ($groups as $group)
- {
- $formPath .= '[' . $group . ']';
- }
- }
-
- $showOnData = array();
- $showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1, PREG_SPLIT_DELIM_CAPTURE);
- $op = '';
-
- foreach ($showOnParts as $showOnPart)
- {
- if (($showOnPart === '[AND]') || $showOnPart === '[OR]')
- {
- $op = trim($showOnPart, '[]');
- continue;
- }
-
- $compareEqual = strpos($showOnPart, '!:') === false;
- $showOnPartBlocks = explode(($compareEqual ? ':' : '!:'), $showOnPart, 2);
-
- $dotPos = strpos($showOnPartBlocks[0], '.');
-
- if ($dotPos === false)
- {
- $field = $formPath ? $formPath . '[' . $showOnPartBlocks[0] . ']' : $showOnPartBlocks[0];
- }
- else
- {
- if ($dotPos === 0)
- {
- $fieldName = substr($showOnPartBlocks[0], 1);
- $field = $formControl ? $formControl . '[' . $fieldName . ']' : $fieldName;
- }
- else
- {
- if ($formControl)
- {
- $field = $formControl . ('[' . str_replace('.', '][', $showOnPartBlocks[0]) . ']');
- }
- else
- {
- $groupParts = explode('.', $showOnPartBlocks[0]);
- $field = array_shift($groupParts) . '[' . join('][', $groupParts) . ']';
- }
- }
- }
-
- $showOnData[] = array(
- 'field' => $field,
- 'values' => explode(',', $showOnPartBlocks[1]),
- 'sign' => $compareEqual === true ? '=' : '!=',
- 'op' => $op,
- );
-
- if ($op !== '')
- {
- $op = '';
- }
- }
-
- return $showOnData;
- }
+ /**
+ * Array with paths where entities(field, rule, form) can be found.
+ *
+ * Array's structure:
+ *
+ * paths:
+ * {ENTITY_NAME}:
+ * - /path/1
+ * - /path/2
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected static $paths;
+
+ /**
+ * The class namespaces.
+ *
+ * @var string
+ * @since 3.8.0
+ */
+ protected static $prefixes = array('field' => array(), 'form' => array(), 'rule' => array(), 'filter' => array());
+
+ /**
+ * Static array of Form's entity objects for re-use.
+ * Prototypes for all fields and rules are here.
+ *
+ * Array's structure:
+ * entities:
+ * {ENTITY_NAME}:
+ * {KEY}: {OBJECT}
+ *
+ * @var array
+ * @since 1.7.0
+ */
+ protected static $entities = array('field' => array(), 'form' => array(), 'rule' => array(), 'filter' => array());
+
+ /**
+ * Method to load a form field object given a type.
+ *
+ * @param string $type The field type.
+ * @param boolean $new Flag to toggle whether we should get a new instance of the object.
+ *
+ * @return FormField|boolean FormField object on success, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public static function loadFieldType($type, $new = true)
+ {
+ return self::loadType('field', $type, $new);
+ }
+
+ /**
+ * Method to load a form rule object given a type.
+ *
+ * @param string $type The rule type.
+ * @param boolean $new Flag to toggle whether we should get a new instance of the object.
+ *
+ * @return FormRule|boolean FormRule object on success, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public static function loadRuleType($type, $new = true)
+ {
+ return self::loadType('rule', $type, $new);
+ }
+
+ /**
+ * Method to load a form filter object given a type.
+ *
+ * @param string $type The rule type.
+ * @param boolean $new Flag to toggle whether we should get a new instance of the object.
+ *
+ * @return FormFilterInterface|boolean FormRule object on success, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public static function loadFilterType($type, $new = true)
+ {
+ return self::loadType('filter', $type, $new);
+ }
+
+ /**
+ * Method to load a form entity object given a type.
+ * Each type is loaded only once and then used as a prototype for other objects of same type.
+ * Please, use this method only with those entities which support types (forms don't support them).
+ *
+ * @param string $entity The entity.
+ * @param string $type The entity type.
+ * @param boolean $new Flag to toggle whether we should get a new instance of the object.
+ *
+ * @return mixed Entity object on success, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ protected static function loadType($entity, $type, $new = true)
+ {
+ // Reference to an array with current entity's type instances
+ $types = &self::$entities[$entity];
+
+ $key = md5($type);
+
+ // Return an entity object if it already exists and we don't need a new one.
+ if (isset($types[$key]) && $new === false) {
+ return $types[$key];
+ }
+
+ $class = self::loadClass($entity, $type);
+
+ if ($class === false) {
+ return false;
+ }
+
+ // Instantiate a new type object.
+ $types[$key] = new $class();
+
+ return $types[$key];
+ }
+
+ /**
+ * Attempt to import the FormField class file if it isn't already imported.
+ * You can use this method outside of Form for loading a field for inheritance or composition.
+ *
+ * @param string $type Type of a field whose class should be loaded.
+ *
+ * @return string|boolean Class name on success or false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public static function loadFieldClass($type)
+ {
+ return self::loadClass('field', $type);
+ }
+
+ /**
+ * Attempt to import the FormRule class file if it isn't already imported.
+ * You can use this method outside of Form for loading a rule for inheritance or composition.
+ *
+ * @param string $type Type of a rule whose class should be loaded.
+ *
+ * @return string|boolean Class name on success or false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public static function loadRuleClass($type)
+ {
+ return self::loadClass('rule', $type);
+ }
+
+ /**
+ * Attempt to import the FormFilter class file if it isn't already imported.
+ * You can use this method outside of Form for loading a filter for inheritance or composition.
+ *
+ * @param string $type Type of a filter whose class should be loaded.
+ *
+ * @return string|boolean Class name on success or false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public static function loadFilterClass($type)
+ {
+ return self::loadClass('filter', $type);
+ }
+
+ /**
+ * Load a class for one of the form's entities of a particular type.
+ * Currently, it makes sense to use this method for the "field" and "rule" entities
+ * (but you can support more entities in your subclass).
+ *
+ * @param string $entity One of the form entities (field or rule).
+ * @param string $type Type of an entity.
+ *
+ * @return string|boolean Class name on success or false otherwise.
+ *
+ * @since 1.7.0
+ */
+ protected static function loadClass($entity, $type)
+ {
+ // Check if there is a class in the registered namespaces
+ foreach (self::addPrefix($entity) as $prefix) {
+ // Treat underscores as namespace
+ $name = Normalise::toSpaceSeparated($type);
+ $name = str_ireplace(' ', '\\', ucwords($name));
+
+ $subPrefix = '';
+
+ if (strpos($name, '.')) {
+ list($subPrefix, $name) = explode('.', $name);
+ $subPrefix = ucfirst($subPrefix) . '\\';
+ }
+
+ // Compile the classname
+ $class = rtrim($prefix, '\\') . '\\' . $subPrefix . ucfirst($name) . ucfirst($entity);
+
+ // Check if the class exists
+ if (class_exists($class)) {
+ return $class;
+ }
+ }
+
+ $prefix = 'J';
+
+ if (strpos($type, '.')) {
+ list($prefix, $type) = explode('.', $type);
+ }
+
+ $class = StringHelper::ucfirst($prefix, '_') . 'Form' . StringHelper::ucfirst($entity, '_') . StringHelper::ucfirst($type, '_');
+
+ if (class_exists($class)) {
+ return $class;
+ }
+
+ // Get the field search path array.
+ $paths = self::addPath($entity);
+
+ // If the type is complex, add the base type to the paths.
+ if ($pos = strpos($type, '_')) {
+ // Add the complex type prefix to the paths.
+ for ($i = 0, $n = \count($paths); $i < $n; $i++) {
+ // Derive the new path.
+ $path = $paths[$i] . '/' . strtolower(substr($type, 0, $pos));
+
+ // If the path does not exist, add it.
+ if (!\in_array($path, $paths)) {
+ $paths[] = $path;
+ }
+ }
+
+ // Break off the end of the complex type.
+ $type = substr($type, $pos + 1);
+ }
+
+ // Try to find the class file.
+ $type = strtolower($type) . '.php';
+
+ foreach ($paths as $path) {
+ $file = Path::find($path, $type);
+
+ if (!$file) {
+ continue;
+ }
+
+ require_once $file;
+
+ if (class_exists($class)) {
+ break;
+ }
+ }
+
+ // Check for all if the class exists.
+ return class_exists($class) ? $class : false;
+ }
+
+ /**
+ * Method to add a path to the list of field include paths.
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 1.7.0
+ */
+ public static function addFieldPath($new = null)
+ {
+ return self::addPath('field', $new);
+ }
+
+ /**
+ * Method to add a path to the list of form include paths.
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 1.7.0
+ */
+ public static function addFormPath($new = null)
+ {
+ return self::addPath('form', $new);
+ }
+
+ /**
+ * Method to add a path to the list of rule include paths.
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 1.7.0
+ */
+ public static function addRulePath($new = null)
+ {
+ return self::addPath('rule', $new);
+ }
+
+ /**
+ * Method to add a path to the list of filter include paths.
+ *
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 4.0.0
+ */
+ public static function addFilterPath($new = null)
+ {
+ return self::addPath('filter', $new);
+ }
+
+ /**
+ * Method to add a path to the list of include paths for one of the form's entities.
+ * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
+ *
+ * @param string $entity Form's entity name for which paths will be added.
+ * @param mixed $new A path or array of paths to add.
+ *
+ * @return array The list of paths that have been added.
+ *
+ * @since 1.7.0
+ */
+ protected static function addPath($entity, $new = null)
+ {
+ if (!isset(self::$paths[$entity])) {
+ self::$paths[$entity] = [];
+ }
+
+ // Reference to an array with paths for current entity
+ $paths = &self::$paths[$entity];
+
+ // Force the new path(s) to an array.
+ settype($new, 'array');
+
+ // Add the new paths to the stack if not already there.
+ foreach ($new as $path) {
+ $path = \trim($path);
+
+ if (!\in_array($path, $paths)) {
+ \array_unshift($paths, $path);
+ }
+ }
+
+ return $paths;
+ }
+
+ /**
+ * Method to add a namespace prefix to the list of field lookups.
+ *
+ * @param mixed $new A namespaces or array of namespaces to add.
+ *
+ * @return array The list of namespaces that have been added.
+ *
+ * @since 3.8.0
+ */
+ public static function addFieldPrefix($new = null)
+ {
+ return self::addPrefix('field', $new);
+ }
+
+ /**
+ * Method to add a namespace to the list of form lookups.
+ *
+ * @param mixed $new A namespace or array of namespaces to add.
+ *
+ * @return array The list of namespaces that have been added.
+ *
+ * @since 3.8.0
+ */
+ public static function addFormPrefix($new = null)
+ {
+ return self::addPrefix('form', $new);
+ }
+
+ /**
+ * Method to add a namespace to the list of rule lookups.
+ *
+ * @param mixed $new A namespace or array of namespaces to add.
+ *
+ * @return array The list of namespaces that have been added.
+ *
+ * @since 3.8.0
+ */
+ public static function addRulePrefix($new = null)
+ {
+ return self::addPrefix('rule', $new);
+ }
+
+ /**
+ * Method to add a namespace to the list of filter lookups.
+ *
+ * @param mixed $new A namespace or array of namespaces to add.
+ *
+ * @return array The list of namespaces that have been added.
+ *
+ * @since 4.0.0
+ */
+ public static function addFilterPrefix($new = null)
+ {
+ return self::addPrefix('filter', $new);
+ }
+
+ /**
+ * Method to add a namespace to the list of namespaces for one of the form's entities.
+ * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
+ *
+ * @param string $entity Form's entity name for which paths will be added.
+ * @param mixed $new A namespace or array of namespaces to add.
+ *
+ * @return array The list of namespaces that have been added.
+ *
+ * @since 3.8.0
+ */
+ protected static function addPrefix($entity, $new = null)
+ {
+ // Reference to an array with namespaces for current entity
+ $prefixes = &self::$prefixes[$entity];
+
+ // Add the default entity's search namespace if not set.
+ if (empty($prefixes)) {
+ $prefixes[] = __NAMESPACE__ . '\\' . ucfirst($entity);
+ }
+
+ // Force the new namespace(s) to an array.
+ settype($new, 'array');
+
+ // Add the new paths to the stack if not already there.
+ foreach ($new as $prefix) {
+ $prefix = trim($prefix);
+
+ if (\in_array($prefix, $prefixes)) {
+ continue;
+ }
+
+ array_unshift($prefixes, $prefix);
+ }
+
+ return $prefixes;
+ }
+
+ /**
+ * Parse the show on conditions
+ *
+ * @param string $showOn Show on conditions.
+ * @param string $formControl Form name.
+ * @param string $group The dot-separated form group path.
+ *
+ * @return array Array with show on conditions.
+ *
+ * @since 3.7.0
+ */
+ public static function parseShowOnConditions($showOn, $formControl = null, $group = null)
+ {
+ // Process the showon data.
+ if (!$showOn) {
+ return array();
+ }
+
+ $formPath = $formControl ?: '';
+
+ if ($group) {
+ $groups = explode('.', $group);
+
+ // An empty formControl leads to invalid shown property
+ // Use the 1st part of the group instead to avoid.
+ if (empty($formPath) && isset($groups[0])) {
+ $formPath = $groups[0];
+ array_shift($groups);
+ }
+
+ foreach ($groups as $group) {
+ $formPath .= '[' . $group . ']';
+ }
+ }
+
+ $showOnData = array();
+ $showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $op = '';
+
+ foreach ($showOnParts as $showOnPart) {
+ if (($showOnPart === '[AND]') || $showOnPart === '[OR]') {
+ $op = trim($showOnPart, '[]');
+ continue;
+ }
+
+ $compareEqual = strpos($showOnPart, '!:') === false;
+ $showOnPartBlocks = explode(($compareEqual ? ':' : '!:'), $showOnPart, 2);
+
+ $dotPos = strpos($showOnPartBlocks[0], '.');
+
+ if ($dotPos === false) {
+ $field = $formPath ? $formPath . '[' . $showOnPartBlocks[0] . ']' : $showOnPartBlocks[0];
+ } else {
+ if ($dotPos === 0) {
+ $fieldName = substr($showOnPartBlocks[0], 1);
+ $field = $formControl ? $formControl . '[' . $fieldName . ']' : $fieldName;
+ } else {
+ if ($formControl) {
+ $field = $formControl . ('[' . str_replace('.', '][', $showOnPartBlocks[0]) . ']');
+ } else {
+ $groupParts = explode('.', $showOnPartBlocks[0]);
+ $field = array_shift($groupParts) . '[' . join('][', $groupParts) . ']';
+ }
+ }
+ }
+
+ $showOnData[] = array(
+ 'field' => $field,
+ 'values' => explode(',', $showOnPartBlocks[1]),
+ 'sign' => $compareEqual === true ? '=' : '!=',
+ 'op' => $op,
+ );
+
+ if ($op !== '') {
+ $op = '';
+ }
+ }
+
+ return $showOnData;
+ }
}
diff --git a/libraries/src/Form/FormRule.php b/libraries/src/Form/FormRule.php
index c6a9af1d7f290..38083417224f1 100644
--- a/libraries/src/Form/FormRule.php
+++ b/libraries/src/Form/FormRule.php
@@ -1,9 +1,13 @@
* @license GNU General Public License version 2 or later; see LICENSE.txt
+ *
+ * Remove phpcs exception with deprecated constant JCOMPAT_UNICODE_PROPERTIES
+ * @phpcs:disable PSR1.Files.SideEffects
*/
namespace Joomla\CMS\Form;
@@ -13,15 +17,16 @@
use Joomla\Registry\Registry;
// Detect if we have full UTF-8 and unicode PCRE support.
-if (!\defined('JCOMPAT_UNICODE_PROPERTIES'))
-{
- /**
- * Flag indicating UTF-8 and PCRE support is present
- *
- * @var boolean
- * @since 1.6
- */
- \define('JCOMPAT_UNICODE_PROPERTIES', (bool) @preg_match('/\pL/u', 'a'));
+if (!\defined('JCOMPAT_UNICODE_PROPERTIES')) {
+ /**
+ * Flag indicating UTF-8 and PCRE support is present
+ *
+ * @var boolean
+ * @since 1.6
+ *
+ * @deprecated 5.0 Will be removed without replacement (Also remove phpcs exception)
+ */
+ \define('JCOMPAT_UNICODE_PROPERTIES', (bool) @preg_match('/\pL/u', 'a'));
}
/**
@@ -31,58 +36,62 @@
*/
class FormRule
{
- /**
- * The regular expression to use in testing a form field value.
- *
- * @var string
- * @since 1.6
- */
- protected $regex;
+ /**
+ * The regular expression to use in testing a form field value.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $regex;
+
+ /**
+ * The regular expression modifiers to use when testing a form field value.
+ *
+ * @var string
+ * @since 1.6
+ */
+ protected $modifiers = '';
+
+ /**
+ * Method to test the value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.6
+ * @throws \UnexpectedValueException if rule is invalid.
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Check for a valid regex.
+ if (empty($this->regex)) {
+ throw new \UnexpectedValueException(sprintf('%s has invalid regex.', \get_class($this)));
+ }
- /**
- * The regular expression modifiers to use when testing a form field value.
- *
- * @var string
- * @since 1.6
- */
- protected $modifiers = '';
+ // Detect if we have full UTF-8 and unicode PCRE support.
+ static $unicodePropertiesSupport = null;
- /**
- * Method to test the value.
- *
- * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.6
- * @throws \UnexpectedValueException if rule is invalid.
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Check for a valid regex.
- if (empty($this->regex))
- {
- throw new \UnexpectedValueException(sprintf('%s has invalid regex.', \get_class($this)));
- }
+ if ($unicodePropertiesSupport === null) {
+ $unicodePropertiesSupport = (bool) @\preg_match('/\pL/u', 'a');
+ }
- // Add unicode property support if available.
- if (JCOMPAT_UNICODE_PROPERTIES)
- {
- $this->modifiers = (strpos($this->modifiers, 'u') !== false) ? $this->modifiers : $this->modifiers . 'u';
- }
+ // Add unicode property support if available.
+ if ($unicodePropertiesSupport) {
+ $this->modifiers = (strpos($this->modifiers, 'u') !== false) ? $this->modifiers : $this->modifiers . 'u';
+ }
- // Test the value against the regular expression.
- if (preg_match(\chr(1) . $this->regex . \chr(1) . $this->modifiers, $value))
- {
- return true;
- }
+ // Test the value against the regular expression.
+ if (preg_match(\chr(1) . $this->regex . \chr(1) . $this->modifiers, $value)) {
+ return true;
+ }
- return false;
- }
+ return false;
+ }
}
diff --git a/libraries/src/Form/Rule/BooleanRule.php b/libraries/src/Form/Rule/BooleanRule.php
index 9a2c8d26fef47..fb605f330ca2a 100644
--- a/libraries/src/Form/Rule/BooleanRule.php
+++ b/libraries/src/Form/Rule/BooleanRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test the calendar value for a valid parts.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- if (!$required && empty($value))
- {
- return true;
- }
+ if (!$required && empty($value)) {
+ return true;
+ }
- if (strtolower($value) === 'now')
- {
- return true;
- }
+ if (strtolower($value) === 'now') {
+ return true;
+ }
- try
- {
- return Factory::getDate($value) instanceof Date;
- }
- catch (\Exception $e)
- {
- return false;
- }
- }
+ try {
+ return Factory::getDate($value) instanceof Date;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
}
diff --git a/libraries/src/Form/Rule/CaptchaRule.php b/libraries/src/Form/Rule/CaptchaRule.php
index 9ce97ffccff40..b8c598a540001 100644
--- a/libraries/src/Form/Rule/CaptchaRule.php
+++ b/libraries/src/Form/Rule/CaptchaRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 2.5
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $app = Factory::getApplication();
- $plugin = $app->get('captcha');
+ /**
+ * Method to test if the Captcha is correct.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 2.5
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $app = Factory::getApplication();
+ $plugin = $app->get('captcha');
- if ($app->isClient('site'))
- {
- $plugin = $app->getParams()->get('captcha', $plugin);
- }
+ if ($app->isClient('site')) {
+ $plugin = $app->getParams()->get('captcha', $plugin);
+ }
- $namespace = $element['namespace'] ?: $form->getName();
+ $namespace = $element['namespace'] ?: $form->getName();
- // Use 0 for none
- if ($plugin === 0 || $plugin === '0')
- {
- return true;
- }
+ // Use 0 for none
+ if ($plugin === 0 || $plugin === '0') {
+ return true;
+ }
- try
- {
- $captcha = Captcha::getInstance((string) $plugin, array('namespace' => (string) $namespace));
+ try {
+ $captcha = Captcha::getInstance((string) $plugin, array('namespace' => (string) $namespace));
- return $captcha->checkAnswer($value);
- }
- catch (\RuntimeException $e)
- {
- $app->enqueueMessage($e->getMessage(), 'error');
- }
+ return $captcha->checkAnswer($value);
+ } catch (\RuntimeException $e) {
+ $app->enqueueMessage($e->getMessage(), 'error');
+ }
- return false;
- }
+ return false;
+ }
}
diff --git a/libraries/src/Form/Rule/ColorRule.php b/libraries/src/Form/Rule/ColorRule.php
index 7e674e17e5096..b142a6c1b103e 100644
--- a/libraries/src/Form/Rule/ColorRule.php
+++ b/libraries/src/Form/Rule/ColorRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $value = trim($value);
-
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
-
- if (!$required && empty($value))
- {
- return true;
- }
-
- if ($value[0] != '#')
- {
- return false;
- }
-
- // Remove the leading # if present to validate the numeric part
- $value = ltrim($value, '#');
-
- // The value must be 6 or 3 characters long
- if (!((\strlen($value) == 6 || \strlen($value) == 3) && ctype_xdigit($value)))
- {
- return false;
- }
-
- return true;
- }
+ /**
+ * Method to test for a valid color in hexadecimal.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $value = trim($value);
+
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+
+ if (!$required && empty($value)) {
+ return true;
+ }
+
+ if ($value[0] != '#') {
+ return false;
+ }
+
+ // Remove the leading # if present to validate the numeric part
+ $value = ltrim($value, '#');
+
+ // The value must be 6 or 3 characters long
+ if (!((\strlen($value) == 6 || \strlen($value) == 3) && ctype_xdigit($value))) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/CssIdentifierRule.php b/libraries/src/Form/Rule/CssIdentifierRule.php
index a5416335feef5..cee8d81074984 100644
--- a/libraries/src/Form/Rule/CssIdentifierRule.php
+++ b/libraries/src/Form/Rule/CssIdentifierRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 4.0.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test if the file path is valid
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- if (!$required && empty($value) && $value !== '0')
- {
- return true;
- }
+ if (!$required && empty($value) && $value !== '0') {
+ return true;
+ }
- // Make sure we allow multiple classes to be added
- $cssIdentifiers = explode(' ', $value);
+ // Make sure we allow multiple classes to be added
+ $cssIdentifiers = explode(' ', $value);
- foreach ($cssIdentifiers as $i => $identifier)
- {
- /**
- * The following regex rules are based on the Html::cleanCssIdentifier method from Drupal
- * https://github.com/drupal/drupal/blob/8.8.5/core/lib/Drupal/Component/Utility/Html.php#L116-L130
- *
- * with the addition for Joomla that we allow the colon (U+003A).
- */
+ foreach ($cssIdentifiers as $i => $identifier) {
+ /**
+ * The following regex rules are based on the Html::cleanCssIdentifier method from Drupal
+ * https://github.com/drupal/drupal/blob/8.8.5/core/lib/Drupal/Component/Utility/Html.php#L116-L130
+ *
+ * with the addition for Joomla that we allow the colon (U+003A).
+ */
- /**
- * Valid characters in a CSS identifier are:
- * - the hyphen (U+002D)
- * - a-z (U+0030 - U+0039)
- * - A-Z (U+0041 - U+005A)
- * - the underscore (U+005F)
- * - the colon (U+003A)
- * - 0-9 (U+0061 - U+007A)
- * - ISO 10646 characters U+00A1 and higher
- */
- if (preg_match('/[^\\x{002D}\\x{0030}-\\x{0039}\\x{0041}-\\x{005A}\\x{005F}\\x{003A}\\x{0061}-\\x{007A}\\x{00A1}-\\x{FFFF}]/u', $identifier))
- {
- return false;
- }
+ /**
+ * Valid characters in a CSS identifier are:
+ * - the hyphen (U+002D)
+ * - a-z (U+0030 - U+0039)
+ * - A-Z (U+0041 - U+005A)
+ * - the underscore (U+005F)
+ * - the colon (U+003A)
+ * - 0-9 (U+0061 - U+007A)
+ * - ISO 10646 characters U+00A1 and higher
+ */
+ if (preg_match('/[^\\x{002D}\\x{0030}-\\x{0039}\\x{0041}-\\x{005A}\\x{005F}\\x{003A}\\x{0061}-\\x{007A}\\x{00A1}-\\x{FFFF}]/u', $identifier)) {
+ return false;
+ }
- /**
- * Full identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
- */
- if (preg_match('/^[0-9]/', $identifier) || preg_match('/^(-[0-9])|^(--)/', $identifier))
- {
- return false;
- }
- }
+ /**
+ * Full identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
+ */
+ if (preg_match('/^[0-9]/', $identifier) || preg_match('/^(-[0-9])|^(--)/', $identifier)) {
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/CssIdentifierSubstringRule.php b/libraries/src/Form/Rule/CssIdentifierSubstringRule.php
index 3eea25b5135cf..06cb30418848a 100644
--- a/libraries/src/Form/Rule/CssIdentifierSubstringRule.php
+++ b/libraries/src/Form/Rule/CssIdentifierSubstringRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.10.7
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test if a string is a valid CSS identifier substring
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.10.7
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- if (!$required && empty($value) && $value !== '0')
- {
- return true;
- }
+ if (!$required && empty($value) && $value !== '0') {
+ return true;
+ }
- /**
- * The following regex rules are based on the Html::cleanCssIdentifier method from Drupal
- * https://github.com/drupal/drupal/blob/8.8.5/core/lib/Drupal/Component/Utility/Html.php#L116-L130
- *
- * with the addition for Joomla that we allow the colon (U+003A).
- */
+ /**
+ * The following regex rules are based on the Html::cleanCssIdentifier method from Drupal
+ * https://github.com/drupal/drupal/blob/8.8.5/core/lib/Drupal/Component/Utility/Html.php#L116-L130
+ *
+ * with the addition for Joomla that we allow the colon (U+003A).
+ */
- /**
- * Valid characters in a CSS identifier are:
- * - the hyphen (U+002D)
- * - a-z (U+0030 - U+0039)
- * - A-Z (U+0041 - U+005A)
- * - the underscore (U+005F)
- * - the colon (U+003A)
- * - 0-9 (U+0061 - U+007A)
- * - ISO 10646 characters U+00A1 and higher
- */
- // Make sure we allow multiple classes to be added
- $cssIdentifiers = explode(' ', $value);
+ /**
+ * Valid characters in a CSS identifier are:
+ * - the hyphen (U+002D)
+ * - a-z (U+0030 - U+0039)
+ * - A-Z (U+0041 - U+005A)
+ * - the underscore (U+005F)
+ * - the colon (U+003A)
+ * - 0-9 (U+0061 - U+007A)
+ * - ISO 10646 characters U+00A1 and higher
+ */
+ // Make sure we allow multiple classes to be added
+ $cssIdentifiers = explode(' ', $value);
- foreach ($cssIdentifiers as $identifier)
- {
- if (preg_match('/[^\\x{002D}\\x{0030}-\\x{0039}\\x{0041}-\\x{005A}\\x{005F}\\x{003A}\\x{0061}-\\x{007A}\\x{00A1}-\\x{FFFF}]/u', $identifier))
- {
- return false;
- }
- }
+ foreach ($cssIdentifiers as $identifier) {
+ if (preg_match('/[^\\x{002D}\\x{0030}-\\x{0039}\\x{0041}-\\x{005A}\\x{005F}\\x{003A}\\x{0061}-\\x{007A}\\x{00A1}-\\x{FFFF}]/u', $identifier)) {
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/EmailRule.php b/libraries/src/Form/Rule/EmailRule.php
index 65c59f3b5d396..e7a3e09ee3bd5 100644
--- a/libraries/src/Form/Rule/EmailRule.php
+++ b/libraries/src/Form/Rule/EmailRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return mixed Boolean true if field value is valid.
- *
- * @since 1.7.0
- * @throws \UnexpectedValueException
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
-
- if (!$required && empty($value))
- {
- return true;
- }
-
- // If the tld attribute is present, change the regular expression to require at least 2 characters for it.
- $tld = ((string) $element['tld'] === 'tld' || (string) $element['tld'] === 'required');
-
- if ($tld)
- {
- $this->regex = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])"
- . '?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$';
- }
-
- // Determine if the multiple attribute is present
- $multiple = ((string) $element['multiple'] === 'true' || (string) $element['multiple'] === 'multiple');
-
- if (!$multiple)
- {
- // Handle idn email addresses by converting to punycode.
- $value = PunycodeHelper::emailToPunycode($value);
-
- // Test the value against the regular expression.
- if (!parent::test($element, $value, $group, $input, $form))
- {
- throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
- }
- }
- else
- {
- $values = explode(',', $value);
-
- foreach ($values as $value)
- {
- // Handle idn email addresses by converting to punycode.
- $value = PunycodeHelper::emailToPunycode($value);
-
- // Test the value against the regular expression.
- if (!parent::test($element, $value, $group, $input, $form))
- {
- throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
- }
- }
- }
-
- /**
- * validDomains value should consist of component name and the name of domain list field in component's configuration, separated by a dot.
- * This allows different components and contexts to use different lists.
- * If value is incomplete, com_users.domains is used as fallback.
- */
- $validDomains = (string) $element['validDomains'] !== '' && (string) $element['validDomains'] !== 'false';
-
- if ($validDomains && !$multiple)
- {
- $config = explode('.', $element['validDomains'], 2);
-
- if (\count($config) > 1)
- {
- $domains = ComponentHelper::getParams($config[0])->get($config[1]);
- }
- else
- {
- $domains = ComponentHelper::getParams('com_users')->get('domains');
- }
-
- if ($domains)
- {
- $emailDomain = explode('@', $value);
- $emailDomain = $emailDomain[1];
- $emailParts = array_reverse(explode('.', $emailDomain));
- $emailCount = \count($emailParts);
- $allowed = true;
-
- foreach ($domains as $domain)
- {
- $domainParts = array_reverse(explode('.', $domain->name));
- $status = 0;
-
- // Don't run if the email has less segments than the rule.
- if ($emailCount < \count($domainParts))
- {
- continue;
- }
-
- foreach ($emailParts as $key => $emailPart)
- {
- if (!isset($domainParts[$key]) || $domainParts[$key] == $emailPart || $domainParts[$key] == '*')
- {
- $status++;
- }
- }
-
- // All segments match, check whether to allow the domain or not.
- if ($status === $emailCount)
- {
- if ($domain->rule == 0)
- {
- $allowed = false;
- }
- else
- {
- $allowed = true;
- }
- }
- }
-
- // If domain is not allowed, fail validation. Otherwise continue.
- if (!$allowed)
- {
- throw new \UnexpectedValueException(Text::sprintf('JGLOBAL_EMAIL_DOMAIN_NOT_ALLOWED', $emailDomain));
- }
- }
- }
-
- // Check if we should test for uniqueness. This only can be used if multiple is not true
- $unique = ((string) $element['unique'] === 'true' || (string) $element['unique'] === 'unique');
-
- if ($unique && !$multiple)
- {
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
-
- // Get the extra field check attribute.
- $userId = ($form instanceof Form) ? (int) $form->getValue('id') : 0;
-
- // Build the query.
- $query->select('COUNT(*)')
- ->from($db->quoteName('#__users'))
- ->where(
- [
- $db->quoteName('email') . ' = :email',
- $db->quoteName('id') . ' <> :userId',
- ]
- )
- ->bind(':email', $value)
- ->bind(':userId', $userId, ParameterType::INTEGER);
-
- // Set and query the database.
- $db->setQuery($query);
- $duplicate = (bool) $db->loadResult();
-
- if ($duplicate)
- {
- throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));
- }
- }
-
- return true;
- }
+ use DatabaseAwareTrait;
+
+ /**
+ * The regular expression to use in testing a form field value.
+ *
+ * @var string
+ * @since 1.7.0
+ * @link https://www.w3.org/TR/html/sec-forms.html#email-state-typeemail
+ */
+ protected $regex = "^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])"
+ . "?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$";
+
+ /**
+ * Method to test the email address and optionally check for uniqueness.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return mixed Boolean true if field value is valid.
+ *
+ * @since 1.7.0
+ * @throws \UnexpectedValueException
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+
+ if (!$required && empty($value)) {
+ return true;
+ }
+
+ // If the tld attribute is present, change the regular expression to require at least 2 characters for it.
+ $tld = ((string) $element['tld'] === 'tld' || (string) $element['tld'] === 'required');
+
+ if ($tld) {
+ $this->regex = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])"
+ . '?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$';
+ }
+
+ // Determine if the multiple attribute is present
+ $multiple = ((string) $element['multiple'] === 'true' || (string) $element['multiple'] === 'multiple');
+
+ if (!$multiple) {
+ // Handle idn email addresses by converting to punycode.
+ $value = PunycodeHelper::emailToPunycode($value);
+
+ // Test the value against the regular expression.
+ if (!parent::test($element, $value, $group, $input, $form)) {
+ throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
+ }
+ } else {
+ $values = explode(',', $value);
+
+ foreach ($values as $value) {
+ // Handle idn email addresses by converting to punycode.
+ $value = PunycodeHelper::emailToPunycode($value);
+
+ // Test the value against the regular expression.
+ if (!parent::test($element, $value, $group, $input, $form)) {
+ throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
+ }
+ }
+ }
+
+ /**
+ * validDomains value should consist of component name and the name of domain list field in component's configuration, separated by a dot.
+ * This allows different components and contexts to use different lists.
+ * If value is incomplete, com_users.domains is used as fallback.
+ */
+ $validDomains = (string) $element['validDomains'] !== '' && (string) $element['validDomains'] !== 'false';
+
+ if ($validDomains && !$multiple) {
+ $config = explode('.', $element['validDomains'], 2);
+
+ if (\count($config) > 1) {
+ $domains = ComponentHelper::getParams($config[0])->get($config[1]);
+ } else {
+ $domains = ComponentHelper::getParams('com_users')->get('domains');
+ }
+
+ if ($domains) {
+ $emailDomain = explode('@', $value);
+ $emailDomain = $emailDomain[1];
+ $emailParts = array_reverse(explode('.', $emailDomain));
+ $emailCount = \count($emailParts);
+ $allowed = true;
+
+ foreach ($domains as $domain) {
+ $domainParts = array_reverse(explode('.', $domain->name));
+ $status = 0;
+
+ // Don't run if the email has less segments than the rule.
+ if ($emailCount < \count($domainParts)) {
+ continue;
+ }
+
+ foreach ($emailParts as $key => $emailPart) {
+ if (!isset($domainParts[$key]) || $domainParts[$key] == $emailPart || $domainParts[$key] == '*') {
+ $status++;
+ }
+ }
+
+ // All segments match, check whether to allow the domain or not.
+ if ($status === $emailCount) {
+ if ($domain->rule == 0) {
+ $allowed = false;
+ } else {
+ $allowed = true;
+ }
+ }
+ }
+
+ // If domain is not allowed, fail validation. Otherwise continue.
+ if (!$allowed) {
+ throw new \UnexpectedValueException(Text::sprintf('JGLOBAL_EMAIL_DOMAIN_NOT_ALLOWED', $emailDomain));
+ }
+ }
+ }
+
+ // Check if we should test for uniqueness. This only can be used if multiple is not true
+ $unique = ((string) $element['unique'] === 'true' || (string) $element['unique'] === 'unique');
+
+ if ($unique && !$multiple) {
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
+
+ // Get the extra field check attribute.
+ $userId = ($form instanceof Form) ? (int) $form->getValue('id') : 0;
+
+ // Build the query.
+ $query->select('COUNT(*)')
+ ->from($db->quoteName('#__users'))
+ ->where(
+ [
+ $db->quoteName('email') . ' = :email',
+ $db->quoteName('id') . ' <> :userId',
+ ]
+ )
+ ->bind(':email', $value)
+ ->bind(':userId', $userId, ParameterType::INTEGER);
+
+ // Set and query the database.
+ $db->setQuery($query);
+ $duplicate = (bool) $db->loadResult();
+
+ if ($duplicate) {
+ throw new \UnexpectedValueException(Text::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));
+ }
+ }
+
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/EqualsRule.php b/libraries/src/Form/Rule/EqualsRule.php
index 7cf4a38e602da..c91f14e334f2b 100644
--- a/libraries/src/Form/Rule/EqualsRule.php
+++ b/libraries/src/Form/Rule/EqualsRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- * @throws \InvalidArgumentException
- * @throws \UnexpectedValueException
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $field = (string) $element['field'];
+ /**
+ * Method to test if two values are equal. To use this rule, the form
+ * XML needs a validate attribute of equals and a field attribute
+ * that is equal to the field to test against.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ * @throws \InvalidArgumentException
+ * @throws \UnexpectedValueException
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $field = (string) $element['field'];
- // Check that a validation field is set.
- if (!$field)
- {
- throw new \UnexpectedValueException(sprintf('$field empty in %s::test', \get_class($this)));
- }
+ // Check that a validation field is set.
+ if (!$field) {
+ throw new \UnexpectedValueException(sprintf('$field empty in %s::test', \get_class($this)));
+ }
- if (\is_null($form))
- {
- throw new \InvalidArgumentException(sprintf('The value for $form must not be null in %s', \get_class($this)));
- }
+ if (\is_null($form)) {
+ throw new \InvalidArgumentException(sprintf('The value for $form must not be null in %s', \get_class($this)));
+ }
- if (\is_null($input))
- {
- throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', \get_class($this)));
- }
+ if (\is_null($input)) {
+ throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', \get_class($this)));
+ }
- $test = $input->get($field);
+ $test = $input->get($field);
- if (isset($group) && $group !== '')
- {
- $test = $input->get($group . '.' . $field);
- }
+ if (isset($group) && $group !== '') {
+ $test = $input->get($group . '.' . $field);
+ }
- // Test the two values against each other.
- return $value == $test;
- }
+ // Test the two values against each other.
+ return $value == $test;
+ }
}
diff --git a/libraries/src/Form/Rule/ExistsRule.php b/libraries/src/Form/Rule/ExistsRule.php
index 1852d29eb9960..f634c297ea6f1 100644
--- a/libraries/src/Form/Rule/ExistsRule.php
+++ b/libraries/src/Form/Rule/ExistsRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.9.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $value = trim($value);
+ /**
+ * Method to test the username for uniqueness.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.9.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $value = trim($value);
- $existsTable = (string) $element['exists_table'];
- $existsColumn = (string) $element['exists_column'];
+ $existsTable = (string) $element['exists_table'];
+ $existsColumn = (string) $element['exists_column'];
- // We cannot validate without a table name
- if ($existsTable === '')
- {
- return true;
- }
+ // We cannot validate without a table name
+ if ($existsTable === '') {
+ return true;
+ }
- // Assume a default column name of `id`
- if ($existsColumn === '')
- {
- $existsColumn = 'id';
- }
+ // Assume a default column name of `id`
+ if ($existsColumn === '') {
+ $existsColumn = 'id';
+ }
- $db = $this->getDatabase();
+ $db = $this->getDatabase();
- // Set and query the database.
- $exists = $db->setQuery(
- $db->getQuery(true)
- ->select('COUNT(*)')
- ->from($db->quoteName($existsTable))
- ->where($db->quoteName($existsColumn) . ' = ' . $db->quote($value))
- )->loadResult();
+ // Set and query the database.
+ $exists = $db->setQuery(
+ $db->getQuery(true)
+ ->select('COUNT(*)')
+ ->from($db->quoteName($existsTable))
+ ->where($db->quoteName($existsColumn) . ' = ' . $db->quote($value))
+ )->loadResult();
- return (int) $exists > 0;
- }
+ return (int) $exists > 0;
+ }
}
diff --git a/libraries/src/Form/Rule/FilePathRule.php b/libraries/src/Form/Rule/FilePathRule.php
index 8452acd00cefb..97a991ea53de4 100644
--- a/libraries/src/Form/Rule/FilePathRule.php
+++ b/libraries/src/Form/Rule/FilePathRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.9.21
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $value = trim($value);
+ /**
+ * Method to test if the file path is valid
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.9.21
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $value = trim($value);
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');
- if (!$required && empty($value))
- {
- return true;
- }
+ if (!$required && empty($value)) {
+ return true;
+ }
- // Get the exclude setting from the xml
- $exclude = (array) explode('|', (string) $element['exclude']);
+ // Get the exclude setting from the xml
+ $exclude = (array) explode('|', (string) $element['exclude']);
- // Exclude current folder '.' to be safe from full path disclosure
- $exclude[] = '.';
+ // Exclude current folder '.' to be safe from full path disclosure
+ $exclude[] = '.';
- // Check the exclude setting
- $path = preg_split('/[\/\\\\]/', $value);
+ // Check the exclude setting
+ $path = preg_split('/[\/\\\\]/', $value);
- if (in_array(strtolower($path[0]), $exclude) || empty($path[0]))
- {
- return false;
- }
+ if (in_array(strtolower($path[0]), $exclude) || empty($path[0])) {
+ return false;
+ }
- // Prepend the root path
- $value = JPATH_ROOT . '/' . $value;
+ // Prepend the root path
+ $value = JPATH_ROOT . '/' . $value;
- // Check if $value is a valid path, which includes not allowing to break out of the current path
- try
- {
- Path::check($value);
- }
- catch (\Exception $e)
- {
- // When there is an exception in the check path this is not valid
- return false;
- }
+ // Check if $value is a valid path, which includes not allowing to break out of the current path
+ try {
+ Path::check($value);
+ } catch (\Exception $e) {
+ // When there is an exception in the check path this is not valid
+ return false;
+ }
- // When there are no exception this rule should pass.
- // See: https://github.com/joomla/joomla-cms/issues/30500#issuecomment-683290162
- return true;
- }
+ // When there are no exception this rule should pass.
+ // See: https://github.com/joomla/joomla-cms/issues/30500#issuecomment-683290162
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/FolderPathExistsRule.php b/libraries/src/Form/Rule/FolderPathExistsRule.php
index 9ebadb05b0a13..970c0af0555c6 100644
--- a/libraries/src/Form/Rule/FolderPathExistsRule.php
+++ b/libraries/src/Form/Rule/FolderPathExistsRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid and points to an existing folder below the Joomla root, false otherwise.
- *
- * @since 4.0.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- if (!parent::test($element, $value, $group, $input, $form))
- {
- return false;
- }
+ /**
+ * Method to test if the folder path is valid and points to an existing folder below the Joomla root
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid and points to an existing folder below the Joomla root, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ if (!parent::test($element, $value, $group, $input, $form)) {
+ return false;
+ }
- // If the field is empty and not required so the previous test hasn't failed, the field is valid.
- if ($value === '' || $value === null)
- {
- return true;
- }
+ // If the field is empty and not required so the previous test hasn't failed, the field is valid.
+ if ($value === '' || $value === null) {
+ return true;
+ }
- // Spaces only would result in Joomla root which is not allowed
- if (!trim($value))
- {
- return false;
- }
+ // Spaces only would result in Joomla root which is not allowed
+ if (!trim($value)) {
+ return false;
+ }
- $pathCleaned = rtrim(Path::clean(JPATH_ROOT . '/' . $value), \DIRECTORY_SEPARATOR);
- $rootCleaned = rtrim(Path::clean(JPATH_ROOT), \DIRECTORY_SEPARATOR);
+ $pathCleaned = rtrim(Path::clean(JPATH_ROOT . '/' . $value), \DIRECTORY_SEPARATOR);
+ $rootCleaned = rtrim(Path::clean(JPATH_ROOT), \DIRECTORY_SEPARATOR);
- // JPATH_ROOT is not allowed
- if ($pathCleaned === $rootCleaned)
- {
- return false;
- }
+ // JPATH_ROOT is not allowed
+ if ($pathCleaned === $rootCleaned) {
+ return false;
+ }
- return Folder::exists($pathCleaned);
- }
+ return Folder::exists($pathCleaned);
+ }
}
diff --git a/libraries/src/Form/Rule/ModuleLayoutRule.php b/libraries/src/Form/Rule/ModuleLayoutRule.php
index 00e9136b46e31..10cc28bcbede3 100644
--- a/libraries/src/Form/Rule/ModuleLayoutRule.php
+++ b/libraries/src/Form/Rule/ModuleLayoutRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- * @throws \InvalidArgumentException
- * @throws \UnexpectedValueException
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $field = (string) $element['field'];
-
- // Check that a validation field is set.
- if (!$field)
- {
- throw new \UnexpectedValueException(sprintf('$field empty in %s::test', \get_class($this)));
- }
-
- if ($input === null)
- {
- throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', \get_class($this)));
- }
-
- // Test the two values against each other.
- if ($value != $input->get($field))
- {
- return true;
- }
-
- return false;
- }
+ /**
+ * Method to test if two values are not equal. To use this rule, the form
+ * XML needs a validate attribute of equals and a field attribute
+ * that is equal to the field to test against.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ * @throws \InvalidArgumentException
+ * @throws \UnexpectedValueException
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $field = (string) $element['field'];
+
+ // Check that a validation field is set.
+ if (!$field) {
+ throw new \UnexpectedValueException(sprintf('$field empty in %s::test', \get_class($this)));
+ }
+
+ if ($input === null) {
+ throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', \get_class($this)));
+ }
+
+ // Test the two values against each other.
+ if ($value != $input->get($field)) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/libraries/src/Form/Rule/NumberRule.php b/libraries/src/Form/Rule/NumberRule.php
index 1700242a75210..b62bb399ce943 100644
--- a/libraries/src/Form/Rule/NumberRule.php
+++ b/libraries/src/Form/Rule/NumberRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.5
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Check if the field is required.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test the range for a number value using min and max attributes.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.5
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Check if the field is required.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- // If the value is empty and the field is not required return True.
- if (($value === '' || $value === null) && ! $required)
- {
- return true;
- }
+ // If the value is empty and the field is not required return True.
+ if (($value === '' || $value === null) && ! $required) {
+ return true;
+ }
- $float_value = (float) $value;
+ $float_value = (float) $value;
- if (isset($element['min']))
- {
- $min = (float) $element['min'];
+ if (isset($element['min'])) {
+ $min = (float) $element['min'];
- if ($min > $float_value)
- {
- return false;
- }
- }
+ if ($min > $float_value) {
+ return false;
+ }
+ }
- if (isset($element['max']))
- {
- $max = (float) $element['max'];
+ if (isset($element['max'])) {
+ $max = (float) $element['max'];
- if ($max < $float_value)
- {
- return false;
- }
- }
+ if ($max < $float_value) {
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/OptionsRule.php b/libraries/src/Form/Rule/OptionsRule.php
index 5aa95121171c7..e24fea485e89a 100644
--- a/libraries/src/Form/Rule/OptionsRule.php
+++ b/libraries/src/Form/Rule/OptionsRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Check if the field is required.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test the value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Check if the field is required.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- // Check if the value is empty.
- $blank = empty($value) && $value !== '0' && $value !== 0 && $value !== 0.0;
+ // Check if the value is empty.
+ $blank = empty($value) && $value !== '0' && $value !== 0 && $value !== 0.0;
- if (!$required && $blank)
- {
- return true;
- }
+ if (!$required && $blank) {
+ return true;
+ }
- // Make an array of all available option values.
- $options = array();
+ // Make an array of all available option values.
+ $options = array();
- // Create the field
- $field = null;
+ // Create the field
+ $field = null;
- if ($form)
- {
- $field = $form->getField((string) $element->attributes()->name, $group);
- }
+ if ($form) {
+ $field = $form->getField((string) $element->attributes()->name, $group);
+ }
- // When the field exists, the real options are fetched.
- // This is needed for fields which do have dynamic options like from a database.
- if ($field && \is_array($field->options))
- {
- foreach ($field->options as $opt)
- {
- $options[] = $opt->value;
- }
- }
- else
- {
- foreach ($element->option as $opt)
- {
- $options[] = $opt->attributes()->value;
- }
- }
+ // When the field exists, the real options are fetched.
+ // This is needed for fields which do have dynamic options like from a database.
+ if ($field && \is_array($field->options)) {
+ foreach ($field->options as $opt) {
+ $options[] = $opt->value;
+ }
+ } else {
+ foreach ($element->option as $opt) {
+ $options[] = $opt->attributes()->value;
+ }
+ }
- // There may be multiple values in the form of an array (if the element is checkboxes, for example).
- if (\is_array($value))
- {
- // If all values are in the $options array, $diff will be empty and the options valid.
- $diff = array_diff($value, $options);
+ // There may be multiple values in the form of an array (if the element is checkboxes, for example).
+ if (\is_array($value)) {
+ // If all values are in the $options array, $diff will be empty and the options valid.
+ $diff = array_diff($value, $options);
- return empty($diff);
- }
- else
- {
- // In this case value must be a string
- return \in_array((string) $value, $options);
- }
- }
+ return empty($diff);
+ } else {
+ // In this case value must be a string
+ return \in_array((string) $value, $options);
+ }
+ }
}
diff --git a/libraries/src/Form/Rule/PasswordRule.php b/libraries/src/Form/Rule/PasswordRule.php
index 947d71ac7d664..de7c07e18a766 100644
--- a/libraries/src/Form/Rule/PasswordRule.php
+++ b/libraries/src/Form/Rule/PasswordRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.1.2
- * @throws \InvalidArgumentException
- * @throws \UnexpectedValueException
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- $meter = isset($element['strengthmeter']) ? ' meter="0"' : '1';
- $threshold = isset($element['threshold']) ? (int) $element['threshold'] : 66;
- $minimumLength = isset($element['minimum_length']) ? (int) $element['minimum_length'] : 12;
- $minimumIntegers = isset($element['minimum_integers']) ? (int) $element['minimum_integers'] : 0;
- $minimumSymbols = isset($element['minimum_symbols']) ? (int) $element['minimum_symbols'] : 0;
- $minimumUppercase = isset($element['minimum_uppercase']) ? (int) $element['minimum_uppercase'] : 0;
- $minimumLowercase = isset($element['minimum_lowercase']) ? (int) $element['minimum_lowercase'] : 0;
-
- // In the installer we don't have any access to the
- // database yet so use the hard coded default settings
- if (!Factory::getApplication()->isClient('installation'))
- {
- // If we have parameters from com_users, use those instead.
- // Some of these may be empty for legacy reasons.
- $params = ComponentHelper::getParams('com_users');
-
- if (!empty($params))
- {
- $minimumLengthp = $params->get('minimum_length', 12);
- $minimumIntegersp = $params->get('minimum_integers', 0);
- $minimumSymbolsp = $params->get('minimum_symbols', 0);
- $minimumUppercasep = $params->get('minimum_uppercase', 0);
- $minimumLowercasep = $params->get('minimum_lowercase', 0);
- $meterp = $params->get('meter');
- $thresholdp = $params->get('threshold', 66);
-
- empty($minimumLengthp) ? : $minimumLength = (int) $minimumLengthp;
- empty($minimumIntegersp) ? : $minimumIntegers = (int) $minimumIntegersp;
- empty($minimumSymbolsp) ? : $minimumSymbols = (int) $minimumSymbolsp;
- empty($minimumUppercasep) ? : $minimumUppercase = (int) $minimumUppercasep;
- empty($minimumLowercasep) ? : $minimumLowercase = (int) $minimumLowercasep;
- empty($meterp) ? : $meter = $meterp;
- empty($thresholdp) ? : $threshold = $thresholdp;
- }
- }
-
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
-
- if (!$required && empty($value))
- {
- return true;
- }
-
- $valueLength = \strlen($value);
-
- // We set a maximum length to prevent abuse since it is unfiltered.
- if ($valueLength > 4096)
- {
- Factory::getApplication()->enqueueMessage(Text::_('JFIELD_PASSWORD_TOO_LONG'), 'error');
- }
-
- // We don't allow white space inside passwords
- $valueTrim = trim($value);
-
- // Set a variable to check if any errors are made in password
- $validPassword = true;
-
- if (\strlen($valueTrim) !== $valueLength)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JFIELD_PASSWORD_SPACES_IN_PASSWORD'),
- 'error'
- );
-
- $validPassword = false;
- }
-
- // Minimum number of integers required
- if (!empty($minimumIntegers))
- {
- $nInts = preg_match_all('/[0-9]/', $value, $imatch);
-
- if ($nInts < $minimumIntegers)
- {
- Factory::getApplication()->enqueueMessage(
- Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_INTEGERS_N', $minimumIntegers),
- 'error'
- );
-
- $validPassword = false;
- }
- }
-
- // Minimum number of symbols required
- if (!empty($minimumSymbols))
- {
- $nsymbols = preg_match_all('[\W]', $value, $smatch);
-
- if ($nsymbols < $minimumSymbols)
- {
- Factory::getApplication()->enqueueMessage(
- Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_SYMBOLS_N', $minimumSymbols),
- 'error'
- );
-
- $validPassword = false;
- }
- }
-
- // Minimum number of upper case ASCII characters required
- if (!empty($minimumUppercase))
- {
- $nUppercase = preg_match_all('/[A-Z]/', $value, $umatch);
-
- if ($nUppercase < $minimumUppercase)
- {
- Factory::getApplication()->enqueueMessage(
- Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_UPPERCASE_LETTERS_N', $minimumUppercase),
- 'error'
- );
-
- $validPassword = false;
- }
- }
-
- // Minimum number of lower case ASCII characters required
- if (!empty($minimumLowercase))
- {
- $nLowercase = preg_match_all('/[a-z]/', $value, $umatch);
-
- if ($nLowercase < $minimumLowercase)
- {
- Factory::getApplication()->enqueueMessage(
- Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_LOWERCASE_LETTERS_N', $minimumLowercase),
- 'error'
- );
-
- $validPassword = false;
- }
- }
-
- // Minimum length option
- if (!empty($minimumLength))
- {
- if (\strlen((string) $value) < $minimumLength)
- {
- Factory::getApplication()->enqueueMessage(
- Text::plural('JFIELD_PASSWORD_TOO_SHORT_N', $minimumLength),
- 'error'
- );
-
- $validPassword = false;
- }
- }
-
- // If valid has violated any rules above return false.
- if (!$validPassword)
- {
- return false;
- }
-
- return true;
- }
+ /**
+ * Method to test if two values are not equal. To use this rule, the form
+ * XML needs a validate attribute of equals and a field attribute
+ * that is equal to the field to test against.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.1.2
+ * @throws \InvalidArgumentException
+ * @throws \UnexpectedValueException
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ $meter = isset($element['strengthmeter']) ? ' meter="0"' : '1';
+ $threshold = isset($element['threshold']) ? (int) $element['threshold'] : 66;
+ $minimumLength = isset($element['minimum_length']) ? (int) $element['minimum_length'] : 12;
+ $minimumIntegers = isset($element['minimum_integers']) ? (int) $element['minimum_integers'] : 0;
+ $minimumSymbols = isset($element['minimum_symbols']) ? (int) $element['minimum_symbols'] : 0;
+ $minimumUppercase = isset($element['minimum_uppercase']) ? (int) $element['minimum_uppercase'] : 0;
+ $minimumLowercase = isset($element['minimum_lowercase']) ? (int) $element['minimum_lowercase'] : 0;
+
+ // In the installer we don't have any access to the
+ // database yet so use the hard coded default settings
+ if (!Factory::getApplication()->isClient('installation')) {
+ // If we have parameters from com_users, use those instead.
+ // Some of these may be empty for legacy reasons.
+ $params = ComponentHelper::getParams('com_users');
+
+ if (!empty($params)) {
+ $minimumLengthp = $params->get('minimum_length', 12);
+ $minimumIntegersp = $params->get('minimum_integers', 0);
+ $minimumSymbolsp = $params->get('minimum_symbols', 0);
+ $minimumUppercasep = $params->get('minimum_uppercase', 0);
+ $minimumLowercasep = $params->get('minimum_lowercase', 0);
+ $meterp = $params->get('meter');
+ $thresholdp = $params->get('threshold', 66);
+
+ empty($minimumLengthp) ? : $minimumLength = (int) $minimumLengthp;
+ empty($minimumIntegersp) ? : $minimumIntegers = (int) $minimumIntegersp;
+ empty($minimumSymbolsp) ? : $minimumSymbols = (int) $minimumSymbolsp;
+ empty($minimumUppercasep) ? : $minimumUppercase = (int) $minimumUppercasep;
+ empty($minimumLowercasep) ? : $minimumLowercase = (int) $minimumLowercasep;
+ empty($meterp) ? : $meter = $meterp;
+ empty($thresholdp) ? : $threshold = $thresholdp;
+ }
+ }
+
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+
+ if (!$required && empty($value)) {
+ return true;
+ }
+
+ $valueLength = \strlen($value);
+
+ // We set a maximum length to prevent abuse since it is unfiltered.
+ if ($valueLength > 4096) {
+ Factory::getApplication()->enqueueMessage(Text::_('JFIELD_PASSWORD_TOO_LONG'), 'error');
+ }
+
+ // We don't allow white space inside passwords
+ $valueTrim = trim($value);
+
+ // Set a variable to check if any errors are made in password
+ $validPassword = true;
+
+ if (\strlen($valueTrim) !== $valueLength) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JFIELD_PASSWORD_SPACES_IN_PASSWORD'),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+
+ // Minimum number of integers required
+ if (!empty($minimumIntegers)) {
+ $nInts = preg_match_all('/[0-9]/', $value, $imatch);
+
+ if ($nInts < $minimumIntegers) {
+ Factory::getApplication()->enqueueMessage(
+ Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_INTEGERS_N', $minimumIntegers),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+ }
+
+ // Minimum number of symbols required
+ if (!empty($minimumSymbols)) {
+ $nsymbols = preg_match_all('[\W]', $value, $smatch);
+
+ if ($nsymbols < $minimumSymbols) {
+ Factory::getApplication()->enqueueMessage(
+ Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_SYMBOLS_N', $minimumSymbols),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+ }
+
+ // Minimum number of upper case ASCII characters required
+ if (!empty($minimumUppercase)) {
+ $nUppercase = preg_match_all('/[A-Z]/', $value, $umatch);
+
+ if ($nUppercase < $minimumUppercase) {
+ Factory::getApplication()->enqueueMessage(
+ Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_UPPERCASE_LETTERS_N', $minimumUppercase),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+ }
+
+ // Minimum number of lower case ASCII characters required
+ if (!empty($minimumLowercase)) {
+ $nLowercase = preg_match_all('/[a-z]/', $value, $umatch);
+
+ if ($nLowercase < $minimumLowercase) {
+ Factory::getApplication()->enqueueMessage(
+ Text::plural('JFIELD_PASSWORD_NOT_ENOUGH_LOWERCASE_LETTERS_N', $minimumLowercase),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+ }
+
+ // Minimum length option
+ if (!empty($minimumLength)) {
+ if (\strlen((string) $value) < $minimumLength) {
+ Factory::getApplication()->enqueueMessage(
+ Text::plural('JFIELD_PASSWORD_TOO_SHORT_N', $minimumLength),
+ 'error'
+ );
+
+ $validPassword = false;
+ }
+ }
+
+ // If valid has violated any rules above return false.
+ if (!$validPassword) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/RulesRule.php b/libraries/src/Form/Rule/RulesRule.php
index 218eb0abac72f..dc2c2f104b906 100644
--- a/libraries/src/Form/Rule/RulesRule.php
+++ b/libraries/src/Form/Rule/RulesRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Get the possible field actions and the ones posted to validate them.
- $fieldActions = self::getFieldActions($element);
- $valueActions = self::getValueActions($value);
+ /**
+ * Method to test the value.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Get the possible field actions and the ones posted to validate them.
+ $fieldActions = self::getFieldActions($element);
+ $valueActions = self::getValueActions($value);
- // Make sure that all posted actions are in the list of possible actions for the field.
- foreach ($valueActions as $action)
- {
- if (!\in_array($action, $fieldActions))
- {
- return false;
- }
- }
+ // Make sure that all posted actions are in the list of possible actions for the field.
+ foreach ($valueActions as $action) {
+ if (!\in_array($action, $fieldActions)) {
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
- /**
- * Method to get the list of permission action names from the form field value.
- *
- * @param mixed $value The form field value to validate.
- *
- * @return array A list of permission action names from the form field value.
- *
- * @since 1.7.0
- */
- protected function getValueActions($value)
- {
- $actions = array();
+ /**
+ * Method to get the list of permission action names from the form field value.
+ *
+ * @param mixed $value The form field value to validate.
+ *
+ * @return array A list of permission action names from the form field value.
+ *
+ * @since 1.7.0
+ */
+ protected function getValueActions($value)
+ {
+ $actions = array();
- // Iterate over the asset actions and add to the actions.
- foreach ((array) $value as $name => $rules)
- {
- $actions[] = $name;
- }
+ // Iterate over the asset actions and add to the actions.
+ foreach ((array) $value as $name => $rules) {
+ $actions[] = $name;
+ }
- return $actions;
- }
+ return $actions;
+ }
- /**
- * Method to get the list of possible permission action names for the form field.
- *
- * @param \SimpleXMLElement $element The \SimpleXMLElement object representing the `` tag for the form field object.
- *
- * @return array A list of permission action names from the form field element definition.
- *
- * @since 1.7.0
- */
- protected function getFieldActions(\SimpleXMLElement $element)
- {
- $actions = array();
+ /**
+ * Method to get the list of possible permission action names for the form field.
+ *
+ * @param \SimpleXMLElement $element The \SimpleXMLElement object representing the `` tag for the form field object.
+ *
+ * @return array A list of permission action names from the form field element definition.
+ *
+ * @since 1.7.0
+ */
+ protected function getFieldActions(\SimpleXMLElement $element)
+ {
+ $actions = array();
- // Initialise some field attributes.
- $section = $element['section'] ? (string) $element['section'] : '';
- $component = $element['component'] ? (string) $element['component'] : '';
+ // Initialise some field attributes.
+ $section = $element['section'] ? (string) $element['section'] : '';
+ $component = $element['component'] ? (string) $element['component'] : '';
- // Get the asset actions for the element.
- $elActions = Access::getActionsFromFile(
- JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
- "/access/section[@name='" . $section . "']/"
- );
+ // Get the asset actions for the element.
+ $elActions = Access::getActionsFromFile(
+ JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
+ "/access/section[@name='" . $section . "']/"
+ );
- if ($elActions)
- {
- // Iterate over the asset actions and add to the actions.
- foreach ($elActions as $item)
- {
- $actions[] = $item->name;
- }
- }
+ if ($elActions) {
+ // Iterate over the asset actions and add to the actions.
+ foreach ($elActions as $item) {
+ $actions[] = $item->name;
+ }
+ }
- // Iterate over the children and add to the actions.
- foreach ($element->children() as $el)
- {
- if ($el->getName() === 'action')
- {
- $actions[] = (string) $el['name'];
- }
- }
+ // Iterate over the children and add to the actions.
+ foreach ($element->children() as $el) {
+ if ($el->getName() === 'action') {
+ $actions[] = (string) $el['name'];
+ }
+ }
- return $actions;
- }
+ return $actions;
+ }
}
diff --git a/libraries/src/Form/Rule/SubformRule.php b/libraries/src/Form/Rule/SubformRule.php
index 06b2b6b8032bb..c7e69c70fb4fc 100644
--- a/libraries/src/Form/Rule/SubformRule.php
+++ b/libraries/src/Form/Rule/SubformRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 3.9.7
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Get the form field object.
- $field = $form->getField($element['name'], $group);
+ /**
+ * Method to test given values for a subform..
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 3.9.7
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Get the form field object.
+ $field = $form->getField($element['name'], $group);
- if (!($field instanceof SubformField))
- {
- throw new \UnexpectedValueException(sprintf('%s is no subform field.', $element['name']));
- }
+ if (!($field instanceof SubformField)) {
+ throw new \UnexpectedValueException(sprintf('%s is no subform field.', $element['name']));
+ }
- if ($value === null)
- {
- return true;
- }
+ if ($value === null) {
+ return true;
+ }
- $subForm = $field->loadSubForm();
+ $subForm = $field->loadSubForm();
- // Multiple values: Validate every row.
- if ($field->multiple)
- {
- foreach ($value as $row)
- {
- if ($subForm->validate($row) === false)
- {
- // Pass the first error that occurred on the subform validation.
- $errors = $subForm->getErrors();
+ // Multiple values: Validate every row.
+ if ($field->multiple) {
+ foreach ($value as $row) {
+ if ($subForm->validate($row) === false) {
+ // Pass the first error that occurred on the subform validation.
+ $errors = $subForm->getErrors();
- if (!empty($errors[0]))
- {
- return $errors[0];
- }
+ if (!empty($errors[0])) {
+ return $errors[0];
+ }
- return false;
- }
- }
- }
- // Single value.
- else
- {
- if ($subForm->validate($value) === false)
- {
- // Pass the first error that occurred on the subform validation.
- $errors = $subForm->getErrors();
+ return false;
+ }
+ }
+ } else {
+ // Single value.
+ if ($subForm->validate($value) === false) {
+ // Pass the first error that occurred on the subform validation.
+ $errors = $subForm->getErrors();
- if (!empty($errors[0]))
- {
- return $errors[0];
- }
+ if (!empty($errors[0])) {
+ return $errors[0];
+ }
- return false;
- }
- }
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/TelRule.php b/libraries/src/Form/Rule/TelRule.php
index 62079b94a6e58..f007825ee306a 100644
--- a/libraries/src/Form/Rule/TelRule.php
+++ b/libraries/src/Form/Rule/TelRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test the url for a valid parts.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- if (!$required && empty($value))
- {
- return true;
- }
+ if (!$required && empty($value)) {
+ return true;
+ }
- /*
- * @link http://www.nanpa.com/
- * @link http://tools.ietf.org/html/rfc4933
- * @link http://www.itu.int/rec/T-REC-E.164/en
- *
- * Regex by Steve Levithan
- * @link http://blog.stevenlevithan.com/archives/validate-phone-number
- * @note that valid ITU-T and EPP must begin with +.
- */
- $regexarray = array(
- 'NANP' => '/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/',
- 'ITU-T' => '/^\+(?:[0-9] ?){6,14}[0-9]$/',
- 'EPP' => '/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/',
- );
+ /*
+ * @link http://www.nanpa.com/
+ * @link http://tools.ietf.org/html/rfc4933
+ * @link http://www.itu.int/rec/T-REC-E.164/en
+ *
+ * Regex by Steve Levithan
+ * @link http://blog.stevenlevithan.com/archives/validate-phone-number
+ * @note that valid ITU-T and EPP must begin with +.
+ */
+ $regexarray = array(
+ 'NANP' => '/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/',
+ 'ITU-T' => '/^\+(?:[0-9] ?){6,14}[0-9]$/',
+ 'EPP' => '/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/',
+ );
- if (isset($element['plan']))
- {
- $plan = (string) $element['plan'];
+ if (isset($element['plan'])) {
+ $plan = (string) $element['plan'];
- if ($plan === 'northamerica' || $plan === 'us')
- {
- $plan = 'NANP';
- }
- elseif ($plan === 'International' || $plan === 'int' || $plan === 'missdn' || !$plan)
- {
- $plan = 'ITU-T';
- }
- elseif ($plan === 'IETF')
- {
- $plan = 'EPP';
- }
+ if ($plan === 'northamerica' || $plan === 'us') {
+ $plan = 'NANP';
+ } elseif ($plan === 'International' || $plan === 'int' || $plan === 'missdn' || !$plan) {
+ $plan = 'ITU-T';
+ } elseif ($plan === 'IETF') {
+ $plan = 'EPP';
+ }
- $regex = $regexarray[$plan];
+ $regex = $regexarray[$plan];
- // Test the value against the regular expression.
- if (preg_match($regex, $value) == false)
- {
- return false;
- }
- }
- else
- {
- /*
- * If the rule is set but no plan is selected just check that there are between
- * 7 and 15 digits inclusive and no illegal characters (but common number separators
- * are allowed).
- */
- $cleanvalue = preg_replace('/[+. \-(\)]/', '', $value);
- $regex = '/^[0-9]{7,15}?$/';
+ // Test the value against the regular expression.
+ if (preg_match($regex, $value) == false) {
+ return false;
+ }
+ } else {
+ /*
+ * If the rule is set but no plan is selected just check that there are between
+ * 7 and 15 digits inclusive and no illegal characters (but common number separators
+ * are allowed).
+ */
+ $cleanvalue = preg_replace('/[+. \-(\)]/', '', $value);
+ $regex = '/^[0-9]{7,15}?$/';
- if (preg_match($regex, $cleanvalue) == true)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
+ if (preg_match($regex, $cleanvalue) == true) {
+ return true;
+ } else {
+ return false;
+ }
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/TimeRule.php b/libraries/src/Form/Rule/TimeRule.php
index b8da759dd2e16..3fcc918c73eda 100644
--- a/libraries/src/Form/Rule/TimeRule.php
+++ b/libraries/src/Form/Rule/TimeRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 4.0.0
- *
- * @throws \Exception
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null): bool
- {
- // Check if the field is required.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
-
- // If the value is empty and the field is not required return True.
- if (($value === '' || $value === null) && !$required)
- {
- return true;
- }
-
- $stringValue = (string) $value;
-
- // If the length of a field is smaller than 5 return error message
- if (strlen($stringValue) !== 5 && !isset($element['step']))
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If the third symbol isn't a ':' return error message
- if ($stringValue[2] !== ':')
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If the are other symbols except of numbers and ':' return error message
- if (!preg_match('#^[0-9:]+$#', $stringValue))
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If min and max is set
- if (isset($element['min']) && isset($element['max']))
- {
- $min = $element['min'][0] . $element['min'][1];
- $max = $element['max'][0] . $element['max'][1];
-
- // If the input is smaller than the set min return error message
- if (intval($min) > intval($stringValue[0] . $stringValue[1]))
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_MIN_TIME', $min),
- 'warning'
- );
-
- return false;
- }
-
- // If the input is greater than the set max return error message
- if (intval($max) < intval($stringValue[0] . $stringValue[1]))
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_MAX_TIME'),
- 'warning'
- );
-
- return false;
- }
-
- // If the hour input is equal to the set max but the minutes input is greater than zero return error message
- if (intval($max) === intval($stringValue[0] . $stringValue[1]))
- {
- if (intval($element['min'][3] . $element['min'][4]) !== 0)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_MAX_TIME'),
- 'warning'
- );
-
- return false;
- }
- }
- }
-
- // If the first symbol is greater than 2 return error message
- if (intval($stringValue[0]) > 2)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If the first symbol is greater than 2 and the second symbol is greater than 3 return error message
- if (intval($stringValue[0]) === 2 && intval($stringValue[1]) > 3)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If the fourth symbol is greater than 5 return error message
- if (intval($stringValue[3]) > 5)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
- 'warning'
- );
-
- return false;
- }
-
- // If the step is set return same error messages as above but taking into a count that there 8 and not 5 symbols
- if (isset($element['step']))
- {
- if (strlen($stringValue) !== 8
- || intval($stringValue[5]) !== ':'
- || intval($stringValue[6]) > 5)
- {
- Factory::getApplication()->enqueueMessage(
- Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT_SECONDS'),
- 'warning'
- );
-
- return false;
- }
- }
-
- return true;
- }
+ /**
+ * Method to test the range for a number value using min and max attributes.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 4.0.0
+ *
+ * @throws \Exception
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null): bool
+ {
+ // Check if the field is required.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+
+ // If the value is empty and the field is not required return True.
+ if (($value === '' || $value === null) && !$required) {
+ return true;
+ }
+
+ $stringValue = (string) $value;
+
+ // If the length of a field is smaller than 5 return error message
+ if (strlen($stringValue) !== 5 && !isset($element['step'])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the third symbol isn't a ':' return error message
+ if ($stringValue[2] !== ':') {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the are other symbols except of numbers and ':' return error message
+ if (!preg_match('#^[0-9:]+$#', $stringValue)) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If min and max is set
+ if (isset($element['min']) && isset($element['max'])) {
+ $min = $element['min'][0] . $element['min'][1];
+ $max = $element['max'][0] . $element['max'][1];
+
+ // If the input is smaller than the set min return error message
+ if (intval($min) > intval($stringValue[0] . $stringValue[1])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_MIN_TIME', $min),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the input is greater than the set max return error message
+ if (intval($max) < intval($stringValue[0] . $stringValue[1])) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_MAX_TIME'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the hour input is equal to the set max but the minutes input is greater than zero return error message
+ if (intval($max) === intval($stringValue[0] . $stringValue[1])) {
+ if (intval($element['min'][3] . $element['min'][4]) !== 0) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_MAX_TIME'),
+ 'warning'
+ );
+
+ return false;
+ }
+ }
+ }
+
+ // If the first symbol is greater than 2 return error message
+ if (intval($stringValue[0]) > 2) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the first symbol is greater than 2 and the second symbol is greater than 3 return error message
+ if (intval($stringValue[0]) === 2 && intval($stringValue[1]) > 3) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the fourth symbol is greater than 5 return error message
+ if (intval($stringValue[3]) > 5) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT'),
+ 'warning'
+ );
+
+ return false;
+ }
+
+ // If the step is set return same error messages as above but taking into a count that there 8 and not 5 symbols
+ if (isset($element['step'])) {
+ if (
+ strlen($stringValue) !== 8
+ || intval($stringValue[5]) !== ':'
+ || intval($stringValue[6]) > 5
+ ) {
+ Factory::getApplication()->enqueueMessage(
+ Text::_('JLIB_FORM_FIELD_INVALID_TIME_INPUT_SECONDS'),
+ 'warning'
+ );
+
+ return false;
+ }
+ }
+
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/UrlRule.php b/libraries/src/Form/Rule/UrlRule.php
index f1d8bbb00ab70..c0e9e54856345 100644
--- a/libraries/src/Form/Rule/UrlRule.php
+++ b/libraries/src/Form/Rule/UrlRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- * @link https://www.w3.org/Addressing/URL/url-spec.txt
- * @see \Joomla\String\StringHelper
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // If the field is empty and not required, the field is valid.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
-
- if (!$required && empty($value))
- {
- return true;
- }
-
- $urlParts = UriHelper::parse_url($value);
-
- // See https://www.w3.org/Addressing/URL/url-spec.txt
- // Use the full list or optionally specify a list of permitted schemes.
- if ($element['schemes'] == '')
- {
- $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp', 'tn3270', 'wais',
- 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax', 'modem', 'git');
- }
- else
- {
- $scheme = explode(',', $element['schemes']);
- }
-
- /*
- * Note that parse_url() does not always parse accurately without a scheme,
- * but at least the path should be set always. Note also that parse_url()
- * returns False for seriously malformed URLs instead of an associative array.
- * @link https://www.php.net/manual/en/function.parse-url.php
- */
- if ($urlParts === false || !\array_key_exists('scheme', $urlParts))
- {
- /*
- * The function parse_url() returned false (seriously malformed URL) or no scheme
- * was found and the relative option is not set: in both cases the field is not valid.
- */
- if ($urlParts === false || !$element['relative'])
- {
- $element->addAttribute('message', Text::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme)));
-
- return false;
- }
-
- // The best we can do for the rest is make sure that the path exists and is valid UTF-8.
- if (!\array_key_exists('path', $urlParts) || !StringHelper::valid((string) $urlParts['path']))
- {
- return false;
- }
-
- // The internal URL seems to be good.
- return true;
- }
-
- // Scheme found, check all parts found.
- $urlScheme = (string) $urlParts['scheme'];
- $urlScheme = strtolower($urlScheme);
-
- if (\in_array($urlScheme, $scheme) == false)
- {
- return false;
- }
-
- // For some schemes here must be two slashes.
- $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais', 'prospero', 'sftp', 'telnet', 'git');
-
- if (\in_array($urlScheme, $scheme) && substr($value, \strlen($urlScheme), 3) !== '://')
- {
- return false;
- }
-
- // The best we can do for the rest is make sure that the strings are valid UTF-8
- // and the port is an integer.
- if (\array_key_exists('host', $urlParts) && !StringHelper::valid((string) $urlParts['host']))
- {
- return false;
- }
-
- if (\array_key_exists('port', $urlParts) && !\is_int((int) $urlParts['port']))
- {
- return false;
- }
-
- if (\array_key_exists('path', $urlParts) && !StringHelper::valid((string) $urlParts['path']))
- {
- return false;
- }
-
- return true;
- }
+ /**
+ * Method to test an external or internal url for all valid parts.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ * @link https://www.w3.org/Addressing/URL/url-spec.txt
+ * @see \Joomla\String\StringHelper
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // If the field is empty and not required, the field is valid.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+
+ if (!$required && empty($value)) {
+ return true;
+ }
+
+ $urlParts = UriHelper::parse_url($value);
+
+ // See https://www.w3.org/Addressing/URL/url-spec.txt
+ // Use the full list or optionally specify a list of permitted schemes.
+ if ($element['schemes'] == '') {
+ $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp', 'tn3270', 'wais',
+ 'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax', 'modem', 'git');
+ } else {
+ $scheme = explode(',', $element['schemes']);
+ }
+
+ /*
+ * Note that parse_url() does not always parse accurately without a scheme,
+ * but at least the path should be set always. Note also that parse_url()
+ * returns False for seriously malformed URLs instead of an associative array.
+ * @link https://www.php.net/manual/en/function.parse-url.php
+ */
+ if ($urlParts === false || !\array_key_exists('scheme', $urlParts)) {
+ /*
+ * The function parse_url() returned false (seriously malformed URL) or no scheme
+ * was found and the relative option is not set: in both cases the field is not valid.
+ */
+ if ($urlParts === false || !$element['relative']) {
+ $element->addAttribute('message', Text::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme)));
+
+ return false;
+ }
+
+ // The best we can do for the rest is make sure that the path exists and is valid UTF-8.
+ if (!\array_key_exists('path', $urlParts) || !StringHelper::valid((string) $urlParts['path'])) {
+ return false;
+ }
+
+ // The internal URL seems to be good.
+ return true;
+ }
+
+ // Scheme found, check all parts found.
+ $urlScheme = (string) $urlParts['scheme'];
+ $urlScheme = strtolower($urlScheme);
+
+ if (\in_array($urlScheme, $scheme) == false) {
+ return false;
+ }
+
+ // For some schemes here must be two slashes.
+ $scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais', 'prospero', 'sftp', 'telnet', 'git');
+
+ if (\in_array($urlScheme, $scheme) && substr($value, \strlen($urlScheme), 3) !== '://') {
+ return false;
+ }
+
+ // The best we can do for the rest is make sure that the strings are valid UTF-8
+ // and the port is an integer.
+ if (\array_key_exists('host', $urlParts) && !StringHelper::valid((string) $urlParts['host'])) {
+ return false;
+ }
+
+ if (\array_key_exists('port', $urlParts) && !\is_int((int) $urlParts['port'])) {
+ return false;
+ }
+
+ if (\array_key_exists('path', $urlParts) && !StringHelper::valid((string) $urlParts['path'])) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/libraries/src/Form/Rule/UserIdRule.php b/libraries/src/Form/Rule/UserIdRule.php
index 870d240a6e2ac..25fe20034dd13 100644
--- a/libraries/src/Form/Rule/UserIdRule.php
+++ b/libraries/src/Form/Rule/UserIdRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param ?string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param ?Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param ?Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 4.0.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Check if the field is required.
- $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
+ /**
+ * Method to test the validity of a Joomla User.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param ?string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param ?Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param ?Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 4.0.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Check if the field is required.
+ $required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');
- // If the value is empty, null or has the value 0 and the field is not required return true else return false
- if (($value === '' || $value === null || (string) $value === '0'))
- {
- return !$required;
- }
+ // If the value is empty, null or has the value 0 and the field is not required return true else return false
+ if (($value === '' || $value === null || (string) $value === '0')) {
+ return !$required;
+ }
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
- // Build the query.
- $query->select('COUNT(*)')
- ->from($db->quoteName('#__users'))
- ->where($db->quoteName('id') . ' = :userId')
- ->bind(':userId', $value, ParameterType::INTEGER);
+ // Build the query.
+ $query->select('COUNT(*)')
+ ->from($db->quoteName('#__users'))
+ ->where($db->quoteName('id') . ' = :userId')
+ ->bind(':userId', $value, ParameterType::INTEGER);
- // Set and query the database.
- return (bool) $db->setQuery($query)->loadResult();
- }
+ // Set and query the database.
+ return (bool) $db->setQuery($query)->loadResult();
+ }
}
diff --git a/libraries/src/Form/Rule/UsernameRule.php b/libraries/src/Form/Rule/UsernameRule.php
index 8d5fea60565b4..9c968e9b9a84a 100644
--- a/libraries/src/Form/Rule/UsernameRule.php
+++ b/libraries/src/Form/Rule/UsernameRule.php
@@ -1,4 +1,5 @@
` tag for the form field object.
- * @param mixed $value The form field value to validate.
- * @param string $group The field name group control value. This acts as an array container for the field.
- * For example if the field has name="foo" and the group value is set to "bar" then the
- * full field name would end up being "bar[foo]".
- * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
- * @param Form $form The form object for which the field is being tested.
- *
- * @return boolean True if the value is valid, false otherwise.
- *
- * @since 1.7.0
- */
- public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
- {
- // Get the database object and a new query object.
- $db = $this->getDatabase();
- $query = $db->getQuery(true);
+ /**
+ * Method to test the username for uniqueness.
+ *
+ * @param \SimpleXMLElement $element The SimpleXMLElement object representing the `` tag for the form field object.
+ * @param mixed $value The form field value to validate.
+ * @param string $group The field name group control value. This acts as an array container for the field.
+ * For example if the field has name="foo" and the group value is set to "bar" then the
+ * full field name would end up being "bar[foo]".
+ * @param Registry $input An optional Registry object with the entire data set to validate against the entire form.
+ * @param Form $form The form object for which the field is being tested.
+ *
+ * @return boolean True if the value is valid, false otherwise.
+ *
+ * @since 1.7.0
+ */
+ public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
+ {
+ // Get the database object and a new query object.
+ $db = $this->getDatabase();
+ $query = $db->getQuery(true);
- // Get the extra field check attribute.
- $userId = ($form instanceof Form) ? (int) $form->getValue('id') : 0;
+ // Get the extra field check attribute.
+ $userId = ($form instanceof Form) ? (int) $form->getValue('id') : 0;
- // Build the query.
- $query->select('COUNT(*)')
- ->from($db->quoteName('#__users'))
- ->where(
- [
- $db->quoteName('username') . ' = :username',
- $db->quoteName('id') . ' <> :userId',
- ]
- )
- ->bind(':username', $value)
- ->bind(':userId', $userId, ParameterType::INTEGER);
+ // Build the query.
+ $query->select('COUNT(*)')
+ ->from($db->quoteName('#__users'))
+ ->where(
+ [
+ $db->quoteName('username') . ' = :username',
+ $db->quoteName('id') . ' <> :userId',
+ ]
+ )
+ ->bind(':username', $value)
+ ->bind(':userId', $userId, ParameterType::INTEGER);
- // Set and query the database.
- $db->setQuery($query);
- $duplicate = (bool) $db->loadResult();
+ // Set and query the database.
+ $db->setQuery($query);
+ $duplicate = (bool) $db->loadResult();
- if ($duplicate)
- {
- return false;
- }
+ if ($duplicate) {
+ return false;
+ }
- return true;
- }
+ return true;
+ }
}
diff --git a/libraries/src/HTML/HTMLHelper.php b/libraries/src/HTML/HTMLHelper.php
index 0d3cd92344666..f0892b1e5158d 100644
--- a/libraries/src/HTML/HTMLHelper.php
+++ b/libraries/src/HTML/HTMLHelper.php
@@ -1,4 +1,5 @@
0, 'format.eol' => "\n", 'format.indent' => "\t");
-
- /**
- * An array to hold included paths
- *
- * @var string[]
- * @since 1.5
- * @deprecated 5.0
- */
- protected static $includePaths = array();
-
- /**
- * An array to hold method references
- *
- * @var callable[]
- * @since 1.6
- * @deprecated 5.0
- */
- protected static $registry = array();
-
- /**
- * The service registry for custom and overridden JHtml helpers
- *
- * @var Registry
- * @since 4.0.0
- */
- protected static $serviceRegistry;
-
- /**
- * Method to extract a key
- *
- * @param string $key The name of helper method to load, (prefix).(class).function
- * prefix and class are optional and can be used to load custom html helpers.
- *
- * @return array Contains lowercase key, prefix, file, function.
- *
- * @since 1.6
- * @deprecated 5.0 Use the service registry instead
- */
- protected static function extract($key)
- {
- $key = preg_replace('#[^A-Z0-9_\.]#i', '', $key);
-
- // Check to see whether we need to load a helper file
- $parts = explode('.', $key);
-
- if (\count($parts) === 3)
- {
- @trigger_error(
- 'Support for a three segment service key is deprecated and will be removed in Joomla 5.0, use the service registry instead',
- E_USER_DEPRECATED
- );
- }
-
- $prefix = \count($parts) === 3 ? array_shift($parts) : 'JHtml';
- $file = \count($parts) === 2 ? array_shift($parts) : '';
- $func = array_shift($parts);
-
- return array(strtolower($prefix . '.' . $file . '.' . $func), $prefix, $file, $func);
- }
-
- /**
- * Class loader method
- *
- * Additional arguments may be supplied and are passed to the sub-class.
- * Additional include paths are also able to be specified for third-party use
- *
- * @param string $key The name of helper method to load, (prefix).(class).function
- * prefix and class are optional and can be used to load custom
- * html helpers.
- * @param array $methodArgs The arguments to pass forward to the method being called
- *
- * @return mixed Result of HTMLHelper::call($function, $args)
- *
- * @since 1.5
- * @throws \InvalidArgumentException
- */
- final public static function _(string $key, ...$methodArgs)
- {
- list($key, $prefix, $file, $func) = static::extract($key);
-
- if (\array_key_exists($key, static::$registry))
- {
- $function = static::$registry[$key];
-
- return static::call($function, $methodArgs);
- }
-
- /*
- * Support fetching services from the registry if a custom class prefix was not given (a three segment key),
- * the service comes from a class other than this one, and a service has been registered for the file.
- */
- if ($prefix === 'JHtml' && $file !== '' && static::getServiceRegistry()->hasService($file))
- {
- $service = static::getServiceRegistry()->getService($file);
-
- $toCall = array($service, $func);
-
- if (!\is_callable($toCall))
- {
- throw new \InvalidArgumentException(sprintf('%s::%s not found.', $file, $func), 500);
- }
-
- static::register($key, $toCall);
-
- return static::call($toCall, $methodArgs);
- }
-
- $className = $prefix . ucfirst($file);
-
- if (!class_exists($className))
- {
- $path = Path::find(static::$includePaths, strtolower($file) . '.php');
-
- if (!$path)
- {
- throw new \InvalidArgumentException(sprintf('%s %s not found.', $prefix, $file), 500);
- }
-
- \JLoader::register($className, $path);
-
- if (!class_exists($className))
- {
- throw new \InvalidArgumentException(sprintf('%s not found.', $className), 500);
- }
- }
-
- // If calling a method from this class, do not allow access to internal methods
- if ($className === __CLASS__)
- {
- if (!((new \ReflectionMethod($className, $func))->isPublic()))
- {
- throw new \InvalidArgumentException('Access to internal class methods is not allowed.');
- }
- }
-
- $toCall = array($className, $func);
-
- if (!\is_callable($toCall))
- {
- throw new \InvalidArgumentException(sprintf('%s::%s not found.', $className, $func), 500);
- }
-
- static::register($key, $toCall);
-
- return static::call($toCall, $methodArgs);
- }
-
- /**
- * Registers a function to be called with a specific key
- *
- * @param string $key The name of the key
- * @param callable $function Function or method
- *
- * @return boolean True if the function is callable
- *
- * @since 1.6
- * @deprecated 5.0 Use the service registry instead
- */
- public static function register($key, callable $function)
- {
- @trigger_error(
- 'Support for registering functions is deprecated and will be removed in Joomla 5.0, use the service registry instead',
- E_USER_DEPRECATED
- );
-
- list($key) = static::extract($key);
-
- static::$registry[$key] = $function;
-
- return true;
- }
-
- /**
- * Removes a key for a method from registry.
- *
- * @param string $key The name of the key
- *
- * @return boolean True if a set key is unset
- *
- * @since 1.6
- * @deprecated 5.0 Use the service registry instead
- */
- public static function unregister($key)
- {
- @trigger_error(
- 'Support for registering functions is deprecated and will be removed in Joomla 5.0, use the service registry instead',
- E_USER_DEPRECATED
- );
-
- list($key) = static::extract($key);
-
- if (isset(static::$registry[$key]))
- {
- unset(static::$registry[$key]);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Test if the key is registered.
- *
- * @param string $key The name of the key
- *
- * @return boolean True if the key is registered.
- *
- * @since 1.6
- */
- public static function isRegistered($key)
- {
- list($key) = static::extract($key);
-
- return isset(static::$registry[$key]);
- }
-
- /**
- * Retrieves the service registry.
- *
- * @return Registry
- *
- * @since 4.0.0
- */
- public static function getServiceRegistry(): Registry
- {
- if (!static::$serviceRegistry)
- {
- static::$serviceRegistry = Factory::getContainer()->get(Registry::class);
- }
-
- return static::$serviceRegistry;
- }
-
- /**
- * Function caller method
- *
- * @param callable $function Function or method to call
- * @param array $args Arguments to be passed to function
- *
- * @return mixed Function result or false on error.
- *
- * @link https://www.php.net/manual/en/function.call-user-func-array.php
- * @since 1.6
- * @throws \InvalidArgumentException
- */
- protected static function call(callable $function, $args)
- {
- // Workaround to allow calling helper methods have arguments passed by reference
- $temp = [];
-
- foreach ($args as &$arg)
- {
- $temp[] = &$arg;
- }
-
- return \call_user_func_array($function, $temp);
- }
-
- /**
- * Write a `` element
- *
- * @param string $url The relative URL to use for the href attribute
- * @param string $text The target attribute to use
- * @param array|string $attribs Attributes to be added to the ` ` element
- *
- * @return string
- *
- * @since 1.5
- */
- public static function link($url, $text, $attribs = null)
- {
- if (\is_array($attribs))
- {
- $attribs = ArrayHelper::toString($attribs);
- }
-
- return ' ' . $text . ' ';
- }
-
- /**
- * Write a `